From c352e69689d05ccb53617db07de7c9260e402f5a Mon Sep 17 00:00:00 2001 From: Ishmeet Date: Fri, 31 Jul 2020 22:30:45 +0530 Subject: [PATCH] examples: Add a snake game (#1276) --- examples/snake/main.go | 208 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 examples/snake/main.go diff --git a/examples/snake/main.go b/examples/snake/main.go new file mode 100644 index 000000000..44211b888 --- /dev/null +++ b/examples/snake/main.go @@ -0,0 +1,208 @@ +// 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. + +// +build example jsgo + +package main + +import ( + "fmt" + "image/color" + "log" + "math/rand" + "time" + + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/ebitenutil" + "github.com/hajimehoshi/ebiten/inpututil" +) + +const ( + screenWidth = 640 + screenHeight = 480 + gridSize = 10 + xNumInScreen = screenWidth / gridSize + yNumInScreen = 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 init() { + rand.Seed(time.Now().UnixNano()) +} + +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 >= xNumInScreen || + g.snakeBody[0].Y >= yNumInScreen +} + +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 = xNumInScreen / 2 + g.snakeBody[0].Y = yNumInScreen / 2 + g.score = 0 + g.level = 1 + g.moveDirection = dirNone +} + +func (g *Game) Update(screen *ebiten.Image) error { + if inpututil.IsKeyJustPressed(ebiten.KeyLeft) || inpututil.IsKeyJustPressed(ebiten.KeyA) { + if g.moveDirection != dirRight { + g.moveDirection = dirLeft + } + } else if inpututil.IsKeyJustPressed(ebiten.KeyRight) || inpututil.IsKeyJustPressed(ebiten.KeyD) { + if g.moveDirection != dirLeft { + g.moveDirection = dirRight + } + } else if inpututil.IsKeyJustPressed(ebiten.KeyDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) { + if g.moveDirection != dirUp { + g.moveDirection = dirDown + } + } else if inpututil.IsKeyJustPressed(ebiten.KeyUp) || 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(xNumInScreen - 1) + g.apple.Y = rand.Intn(yNumInScreen - 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 { + ebitenutil.DrawRect(screen, float64(v.X*gridSize), float64(v.Y*gridSize), gridSize, gridSize, color.RGBA{0x80, 0xa0, 0xc0, 0xff}) + } + ebitenutil.DrawRect(screen, float64(g.apple.X*gridSize), float64(g.apple.Y*gridSize), gridSize, gridSize, color.RGBA{0xFF, 0x00, 0x00, 0xff}) + + 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.CurrentFPS(), 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 = xNumInScreen / 2 + g.snakeBody[0].Y = yNumInScreen / 2 + return g +} + +func main() { + ebiten.SetWindowSize(screenWidth, screenHeight) + ebiten.SetWindowTitle("Snake (Ebiten Demo)") + if err := ebiten.RunGame(newGame()); err != nil { + log.Fatal(err) + } +}