main.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Copyright 2020 The Ebiten Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "fmt"
  17. "image/color"
  18. "log"
  19. "math/rand/v2"
  20. "github.com/hajimehoshi/ebiten/v2"
  21. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  22. "github.com/hajimehoshi/ebiten/v2/inpututil"
  23. "github.com/hajimehoshi/ebiten/v2/vector"
  24. )
  25. const (
  26. screenWidth = 640
  27. screenHeight = 480
  28. gridSize = 10
  29. xGridCountInScreen = screenWidth / gridSize
  30. yGridCountInScreen = screenHeight / gridSize
  31. )
  32. const (
  33. dirNone = iota
  34. dirLeft
  35. dirRight
  36. dirDown
  37. dirUp
  38. )
  39. type Position struct {
  40. X int
  41. Y int
  42. }
  43. type Game struct {
  44. moveDirection int
  45. snakeBody []Position
  46. apple Position
  47. timer int
  48. moveTime int
  49. score int
  50. bestScore int
  51. level int
  52. }
  53. func (g *Game) collidesWithApple() bool {
  54. return g.snakeBody[0].X == g.apple.X &&
  55. g.snakeBody[0].Y == g.apple.Y
  56. }
  57. func (g *Game) collidesWithSelf() bool {
  58. for _, v := range g.snakeBody[1:] {
  59. if g.snakeBody[0].X == v.X &&
  60. g.snakeBody[0].Y == v.Y {
  61. return true
  62. }
  63. }
  64. return false
  65. }
  66. func (g *Game) collidesWithWall() bool {
  67. return g.snakeBody[0].X < 0 ||
  68. g.snakeBody[0].Y < 0 ||
  69. g.snakeBody[0].X >= xGridCountInScreen ||
  70. g.snakeBody[0].Y >= yGridCountInScreen
  71. }
  72. func (g *Game) needsToMoveSnake() bool {
  73. return g.timer%g.moveTime == 0
  74. }
  75. func (g *Game) reset() {
  76. g.apple.X = 3 * gridSize
  77. g.apple.Y = 3 * gridSize
  78. g.moveTime = 4
  79. g.snakeBody = g.snakeBody[:1]
  80. g.snakeBody[0].X = xGridCountInScreen / 2
  81. g.snakeBody[0].Y = yGridCountInScreen / 2
  82. g.score = 0
  83. g.level = 1
  84. g.moveDirection = dirNone
  85. }
  86. func (g *Game) Update() error {
  87. // Decide the snake's direction along with the user input.
  88. // 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).
  89. if inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) || inpututil.IsKeyJustPressed(ebiten.KeyA) {
  90. if g.moveDirection != dirRight {
  91. g.moveDirection = dirLeft
  92. }
  93. } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) || inpututil.IsKeyJustPressed(ebiten.KeyD) {
  94. if g.moveDirection != dirLeft {
  95. g.moveDirection = dirRight
  96. }
  97. } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) {
  98. if g.moveDirection != dirUp {
  99. g.moveDirection = dirDown
  100. }
  101. } else if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) {
  102. if g.moveDirection != dirDown {
  103. g.moveDirection = dirUp
  104. }
  105. } else if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
  106. g.reset()
  107. }
  108. if g.needsToMoveSnake() {
  109. if g.collidesWithWall() || g.collidesWithSelf() {
  110. g.reset()
  111. }
  112. if g.collidesWithApple() {
  113. g.apple.X = rand.IntN(xGridCountInScreen - 1)
  114. g.apple.Y = rand.IntN(yGridCountInScreen - 1)
  115. g.snakeBody = append(g.snakeBody, Position{
  116. X: g.snakeBody[len(g.snakeBody)-1].X,
  117. Y: g.snakeBody[len(g.snakeBody)-1].Y,
  118. })
  119. if len(g.snakeBody) > 10 && len(g.snakeBody) < 20 {
  120. g.level = 2
  121. g.moveTime = 3
  122. } else if len(g.snakeBody) > 20 {
  123. g.level = 3
  124. g.moveTime = 2
  125. } else {
  126. g.level = 1
  127. }
  128. g.score++
  129. if g.bestScore < g.score {
  130. g.bestScore = g.score
  131. }
  132. }
  133. for i := int64(len(g.snakeBody)) - 1; i > 0; i-- {
  134. g.snakeBody[i].X = g.snakeBody[i-1].X
  135. g.snakeBody[i].Y = g.snakeBody[i-1].Y
  136. }
  137. switch g.moveDirection {
  138. case dirLeft:
  139. g.snakeBody[0].X--
  140. case dirRight:
  141. g.snakeBody[0].X++
  142. case dirDown:
  143. g.snakeBody[0].Y++
  144. case dirUp:
  145. g.snakeBody[0].Y--
  146. }
  147. }
  148. g.timer++
  149. return nil
  150. }
  151. func (g *Game) Draw(screen *ebiten.Image) {
  152. for _, v := range g.snakeBody {
  153. vector.DrawFilledRect(screen, float32(v.X*gridSize), float32(v.Y*gridSize), gridSize, gridSize, color.RGBA{0x80, 0xa0, 0xc0, 0xff}, false)
  154. }
  155. vector.DrawFilledRect(screen, float32(g.apple.X*gridSize), float32(g.apple.Y*gridSize), gridSize, gridSize, color.RGBA{0xFF, 0x00, 0x00, 0xff}, false)
  156. if g.moveDirection == dirNone {
  157. ebitenutil.DebugPrint(screen, fmt.Sprintf("Press up/down/left/right to start"))
  158. } else {
  159. ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f Level: %d Score: %d Best Score: %d", ebiten.ActualFPS(), g.level, g.score, g.bestScore))
  160. }
  161. }
  162. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  163. return screenWidth, screenHeight
  164. }
  165. func newGame() *Game {
  166. g := &Game{
  167. apple: Position{X: 3 * gridSize, Y: 3 * gridSize},
  168. moveTime: 4,
  169. snakeBody: make([]Position, 1),
  170. }
  171. g.snakeBody[0].X = xGridCountInScreen / 2
  172. g.snakeBody[0].Y = yGridCountInScreen / 2
  173. return g
  174. }
  175. func main() {
  176. ebiten.SetWindowSize(screenWidth, screenHeight)
  177. ebiten.SetWindowTitle("Snake (Ebitengine Demo)")
  178. if err := ebiten.RunGame(newGame()); err != nil {
  179. log.Fatal(err)
  180. }
  181. }