| 1 | import gg |
| 2 | // import sokol.sapp |
| 3 | import time |
| 4 | import rand |
| 5 | |
| 6 | // constants |
| 7 | const top_height = 100 |
| 8 | const canvas_size = 700 |
| 9 | const game_size = 17 |
| 10 | const tile_size = canvas_size / game_size |
| 11 | const tick_rate_ms = 100 |
| 12 | |
| 13 | // types |
| 14 | struct Pos { |
| 15 | x int |
| 16 | y int |
| 17 | } |
| 18 | |
| 19 | fn (a Pos) + (b Pos) Pos { |
| 20 | return Pos{a.x + b.x, a.y + b.y} |
| 21 | } |
| 22 | |
| 23 | fn (a Pos) - (b Pos) Pos { |
| 24 | return Pos{a.x - b.x, a.y - b.y} |
| 25 | } |
| 26 | |
| 27 | enum Direction { |
| 28 | up |
| 29 | down |
| 30 | left |
| 31 | right |
| 32 | } |
| 33 | |
| 34 | struct App { |
| 35 | mut: |
| 36 | gg &gg.Context = unsafe { nil } |
| 37 | score int |
| 38 | snake []Pos |
| 39 | dir Direction |
| 40 | last_dir Direction |
| 41 | food Pos |
| 42 | start_time i64 |
| 43 | last_tick i64 |
| 44 | } |
| 45 | |
| 46 | // utility |
| 47 | fn (mut app App) reset_game() { |
| 48 | app.score = 0 |
| 49 | app.snake = [ |
| 50 | Pos{3, 8}, |
| 51 | Pos{2, 8}, |
| 52 | Pos{1, 8}, |
| 53 | Pos{0, 8}, |
| 54 | ] |
| 55 | app.dir = .right |
| 56 | app.last_dir = app.dir |
| 57 | app.food = Pos{10, 8} |
| 58 | app.start_time = time.ticks() |
| 59 | app.last_tick = time.ticks() |
| 60 | } |
| 61 | |
| 62 | fn (mut app App) move_food() { |
| 63 | for { |
| 64 | x := rand.intn(game_size) or { 0 } |
| 65 | y := rand.intn(game_size) or { 0 } |
| 66 | app.food = Pos{x, y} |
| 67 | |
| 68 | if app.food !in app.snake { |
| 69 | return |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // events |
| 75 | fn on_keydown(key gg.KeyCode, _ gg.Modifier, mut app App) { |
| 76 | match key { |
| 77 | .w, .up { |
| 78 | if app.last_dir != .down { |
| 79 | app.dir = .up |
| 80 | } |
| 81 | } |
| 82 | .s, .down { |
| 83 | if app.last_dir != .up { |
| 84 | app.dir = .down |
| 85 | } |
| 86 | } |
| 87 | .a, .left { |
| 88 | if app.last_dir != .right { |
| 89 | app.dir = .left |
| 90 | } |
| 91 | } |
| 92 | .d, .right { |
| 93 | if app.last_dir != .left { |
| 94 | app.dir = .right |
| 95 | } |
| 96 | } |
| 97 | else {} |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | fn on_frame(mut app App) { |
| 102 | app.gg.begin() |
| 103 | |
| 104 | now := time.ticks() |
| 105 | |
| 106 | if now - app.last_tick >= tick_rate_ms { |
| 107 | app.last_tick = now |
| 108 | |
| 109 | // finding delta direction |
| 110 | delta_dir := match app.dir { |
| 111 | .up { Pos{0, -1} } |
| 112 | .down { Pos{0, 1} } |
| 113 | .left { Pos{-1, 0} } |
| 114 | .right { Pos{1, 0} } |
| 115 | } |
| 116 | |
| 117 | // "snaking" along |
| 118 | mut prev := app.snake[0] |
| 119 | app.snake[0] = app.snake[0] + delta_dir |
| 120 | |
| 121 | for i in 1 .. app.snake.len { |
| 122 | tmp := app.snake[i] |
| 123 | app.snake[i] = prev |
| 124 | prev = tmp |
| 125 | } |
| 126 | |
| 127 | // adding last segment |
| 128 | if app.snake[0] == app.food { |
| 129 | app.move_food() |
| 130 | app.score++ |
| 131 | /* |
| 132 | if app.score > app.best { |
| 133 | app.best = app.score |
| 134 | app.best.save() |
| 135 | }*/ |
| 136 | app.snake << app.snake.last() + app.snake.last() - app.snake[app.snake.len - 2] |
| 137 | } |
| 138 | |
| 139 | app.last_dir = app.dir |
| 140 | } |
| 141 | // drawing snake |
| 142 | for pos in app.snake { |
| 143 | app.gg.draw_rect(tile_size * pos.x, tile_size * pos.y + top_height, tile_size, tile_size, |
| 144 | gg.blue) |
| 145 | } |
| 146 | |
| 147 | // drawing food |
| 148 | app.gg.draw_rect(tile_size * app.food.x, tile_size * app.food.y + top_height, tile_size, |
| 149 | tile_size, gg.red) |
| 150 | |
| 151 | // drawing top |
| 152 | app.gg.draw_rect(0, 0, canvas_size, top_height, gg.black) |
| 153 | app.gg.draw_text(350, top_height / 2, 'Score: ${app.score}', gg.TextCfg{ |
| 154 | color: gg.white |
| 155 | align: .center |
| 156 | vertical_align: .middle |
| 157 | size: 80 |
| 158 | }) |
| 159 | |
| 160 | // checking if snake bit itself |
| 161 | if app.snake[0] in app.snake[1..] { |
| 162 | app.reset_game() |
| 163 | } |
| 164 | // checking if snake hit a wall |
| 165 | if app.snake[0].x < 0 || app.snake[0].x >= game_size || app.snake[0].y < 0 |
| 166 | || app.snake[0].y >= game_size { |
| 167 | app.reset_game() |
| 168 | } |
| 169 | |
| 170 | app.gg.end() |
| 171 | } |
| 172 | |
| 173 | // setup |
| 174 | fn main() { |
| 175 | mut app := App{ |
| 176 | gg: &gg.Context{} |
| 177 | } |
| 178 | app.reset_game() |
| 179 | |
| 180 | app.gg = gg.new_context( |
| 181 | bg_color: gg.white |
| 182 | frame_fn: on_frame |
| 183 | keydown_fn: on_keydown |
| 184 | user_data: &app |
| 185 | width: canvas_size |
| 186 | height: top_height + canvas_size |
| 187 | create_window: true |
| 188 | resizable: false |
| 189 | window_title: 'snek' |
| 190 | html5_canvas_name: 'canvas' |
| 191 | ) |
| 192 | |
| 193 | app.gg.run() |
| 194 | } |
| 195 | |