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