snake.odin 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package snake
  2. import k2 "../.."
  3. import "core:math"
  4. import "core:fmt"
  5. import "core:time"
  6. import "core:math/rand"
  7. import "base:intrinsics"
  8. import "core:log"
  9. import "core:mem"
  10. _ :: mem
  11. WINDOW_SIZE :: 1000
  12. GRID_WIDTH :: 20
  13. CELL_SIZE :: 16
  14. CANVAS_SIZE :: GRID_WIDTH*CELL_SIZE
  15. TICK_RATE :: 0.13
  16. Vec2i :: [2]int
  17. MAX_SNAKE_LENGTH :: GRID_WIDTH*GRID_WIDTH
  18. snake: [MAX_SNAKE_LENGTH]Vec2i
  19. snake_length: int
  20. tick_timer: f32 = TICK_RATE
  21. move_direction: Vec2i
  22. game_over: bool
  23. food_pos: Vec2i
  24. food_sprite: k2.Texture
  25. head_sprite: k2.Texture
  26. body_sprite: k2.Texture
  27. tail_sprite: k2.Texture
  28. food_eaten_at: time.Time
  29. started_at: time.Time
  30. prev_time: time.Time
  31. place_food :: proc() {
  32. occupied: [GRID_WIDTH][GRID_WIDTH]bool
  33. for i in 0..<snake_length {
  34. occupied[snake[i].x][snake[i].y] = true
  35. }
  36. free_cells := make([dynamic]Vec2i, context.temp_allocator)
  37. for x in 0..<GRID_WIDTH {
  38. for y in 0..<GRID_WIDTH {
  39. if !occupied[x][y] {
  40. append(&free_cells, Vec2i {x, y})
  41. }
  42. }
  43. }
  44. if len(free_cells) > 0 {
  45. random_cell_index := rand.int31_max(i32(len(free_cells)))
  46. food_pos = free_cells[random_cell_index]
  47. }
  48. }
  49. restart :: proc() {
  50. start_head_pos := Vec2i { GRID_WIDTH / 2, GRID_WIDTH / 2 }
  51. snake[0] = start_head_pos
  52. snake[1] = start_head_pos - {0, 1}
  53. snake[2] = start_head_pos - {0, 2}
  54. snake_length = 3
  55. move_direction = {0, 1}
  56. game_over = false
  57. place_food()
  58. }
  59. main :: proc() {
  60. context.logger = log.create_console_logger()
  61. init()
  62. run := true
  63. for run {
  64. run = step()
  65. }
  66. shutdown()
  67. }
  68. font: k2.Font_Handle
  69. init :: proc() {
  70. k2.init(WINDOW_SIZE, WINDOW_SIZE, "Snake")
  71. prev_time = time.now()
  72. restart()
  73. food_sprite = k2.load_texture_from_bytes(#load("food.png"))
  74. head_sprite = k2.load_texture_from_bytes(#load("head.png"))
  75. body_sprite = k2.load_texture_from_bytes(#load("body.png"))
  76. tail_sprite = k2.load_texture_from_bytes(#load("tail.png"))
  77. food_eaten_at = time.now()
  78. started_at = time.now()
  79. }
  80. step :: proc() -> bool {
  81. k2.new_frame()
  82. k2.process_events()
  83. if k2.key_is_held(.Up) || k2.gamepad_button_is_held(0, .Left_Face_Up) {
  84. move_direction = {0, -1}
  85. }
  86. if k2.key_is_held(.Down) || k2.gamepad_button_is_held(0, .Left_Face_Down) {
  87. move_direction = {0, 1}
  88. }
  89. if k2.key_is_held(.Left) || k2.gamepad_button_is_held(0, .Left_Face_Left) {
  90. move_direction = {-1, 0}
  91. }
  92. if k2.key_is_held(.Right) || k2.gamepad_button_is_held(0, .Left_Face_Right) {
  93. move_direction = {1, 0}
  94. }
  95. dt := k2.get_frame_time()
  96. if game_over {
  97. if k2.key_went_down(.Enter) {
  98. restart()
  99. }
  100. } else {
  101. tick_timer -= dt
  102. }
  103. if tick_timer <= 0 {
  104. next_part_pos := snake[0]
  105. snake[0] += move_direction
  106. head_pos := snake[0]
  107. if head_pos.x < 0 || head_pos.y < 0 || head_pos.x >= GRID_WIDTH || head_pos.y >= GRID_WIDTH {
  108. game_over = true
  109. }
  110. for i in 1..<snake_length {
  111. cur_pos := snake[i]
  112. if cur_pos == head_pos {
  113. game_over = true
  114. }
  115. snake[i] = next_part_pos
  116. next_part_pos = cur_pos
  117. }
  118. if head_pos == food_pos {
  119. snake_length += 1
  120. snake[snake_length - 1] = next_part_pos
  121. place_food()
  122. food_eaten_at = time.now()
  123. }
  124. tick_timer = TICK_RATE + tick_timer
  125. }
  126. k2.clear({76, 53, 83, 255})
  127. camera := k2.Camera {
  128. zoom = f32(WINDOW_SIZE) / CANVAS_SIZE,
  129. }
  130. k2.set_camera(camera)
  131. food_sprite.width = CELL_SIZE
  132. food_sprite.height = CELL_SIZE
  133. k2.draw_texture(food_sprite, {f32(food_pos.x), f32(food_pos.y)}*CELL_SIZE)
  134. for i in 0..<snake_length {
  135. part_sprite := body_sprite
  136. dir: Vec2i
  137. if i == 0 {
  138. part_sprite = head_sprite
  139. dir = snake[i] - snake[i + 1]
  140. } else if i == snake_length - 1 {
  141. part_sprite = tail_sprite
  142. dir = snake[i - 1] - snake[i]
  143. } else {
  144. dir = snake[i - 1] - snake[i]
  145. }
  146. rot := math.atan2(f32(dir.y), f32(dir.x)) * math.DEG_PER_RAD
  147. source := k2.Rect {
  148. 0, 0,
  149. f32(part_sprite.width), f32(part_sprite.height),
  150. }
  151. dest := k2.Rect {
  152. f32(snake[i].x)*CELL_SIZE + 0.5*CELL_SIZE,
  153. f32(snake[i].y)*CELL_SIZE + 0.5*CELL_SIZE,
  154. CELL_SIZE,
  155. CELL_SIZE,
  156. }
  157. k2.draw_texture_ex(part_sprite, source, dest, {CELL_SIZE, CELL_SIZE}*0.5, rot)
  158. }
  159. if game_over {
  160. k2.draw_text("Game Over!", {4, 4}, 25, k2.RL_RED)
  161. k2.draw_text("Press Enter to play again", {4, 30}, 15, k2.BLACK)
  162. }
  163. score := snake_length - 3
  164. score_str := fmt.tprintf("Score: %v", score)
  165. k2.draw_text(score_str, {4, CANVAS_SIZE - 14}, 10, k2.RL_GRAY)
  166. k2.present()
  167. free_all(context.temp_allocator)
  168. return !k2.shutdown_wanted()
  169. }
  170. shutdown :: proc() {
  171. k2.destroy_texture(head_sprite)
  172. k2.destroy_texture(food_sprite)
  173. k2.destroy_texture(body_sprite)
  174. k2.destroy_texture(tail_sprite)
  175. k2.shutdown()
  176. }