snake.odin 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. WINDOW_SIZE :: 1000
  11. GRID_WIDTH :: 20
  12. CELL_SIZE :: 16
  13. CANVAS_SIZE :: GRID_WIDTH*CELL_SIZE
  14. TICK_RATE :: 0.13
  15. Vec2i :: [2]int
  16. MAX_SNAKE_LENGTH :: GRID_WIDTH*GRID_WIDTH
  17. snake: [MAX_SNAKE_LENGTH]Vec2i
  18. snake_length: int
  19. tick_timer: f32 = TICK_RATE
  20. move_direction: Vec2i
  21. game_over: bool
  22. food_pos: Vec2i
  23. place_food :: proc() {
  24. occupied: [GRID_WIDTH][GRID_WIDTH]bool
  25. for i in 0..<snake_length {
  26. occupied[snake[i].x][snake[i].y] = true
  27. }
  28. free_cells := make([dynamic]Vec2i, context.temp_allocator)
  29. for x in 0..<GRID_WIDTH {
  30. for y in 0..<GRID_WIDTH {
  31. if !occupied[x][y] {
  32. append(&free_cells, Vec2i {x, y})
  33. }
  34. }
  35. }
  36. if len(free_cells) > 0 {
  37. random_cell_index := rand.int31_max(i32(len(free_cells)))
  38. food_pos = free_cells[random_cell_index]
  39. }
  40. }
  41. restart :: proc() {
  42. start_head_pos := Vec2i { GRID_WIDTH / 2, GRID_WIDTH / 2 }
  43. snake[0] = start_head_pos
  44. snake[1] = start_head_pos - {0, 1}
  45. snake[2] = start_head_pos - {0, 2}
  46. snake_length = 3
  47. move_direction = {0, 1}
  48. game_over = false
  49. place_food()
  50. }
  51. main :: proc() {
  52. context.logger = log.create_console_logger()
  53. when ODIN_DEBUG {
  54. track: mem.Tracking_Allocator
  55. mem.tracking_allocator_init(&track, context.allocator)
  56. context.allocator = mem.tracking_allocator(&track)
  57. defer {
  58. if len(track.allocation_map) > 0 {
  59. for _, entry in track.allocation_map {
  60. fmt.eprintf("%v leaked: %v bytes\n", entry.location, entry.size)
  61. }
  62. }
  63. mem.tracking_allocator_destroy(&track)
  64. }
  65. }
  66. k2.init(WINDOW_SIZE, WINDOW_SIZE, "Snake")
  67. SHADER_SOURCE :: #load("shader.hlsl")
  68. shader := k2.load_shader(string(SHADER_SOURCE), {
  69. .RG32_Float,
  70. .RG32_Float,
  71. .RGBA8_Norm,
  72. .RGBA8_Norm,
  73. })
  74. prev_time := time.now()
  75. restart()
  76. food_sprite := k2.load_texture_from_file("food.png")
  77. head_sprite := k2.load_texture_from_file("head.png")
  78. body_sprite := k2.load_texture_from_file("body.png")
  79. tail_sprite := k2.load_texture_from_file("tail.png")
  80. for !k2.window_should_close() {
  81. time_now := time.now()
  82. dt := f32(time.duration_seconds(time.diff(prev_time, time_now)))
  83. prev_time = time_now
  84. k2.process_events()
  85. if k2.key_is_held(.Up) {
  86. move_direction = {0, -1}
  87. }
  88. if k2.key_is_held(.Down) {
  89. move_direction = {0, 1}
  90. }
  91. if k2.key_is_held(.Left) {
  92. move_direction = {-1, 0}
  93. }
  94. if k2.key_is_held(.Right) {
  95. move_direction = {1, 0}
  96. }
  97. if game_over {
  98. if k2.key_went_down(.Enter) {
  99. restart()
  100. }
  101. } else {
  102. tick_timer -= dt
  103. }
  104. if tick_timer <= 0 {
  105. next_part_pos := snake[0]
  106. snake[0] += move_direction
  107. head_pos := snake[0]
  108. if head_pos.x < 0 || head_pos.y < 0 || head_pos.x >= GRID_WIDTH || head_pos.y >= GRID_WIDTH {
  109. game_over = true
  110. }
  111. for i in 1..<snake_length {
  112. cur_pos := snake[i]
  113. if cur_pos == head_pos {
  114. game_over = true
  115. }
  116. snake[i] = next_part_pos
  117. next_part_pos = cur_pos
  118. }
  119. if head_pos == food_pos {
  120. snake_length += 1
  121. snake[snake_length - 1] = next_part_pos
  122. place_food()
  123. }
  124. tick_timer = TICK_RATE + tick_timer
  125. }
  126. k2.clear({76, 53, 83, 255})
  127. k2.set_shader(shader)
  128. camera := k2.Camera {
  129. zoom = f32(WINDOW_SIZE) / CANVAS_SIZE,
  130. }
  131. k2.set_vertex_input_override(shader, 3, k2.create_vertex_input_override(k2.Color{255, 255, 255, 128}))
  132. k2.set_camera(camera)
  133. food_sprite.width = CELL_SIZE
  134. food_sprite.height = CELL_SIZE
  135. k2.draw_texture(food_sprite, {f32(food_pos.x), f32(food_pos.y)}*CELL_SIZE)
  136. for i in 0..<snake_length {
  137. part_sprite := body_sprite
  138. dir: Vec2i
  139. if i == 0 {
  140. part_sprite = head_sprite
  141. dir = snake[i] - snake[i + 1]
  142. } else if i == snake_length - 1 {
  143. part_sprite = tail_sprite
  144. dir = snake[i - 1] - snake[i]
  145. } else {
  146. dir = snake[i - 1] - snake[i]
  147. }
  148. rot := math.atan2(f32(dir.y), f32(dir.x)) * math.DEG_PER_RAD
  149. source := k2.Rect {
  150. 0, 0,
  151. f32(part_sprite.width), f32(part_sprite.height),
  152. }
  153. dest := k2.Rect {
  154. f32(snake[i].x)*CELL_SIZE + 0.5*CELL_SIZE,
  155. f32(snake[i].y)*CELL_SIZE + 0.5*CELL_SIZE,
  156. CELL_SIZE,
  157. CELL_SIZE,
  158. }
  159. k2.draw_texture_ex(part_sprite, source, dest, {CELL_SIZE, CELL_SIZE}*0.5, rot)
  160. }
  161. if game_over {
  162. k2.draw_text("Game Over!", {4, 4}, 25, k2.RED)
  163. k2.draw_text("Press Enter to play again", {4, 30}, 15, k2.BLACK)
  164. }
  165. score := snake_length - 3
  166. score_str := fmt.tprintf("Score: %v", score)
  167. k2.draw_text(score_str, {4, CANVAS_SIZE - 14}, 10, k2.GRAY)
  168. k2.present()
  169. free_all(context.temp_allocator)
  170. }
  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. }