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
|
-- Defines
local RESOLUTION = { 128, 128 }
local TILE_SIZE = 8
local LEVEL_SIZE = RESOLUTION[1] / TILE_SIZE
local STATE = { TITLE = 0, GAME = 1, OVER = 2 } -- Enum wannabe.
-- Resources
local framebuffer = nil
local monitor = 0
local monitorPos = RL.GetMonitorPosition( monitor )
local monitorSize = RL.GetMonitorSize( monitor )
local winScale = 6
local winSize = { RESOLUTION[1] * winScale, RESOLUTION[2] * winScale }
local gameState = STATE.GAME
local grassTexture = -1
local snakeTexture = -1
local appleTexture = -1
local gameSpeed = 7.0
local moveTimer = 1.0
local snake = {}
local applePos = {}
local function setSnake()
snake = {
heading = { 1, 0 },
control = { 1, 0 },
headPos = { LEVEL_SIZE / 2, LEVEL_SIZE / 2 },
segments = {},
grow = 2,
}
end
local function vector2IsEqual( v1, v2 )
return v1[1] == v2[1] and v1[2] == v2[2]
end
local function addSegment()
-- If first segment, grow from head and otherwise from tail. New segments are inserted firts.
if #snake.segments == 0 then
table.insert( snake.segments, 1, { pos = snake.headPos, heading = snake.heading } )
else
table.insert( snake.segments, 1, { pos = snake.segments[ #snake.segments ].pos,
heading = snake.segments[ #snake.segments ].heading } )
end
end
local function setApplePos()
applePos = { math.random( 0, LEVEL_SIZE - 1 ), math.random( 0, LEVEL_SIZE - 1 ) }
local search = true
while search do
search = false
applePos = { math.random( 0, LEVEL_SIZE - 1 ), math.random( 0, LEVEL_SIZE - 1 ) }
for _, seg in ipairs( snake.segments ) do
search = vector2IsEqual( applePos, seg.pos )
if search then
break
end
end
end
end
-- Init.
function RL.init()
RL.SetWindowState( RL.FLAG_WINDOW_RESIZABLE )
RL.SetWindowState( RL.FLAG_VSYNC_HINT )
RL.SetWindowSize( winSize )
RL.SetWindowPosition( { monitorPos[1] + monitorSize[1] / 2 - winSize[1] / 2, monitorPos[2] + monitorSize[2] / 2 - winSize[2] / 2 } )
RL.SetWindowTitle( "Snake" )
RL.SetWindowIcon( RL.LoadImage( RL.GetBasePath().."../resources/images/apple.png" ) )
framebuffer = RL.LoadRenderTexture( RESOLUTION )
grassTexture = RL.LoadTexture( RL.GetBasePath().."../resources/images/grass.png" )
snakeTexture = RL.LoadTexture( RL.GetBasePath().."../resources/images/snake.png" )
appleTexture = RL.LoadTexture( RL.GetBasePath().."../resources/images/apple.png" )
setSnake()
setApplePos()
end
-- Process.
local function moveSnake()
-- Check if snake has eaten and should grow.
if 0 < snake.grow then
addSegment()
snake.grow = snake.grow - 1
end
-- Move body.
for i, seg in ipairs( snake.segments ) do
if i < #snake.segments then
seg.pos = snake.segments[ i+1 ].pos
seg.heading = snake.segments[ i+1 ].heading
else
seg.pos = snake.headPos
seg.heading = snake.heading
end
end
-- Move head.
snake.heading = { snake.control[1], snake.control[2] }
snake.headPos = { snake.headPos[1] + snake.heading[1], snake.headPos[2] + snake.heading[2] }
-- Check appple eating.
if vector2IsEqual( snake.headPos, applePos ) then
snake.grow = snake.grow + 1
setApplePos()
end
-- Check if hit to body.
for _, seg in ipairs( snake.segments ) do
if vector2IsEqual( snake.headPos, seg.pos ) then
gameState = STATE.OVER
end
end
-- Check if outside or level.
if snake.headPos[1] < 0 or LEVEL_SIZE <= snake.headPos[1] or snake.headPos[2] < 0 or LEVEL_SIZE <= snake.headPos[2] then
gameState = STATE.OVER
end
moveTimer = moveTimer + 1.0
end
function RL.process( delta )
if gameState == STATE.GAME then -- Run game.
-- Controls.
if RL.IsKeyPressed( RL.KEY_RIGHT ) and 0 <= snake.heading[1] then
snake.control = { 1, 0 }
elseif RL.IsKeyPressed( RL.KEY_LEFT ) and snake.heading[1] <= 0 then
snake.control = { -1, 0 }
elseif RL.IsKeyPressed( RL.KEY_DOWN ) and 0 <= snake.heading[2] then
snake.control = { 0, 1 }
elseif RL.IsKeyPressed( RL.KEY_UP ) and snake.heading[2] <= 0 then
snake.control = { 0, -1 }
end
moveTimer = moveTimer - gameSpeed * delta
if moveTimer <= 0.0 then
moveSnake()
end
elseif gameState == STATE.OVER and RL.IsKeyPressed( RL.KEY_ENTER ) then -- Reset game.
setSnake()
setApplePos()
gameState = STATE.GAME
end
end
-- Drawing.
local function drawGrass()
for y = 0, LEVEL_SIZE - 1 do
for x = 0, LEVEL_SIZE - 1 do
RL.DrawTexture( grassTexture, { x * TILE_SIZE, y * TILE_SIZE }, RL.WHITE )
end
end
end
--[[ Check if next segment is on left side. There are more mathematically elegant solution to this, but there is
only four possibilities so we can just check them all. ]]--
local function onLeft( this, nextSeg )
return ( vector2IsEqual( this, { 0, -1 } ) and vector2IsEqual( nextSeg, { -1, 0 } ) )
or ( vector2IsEqual( this, { -1, 0 } ) and vector2IsEqual( nextSeg, { 0, 1 } ) )
or ( vector2IsEqual( this, { 0, 1 } ) and vector2IsEqual( nextSeg, { 1, 0 } ) )
or ( vector2IsEqual( this, { 1, 0 } ) and vector2IsEqual( nextSeg, { 0, -1 } ) )
end
local function drawSnake()
for i, seg in ipairs( snake.segments ) do
local angle = math.deg( RL.Vector2Angle( { 0, 0 }, seg.heading ) )
local source = { 16, 0, 8, 8 }
if i == 1 then -- Tail segment. Yes tail is actually the 'first' segment.
source[1] = 8
if 1 < #snake.segments then
angle = math.deg( RL.Vector2Angle( { 0, 0 }, snake.segments[ 2 ].heading ) )
end
elseif i < #snake.segments and not vector2IsEqual( seg.heading, snake.segments[ i+1 ].heading ) then -- Turned middle segments.
source[1] = 0
-- Mirror turned segment to other way.
if onLeft( seg.heading, snake.segments[ i+1 ].heading ) then
source[4] = -8
end
elseif i == #snake.segments and not vector2IsEqual( seg.heading, snake.heading ) then -- Turned segment before head.
source[1] = 0
if onLeft( seg.heading, snake.heading ) then
source[4] = -8
end
end
-- Notice that we set the origin to center { 4, 4 } that acts as pivot point. We also have to adjust our dest position by 4.
RL.DrawTexturePro( snakeTexture, source, { seg.pos[1] * TILE_SIZE + 4, seg.pos[2] * TILE_SIZE + 4, 8, 8 }, { 4, 4 }, angle, RL.WHITE )
end
-- Let's draw the head last to keep it on top.
local angle = math.deg( RL.Vector2Angle( { 0, 0 }, snake.heading ) )
RL.DrawTexturePro( snakeTexture, { 24, 0, 8, 8 }, { snake.headPos[1] * TILE_SIZE + 4, snake.headPos[2] * TILE_SIZE + 4, 8, 8 }, { 4, 4 }, angle, RL.WHITE )
end
local function drawApple()
RL.DrawTexture( appleTexture, { applePos[1] * TILE_SIZE, applePos[2] * TILE_SIZE }, RL.WHITE )
end
function RL.draw()
-- Clear the window to black.
RL.ClearBackground( RL.BLACK )
-- Draw to framebuffer.
RL.BeginTextureMode( framebuffer )
RL.ClearBackground( RL.BLACK )
drawGrass()
drawSnake()
drawApple()
if gameState == STATE.OVER then
RL.DrawText( RL.defaultFont, "Press Enter to\nrestart", { 10, 10 }, 10, 2, RL.WHITE )
end
RL.EndTextureMode()
-- Draw framebuffer to window.
RL.DrawTexturePro( RL.GetRenderTextureTexture( framebuffer ), { 0, 0, RESOLUTION[1], -RESOLUTION[2] }, { 0, 0, winSize[1], winSize[2] }, { 0, 0 }, 0.0, RL.WHITE )
end
|