123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- // Copyright 2020 The Ebiten Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package main
- import (
- "fmt"
- "image/color"
- "log"
- "math/rand/v2"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/ebitenutil"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
- "github.com/hajimehoshi/ebiten/v2/vector"
- )
- const (
- screenWidth = 640
- screenHeight = 480
- gridSize = 10
- xGridCountInScreen = screenWidth / gridSize
- yGridCountInScreen = screenHeight / gridSize
- )
- const (
- dirNone = iota
- dirLeft
- dirRight
- dirDown
- dirUp
- )
- type Position struct {
- X int
- Y int
- }
- type Game struct {
- moveDirection int
- snakeBody []Position
- apple Position
- timer int
- moveTime int
- score int
- bestScore int
- level int
- }
- func (g *Game) collidesWithApple() bool {
- return g.snakeBody[0].X == g.apple.X &&
- g.snakeBody[0].Y == g.apple.Y
- }
- func (g *Game) collidesWithSelf() bool {
- for _, v := range g.snakeBody[1:] {
- if g.snakeBody[0].X == v.X &&
- g.snakeBody[0].Y == v.Y {
- return true
- }
- }
- return false
- }
- func (g *Game) collidesWithWall() bool {
- return g.snakeBody[0].X < 0 ||
- g.snakeBody[0].Y < 0 ||
- g.snakeBody[0].X >= xGridCountInScreen ||
- g.snakeBody[0].Y >= yGridCountInScreen
- }
- func (g *Game) needsToMoveSnake() bool {
- return g.timer%g.moveTime == 0
- }
- func (g *Game) reset() {
- g.apple.X = 3 * gridSize
- g.apple.Y = 3 * gridSize
- g.moveTime = 4
- g.snakeBody = g.snakeBody[:1]
- g.snakeBody[0].X = xGridCountInScreen / 2
- g.snakeBody[0].Y = yGridCountInScreen / 2
- g.score = 0
- g.level = 1
- g.moveDirection = dirNone
- }
- func (g *Game) Update() error {
- // Decide the snake's direction along with the user input.
- // A U-turn is forbidden here (e.g. if the snake is moving in the left direction, the snake cannot go to the right direction immediately).
- if inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) || inpututil.IsKeyJustPressed(ebiten.KeyA) {
- if g.moveDirection != dirRight {
- g.moveDirection = dirLeft
- }
- } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) || inpututil.IsKeyJustPressed(ebiten.KeyD) {
- if g.moveDirection != dirLeft {
- g.moveDirection = dirRight
- }
- } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
- if g.moveDirection != dirUp {
- g.moveDirection = dirDown
- }
- } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
- if g.moveDirection != dirDown {
- g.moveDirection = dirUp
- }
- } else if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
- g.reset()
- }
- if g.needsToMoveSnake() {
- if g.collidesWithWall() || g.collidesWithSelf() {
- g.reset()
- }
- if g.collidesWithApple() {
- g.apple.X = rand.IntN(xGridCountInScreen - 1)
- g.apple.Y = rand.IntN(yGridCountInScreen - 1)
- g.snakeBody = append(g.snakeBody, Position{
- X: g.snakeBody[len(g.snakeBody)-1].X,
- Y: g.snakeBody[len(g.snakeBody)-1].Y,
- })
- if len(g.snakeBody) > 10 && len(g.snakeBody) < 20 {
- g.level = 2
- g.moveTime = 3
- } else if len(g.snakeBody) > 20 {
- g.level = 3
- g.moveTime = 2
- } else {
- g.level = 1
- }
- g.score++
- if g.bestScore < g.score {
- g.bestScore = g.score
- }
- }
- for i := int64(len(g.snakeBody)) - 1; i > 0; i-- {
- g.snakeBody[i].X = g.snakeBody[i-1].X
- g.snakeBody[i].Y = g.snakeBody[i-1].Y
- }
- switch g.moveDirection {
- case dirLeft:
- g.snakeBody[0].X--
- case dirRight:
- g.snakeBody[0].X++
- case dirDown:
- g.snakeBody[0].Y++
- case dirUp:
- g.snakeBody[0].Y--
- }
- }
- g.timer++
- return nil
- }
- func (g *Game) Draw(screen *ebiten.Image) {
- for _, v := range g.snakeBody {
- vector.DrawFilledRect(screen, float32(v.X*gridSize), float32(v.Y*gridSize), gridSize, gridSize, color.RGBA{0x80, 0xa0, 0xc0, 0xff}, false)
- }
- vector.DrawFilledRect(screen, float32(g.apple.X*gridSize), float32(g.apple.Y*gridSize), gridSize, gridSize, color.RGBA{0xFF, 0x00, 0x00, 0xff}, false)
- if g.moveDirection == dirNone {
- ebitenutil.DebugPrint(screen, fmt.Sprintf("Press up/down/left/right to start"))
- } else {
- ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f Level: %d Score: %d Best Score: %d", ebiten.ActualFPS(), g.level, g.score, g.bestScore))
- }
- }
- func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
- return screenWidth, screenHeight
- }
- func newGame() *Game {
- g := &Game{
- apple: Position{X: 3 * gridSize, Y: 3 * gridSize},
- moveTime: 4,
- snakeBody: make([]Position, 1),
- }
- g.snakeBody[0].X = xGridCountInScreen / 2
- g.snakeBody[0].Y = yGridCountInScreen / 2
- return g
- }
- func main() {
- ebiten.SetWindowSize(screenWidth, screenHeight)
- ebiten.SetWindowTitle("Snake (Ebitengine Demo)")
- if err := ebiten.RunGame(newGame()); err != nil {
- log.Fatal(err)
- }
- }
|