forked from edubart/nelua-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
snakesdl.nelua
284 lines (247 loc) · 7.04 KB
/
snakesdl.nelua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
##[[
if ccinfo.is_emscripten then
cflags '-s USE_SDL=2 -s ASYNCIFY=1'
end
linklib 'SDL2'
]]
require 'math'
-- SDL2 Snake Game Demo
-- import SDL structures
local SDL_Event <cimport> = @record{
type: uint32,
padding: [56]byte
}
local SDL_Keysym <cimport> = @record{
scancode: cint,
sym: int32,
mod: uint16,
unused: uint32
}
local SDL_KeyboardEvent <cimport> = @record{
type: uint32,
timestamp: uint32,
windowID: uint32,
state: uint8,
repeated: uint8,
padding: uint16,
keysym: SDL_Keysym
}
local SDL_Rect <cimport> = @record{
x: cint, y: cint,
w: cint, h: cint,
}
local SDL_Window <cimport> = @record{}
local SDL_Renderer <cimport> = @record{}
-- import SDL constants
local SDL_INIT_VIDEO <comptime> = 0x20
local SDL_WINDOWPOS_UNDEFINED <comptime> = 0x1fff0000
local SDL_WINDOW_OPENGL <comptime> = 0x2
local SDL_QUIT <comptime> = 0x100
local SDL_KEYDOWN <comptime> = 0x300
local SDLK_RIGHT <comptime> = 79 | 0x40000000
local SDLK_LEFT <comptime> = 80 | 0x40000000
local SDLK_DOWN <comptime> = 81 | 0x40000000
local SDLK_UP <comptime> = 82 | 0x40000000
local SDL_RENDERER_ACCELERATED <comptime> = 0x2
local SDL_RENDERER_PRESENTVSYNC <comptime> = 0x4
-- import SDL functions
local function SDL_Init(flags: uint32): int32 <cimport> end
local function SDL_CreateWindow(title: cstring, x: cint, y: cint, w: cint, h: cint, flags: uint32): *SDL_Window <cimport> end
local function SDL_Quit() <cimport> end
local function SDL_DestroyWindow(window: *SDL_Window) <cimport> end
local function SDL_PollEvent(event: *SDL_Event): int32 <cimport> end
local function SDL_CreateRenderer(window: *SDL_Window, index: cint, flags: uint32): *SDL_Renderer <cimport> end
local function SDL_DestroyRenderer(renderer: *SDL_Renderer) <cimport> end
local function SDL_RenderPresent(renderer: *SDL_Renderer) <cimport> end
local function SDL_RenderClear(renderer: *SDL_Renderer): cint <cimport> end
local function SDL_SetRenderDrawColor(renderer: *SDL_Renderer, r: uint8, g: uint8, b: uint8, a: uint8): cint <cimport> end
local function SDL_RenderFillRect(renderer: *SDL_Renderer, rect: *SDL_Rect): cint <cimport> end
local function SDL_GetTicks(): uint32 <cimport> end
-- game types
local Point2D = @record{x: integer, y: integer}
local Direction = @enum(byte){NONE=0, UP, DOWN, RIGHT, LEFT}
local Color = @record{r: byte, g: byte, b: byte}
-- game constants
local TILE_SIZE <comptime> = 64
local GRID_SIZE <comptime> = 12
local SCREEN_SIZE <comptime> = TILE_SIZE * GRID_SIZE
local MOVE_DELAY <comptime> = 128
local COLOR_RED <const> = Color{r=255, g=96, b=96}
local COLOR_GREEN <const> = Color{r=96, g=255, b=96}
local COLOR_BLACK <const> = Color{r=0, g=0, b=0}
-- game state variables
local renderer
local movedir
local quit = false
local nextmove
local score
local headpos, tailpos, applepos
local tiles: [GRID_SIZE][GRID_SIZE]Direction
local function move_point(pos: Point2D, dir: Direction)
switch dir
case Direction.UP then
pos.y = pos.y - 1
case Direction.DOWN then
pos.y = pos.y + 1
case Direction.RIGHT then
pos.x = pos.x + 1
case Direction.LEFT then
pos.x = pos.x - 1
end
return pos
end
local function set_tile(pos: Point2D, dir: Direction)
tiles[pos.x][pos.y] = dir
end
local function reset_tile(pos: Point2D)
tiles[pos.x][pos.y] = Direction.NONE
end
local function get_tile(pos: Point2D)
return tiles[pos.x][pos.y]
end
local function has_tile(pos: Point2D)
return tiles[pos.x][pos.y] ~= Direction.NONE
end
local function respawn_apple()
-- respawn until there is no collision with its body
repeat
applepos = Point2D{
x = math.random(GRID_SIZE) - 1,
y = math.random(GRID_SIZE) - 1
}
until not has_tile(applepos)
end
local function init_game()
tiles = {}
headpos = Point2D{x=GRID_SIZE//2, y=GRID_SIZE//2}
tailpos = Point2D{x=headpos.x, y=headpos.y+1}
movedir = Direction.UP
score = 0
nextmove = 0
set_tile(headpos, Direction.UP)
set_tile(tailpos, Direction.UP)
respawn_apple()
print 'NEW GAME'
end
local function game_over()
print 'GAME OVER.'
init_game()
end
local function poll_events()
local event: SDL_Event
while SDL_PollEvent(&event) ~= 0 do
switch event.type
case SDL_QUIT then
quit = true
case SDL_KEYDOWN then
local kevent = (@*SDL_KeyboardEvent)(&event)
local headdir = get_tile(headpos)
switch kevent.keysym.sym
case SDLK_UP then
if headdir ~= Direction.DOWN then
movedir = Direction.UP
end
case SDLK_DOWN then
if headdir ~= Direction.UP then
movedir = Direction.DOWN
end
case SDLK_RIGHT then
if headdir ~= Direction.LEFT then
movedir = Direction.RIGHT
end
case SDLK_LEFT then
if headdir ~= Direction.RIGHT then
movedir = Direction.LEFT
end
end
end
end
end
local function poll_game()
local now = SDL_GetTicks()
if now < nextmove then return end
nextmove = now + MOVE_DELAY
-- move the head
set_tile(headpos, movedir)
headpos = move_point(headpos, movedir)
-- check collision with map boundaries
if headpos.x >= GRID_SIZE or headpos.y >= GRID_SIZE or
headpos.x < 0 or headpos.y < 0 then
game_over()
return
end
-- check collisions with its body
if has_tile(headpos) then
game_over()
return
end
-- place head on next tile
set_tile(headpos, movedir)
-- check collision with apple
if headpos.x == applepos.x and headpos.y == applepos.y then
respawn_apple()
score = score + 1
print('SCORE', score)
else
-- eat tail
local taildir = get_tile(tailpos)
reset_tile(tailpos)
tailpos = move_point(tailpos, taildir)
end
end
local function draw_background(color: Color)
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255)
SDL_RenderClear(renderer)
end
local function draw_tile(pos: Point2D, color: Color)
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 255)
local rect = SDL_Rect{
x = pos.x * TILE_SIZE,
y = pos.y * TILE_SIZE,
w = TILE_SIZE,
h = TILE_SIZE
}
SDL_RenderFillRect(renderer, &rect)
end
local function draw_apple()
draw_tile(applepos, COLOR_RED)
end
local function draw_snake()
for x=0,GRID_SIZE-1 do
for y=0,GRID_SIZE-1 do
local pos = Point2D{x=x,y=y}
if has_tile(pos) then -- snake is present at this tile
draw_tile(pos, COLOR_GREEN)
end
end
end
end
local function draw()
draw_background(COLOR_BLACK)
draw_apple()
draw_snake()
end
local function go()
-- init sdl
SDL_Init(SDL_INIT_VIDEO)
local window = SDL_CreateWindow("An SDL2 Window",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SCREEN_SIZE, SCREEN_SIZE, SDL_WINDOW_OPENGL)
assert(window, "Could not create window")
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)
assert(renderer, "Could not create renderer")
init_game()
-- draw loop
repeat
poll_events()
poll_game()
draw()
-- swap buffers
SDL_RenderPresent(renderer)
until quit
-- cleanup and finish
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(window)
SDL_Quit()
end
go()