-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.lua
326 lines (277 loc) · 9.55 KB
/
game.lua
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
local addon = LibStub('AceAddon-3.0'):GetAddon('2048')
local game = addon:NewModule('game')
local grid_size = 4 -- actuel gris size
local start_tiles = 2 -- # of tiles on game startup
------------------------------------------------
-- Initialize the grid
------------------------------------------------
function game:OnInitialize()
self.size = grid_size
self.grid = {}
-- Initialize out of bounds cells to -1
for row = 0, self.size + 1 do
self.grid[row] = {}
for col = 0, self.size + 1 do
self.grid[row][col] = {
row = row,
col = col,
val = (row < 1 or row > self.size or col < 1 or col > self.size) and -1 or 0
}
end
end
end
------------------------------------------------
-- Restore a saved game -- used at addon startup
------------------------------------------------
function game:restore_game(state)
if #state.grid > 0 then
-- We have some data to start with
self.best = state.best
self.score = state.score
self.moves = state.moves
self.over = state.over
self.won = state.won
self.cont = state.cont
for row = 1, self.size do
for col = 1, self.size do
self.grid[row][col].val = state.grid[row][col]
end
end
else
-- Just start a new game
self:new_game()
end
end
------------------------------------------------
-- Start a brand new game
------------------------------------------------
function game:new_game()
self.best = self.best or 0
self.score = 0
self.moves = 0
self.over = false
self.won = false
self.cont = false
for row = 1, self.size do
for col = 1, self.size do
self.grid[row][col].val = 0
end
end
-- Add a few cells to start with
self:add_random_cells(start_tiles)
end
------------------------------------------------
-- Save current game state
------------------------------------------------
function game:save_state(state)
-- Copy state
state.best = self.best
state.score = self.score
state.moves = self.moves
state.over = self.over
state.won = self.won
state.cont = self.cont
state.grid = wipe(state.grid or {})
for row = 1, self.size do
state.grid[row] = {}
for col = 1, self.size do
state.grid[row][col] = self.grid[row][col].val
end
end
end
------------------------------------------------
-- Get/set the cell at (row,col)
------------------------------------------------
function game:get_cell(row, col)
return self.grid[row][col]
end
------------------------------------------------
function game:set_cell_value(row, col, val)
self.grid[row][col].val = val
end
-----------------------------------------------
-- Check if further moves are possible
-----------------------------------------------
function game:moves_available()
for row = 1, self.size do
for col = 1, self.size do
-- Only check right and down as actual left and up were checked on previous iteration
local cell = self.grid[row][col].val
local right = self.grid[row][col + 1].val
local below = self.grid[row + 1][col].val
if right == 0 or right == cell or below == 0 or below == cell then
return true
end
end
end
return false
end
------------------------------------------------
-- Set a random cell to a random value
------------------------------------------------
local _empty = {}
function game:add_random_cells(num_cells)
-- Collect and count empty cells
wipe(_empty)
for row = 1, self.size do
for col = 1, self.size do
if self.grid[row][col].val == 0 then
table.insert(_empty, { row = row, col = col } )
end
end
end
num_cells = num_cells or 1
if #_empty >= num_cells then
for i = 1, num_cells do
local random_cell = math.ceil(math.random() * #_empty)
local random_value = (math.random() < 0.7) and 2 or 4
self:set_cell_value(_empty[random_cell].row, _empty[random_cell].col, random_value)
table.remove(_empty, random_cell)
end
return true
end
return false
end
------------------------------------------------
-- Get/set the score
------------------------------------------------
function game:add_score(n)
self.score = self.score + n
if self.score > self.best then
self.best = self.score
end
end
------------------------------------------------
function game:add_move()
self.moves = self.moves + 1
end
------------------------------------------------
function game:get_scores()
return self.moves, self.score, self.best
end
------------------------------------------------
-- Check whether the game is terminated
------------------------------------------------
function game:is_terminated()
return self:is_won() or self:is_over()
end
------------------------------------------------
function game:is_won()
return self.won and not self.cont
end
------------------------------------------------
function game:is_over()
return self.over
end
------------------------------------------------
function game:keep_playing()
self.cont = true
end
-----------------------------------------------
-- Move cells on the grid in the specified direction
-----------------------------------------------
local _moves = {}
function game:move_cells(direction)
-- Find the farthest destination for a cell in the given direction
local function find_dest(cell, row_delta, col_delta)
local dest = self.grid[cell.row + row_delta][cell.col + col_delta]
while dest.val == 0 do
-- Destination is empty, try further
dest = self.grid[dest.row + row_delta][dest.col + col_delta]
end
if (dest.val == cell.val) and not (cell.merged or dest.merged) then
-- We can merge with this cell but only if neither cell and dest were already merged
return dest
end
-- We either reached out of bounds or we are blocked by another cell
-- In any case, return the last valid cell we found
return self.grid[dest.row - row_delta][dest.col - col_delta]
end
-- Move a cell to its destination
local function move_cell(cell, dest)
if dest.row ~= cell.row or dest.col ~= cell.col then
local merged = dest.val == cell.val
table.insert(_moves, {
p_row = cell.row,
p_col = cell.col,
p_val = cell.val,
n_row = dest.row,
n_col = dest.col,
n_val = dest.val + cell.val,
merged = merged
})
dest.val = dest.val + cell.val -- dest.val is either 0 or same as cell.val
dest.merged = merged
cell.val = 0
if merged then
self:add_score(dest.val)
-- The mighty 2048 tile?
self.won = self.won or (dest.val == 2048)
end
end
end
-- Move thes cells
wipe(_moves)
if not self:is_terminated() then
self:add_move()
-- Traverse the grid in the right direction
local source
if direction == 'UP' then
for row = 2, self.size do -- Loop from top to bottom, skipping row #1 since it can't move up
for col = 1, self.size do
source = self.grid[row][col]
if source.val > 0 then
move_cell(source, find_dest(source, -1, 0))
end
end
end
elseif direction == 'DOWN' then
for row = self.size-1, 1, -1 do -- Loop from bottom to up, skipping last row since it can't move down
for col = 1, self.size do
source = self.grid[row][col]
if source.val > 0 then
move_cell(source, find_dest(source, 1, 0))
end
end
end
elseif direction == 'LEFT' then
for row = 1, self.size do
for col = 2, self.size do -- Loop from left to right, skipping col #1 since it can't move left
source = self.grid[row][col]
if source.val > 0 then
move_cell(source, find_dest(source, 0, -1))
end
end
end
elseif direction == 'RIGHT' then
for row = 1, self.size do
for col = self.size-1, 1, -1 do -- Loop from right to left, skipping last col since it can't move right
source = self.grid[row][col]
if source.val > 0 then
move_cell(source, find_dest(source, 0, 1))
end
end
end
end
end
return _moves
end
-----------------------------------------------
function game:next_turn()
-- Reset all cells states
for row = 1, self.size do
for col = 1, self.size do
local cell = self.grid[row][col]
cell.row = row
cell.col = col
cell.merged = false
end
end
-- Update the values of those cells that actually moved
for _, move in ipairs(_moves) do
local cell = self.grid[move.n_row][move.n_col]
cell.val = move.n_val
end
-- Add a new cell, check for game over
self.over = not (self:add_random_cells(1) and self:moves_available())
end