1 | // |
---|
2 | // Monsterz |
---|
3 | // |
---|
4 | // Copyright: (c) 2005-2011 Sam Hocevar <sam@hocevar.net> |
---|
5 | // This program is free software; you can redistribute it and/or |
---|
6 | // modify it under the terms of the Do What The Fuck You Want To |
---|
7 | // Public License, Version 2, as published by Sam Hocevar. See |
---|
8 | // http://sam.zoy.org/projects/COPYING.WTFPL for more details. |
---|
9 | // |
---|
10 | |
---|
11 | #if defined HAVE_CONFIG_H |
---|
12 | # include "config.h" |
---|
13 | #endif |
---|
14 | |
---|
15 | #include <cmath> |
---|
16 | #include <cstdlib> |
---|
17 | #include <ctime> |
---|
18 | #include <cstring> |
---|
19 | |
---|
20 | #include "core.h" |
---|
21 | |
---|
22 | using namespace lol; |
---|
23 | |
---|
24 | #include "board.h" |
---|
25 | #include "piece.h" |
---|
26 | #include "score.h" |
---|
27 | #include "thumbs.h" |
---|
28 | #include "mash.h" |
---|
29 | #include "monsterz.h" |
---|
30 | |
---|
31 | /* |
---|
32 | * Board implementation class |
---|
33 | */ |
---|
34 | |
---|
35 | class BoardData |
---|
36 | { |
---|
37 | friend class Board; |
---|
38 | |
---|
39 | private: |
---|
40 | Board::game_t game; |
---|
41 | |
---|
42 | vec2i dim, size; |
---|
43 | int minnpieces, npieces, maxnpieces; |
---|
44 | |
---|
45 | int screen, board, tiles, icons; |
---|
46 | int click, whip; |
---|
47 | |
---|
48 | struct Pair |
---|
49 | { |
---|
50 | int id; |
---|
51 | Piece *piece; |
---|
52 | } |
---|
53 | pairs[MAX_WIDTH][MAX_HEIGHT]; |
---|
54 | |
---|
55 | /* Hunt */ |
---|
56 | struct Pair grabbed; |
---|
57 | int nmoves; |
---|
58 | vec2i src_cell, dst_cell; |
---|
59 | |
---|
60 | /* Fusion */ |
---|
61 | struct Pair current[2]; |
---|
62 | int next[2], rotation; |
---|
63 | |
---|
64 | Score *score; |
---|
65 | |
---|
66 | Mash *mashes; |
---|
67 | Emitter *emitter; |
---|
68 | Thumbs *thumbs; |
---|
69 | |
---|
70 | vec2i oldmouse; |
---|
71 | vec3i oldbuttons; |
---|
72 | float whipdelay; |
---|
73 | |
---|
74 | enum |
---|
75 | { |
---|
76 | HUNT_IDLE, |
---|
77 | HUNT_BADCLICK, |
---|
78 | HUNT_GRAB, |
---|
79 | FUSION_IDLE, |
---|
80 | } |
---|
81 | state; |
---|
82 | }; |
---|
83 | |
---|
84 | /* |
---|
85 | * Public Board class |
---|
86 | */ |
---|
87 | |
---|
88 | Board::Board(game_t game, vec2i dim, int minnpieces, int maxnpieces) |
---|
89 | : data(new BoardData()) |
---|
90 | { |
---|
91 | data->game = game; |
---|
92 | |
---|
93 | switch (data->game) |
---|
94 | { |
---|
95 | case GAME_HUNT: |
---|
96 | data->size = 48; |
---|
97 | break; |
---|
98 | case GAME_FUSION: |
---|
99 | data->size = 40; |
---|
100 | break; |
---|
101 | } |
---|
102 | |
---|
103 | data->dim = dim; |
---|
104 | data->minnpieces = minnpieces; |
---|
105 | data->npieces = minnpieces; |
---|
106 | data->maxnpieces = maxnpieces; |
---|
107 | data->screen = Tiler::Register(PNG_BACKGROUND, vec2i(640, 480), 0, 1.0f); |
---|
108 | data->board = Tiler::Register(PNG_BOARD, 384, 0, 1.0f); |
---|
109 | data->tiles = Tiler::Register(PNG_TILES, data->size, 0, 1.0f); |
---|
110 | data->icons = Tiler::Register(PNG_ICONS, 24, 0, 1.0f); |
---|
111 | data->click = Sampler::Register(WAV_CLICK); |
---|
112 | data->whip = Sampler::Register(WAV_WHIP); |
---|
113 | |
---|
114 | data->emitter = new Emitter(data->tiles, vec3(0, -0.0006f, 0)); |
---|
115 | Ticker::Ref(data->emitter); |
---|
116 | |
---|
117 | data->thumbs = new Thumbs(0); |
---|
118 | Ticker::Ref(data->thumbs); |
---|
119 | |
---|
120 | switch (data->game) |
---|
121 | { |
---|
122 | case GAME_HUNT: |
---|
123 | Fill(); |
---|
124 | data->thumbs->SetMax(data->npieces); |
---|
125 | data->state = BoardData::HUNT_IDLE; |
---|
126 | break; |
---|
127 | |
---|
128 | case GAME_FUSION: |
---|
129 | for (int j = 0; j < data->dim.j; j++) |
---|
130 | for (int i = 0; i < data->dim.i; i++) |
---|
131 | data->pairs[i][j].id = 0; |
---|
132 | |
---|
133 | data->current[0].id = GetRandomId(); |
---|
134 | data->current[1].id = GetRandomId(); |
---|
135 | data->current[0].piece = new Piece(Piece::PIECE_FUSION, |
---|
136 | data->emitter, vec2i(3, 8), |
---|
137 | data->current[0].id); |
---|
138 | data->current[1].piece = new Piece(Piece::PIECE_FUSION, |
---|
139 | data->emitter, vec2i(4, 8), |
---|
140 | data->current[1].id); |
---|
141 | Ticker::Ref(data->current[0].piece); |
---|
142 | Ticker::Ref(data->current[1].piece); |
---|
143 | data->current[0].piece->SetPos(vec2i(3, 7) * data->size); |
---|
144 | data->current[1].piece->SetPos(vec2i(4, 7) * data->size); |
---|
145 | |
---|
146 | data->next[0] = GetRandomId(); |
---|
147 | data->next[1] = GetRandomId(); |
---|
148 | data->rotation = 0; |
---|
149 | |
---|
150 | data->thumbs->SetMax(data->npieces + 1); |
---|
151 | data->state = BoardData::FUSION_IDLE; |
---|
152 | break; |
---|
153 | } |
---|
154 | |
---|
155 | data->mashes = NULL; |
---|
156 | data->whipdelay = 0.0f; |
---|
157 | |
---|
158 | data->score = new Score(0); |
---|
159 | data->score->SetPos(vec3i(624, 432, 20)); |
---|
160 | Ticker::Ref(data->score); |
---|
161 | |
---|
162 | position = vec3i(24, 72, 1); |
---|
163 | bbox[0] = position; |
---|
164 | bbox[1] = bbox[0] + vec3i(384, 384, 0); |
---|
165 | |
---|
166 | Input::TrackMouse(this); |
---|
167 | } |
---|
168 | |
---|
169 | void Board::TickGame(float deltams) |
---|
170 | { |
---|
171 | Entity::TickGame(deltams); |
---|
172 | |
---|
173 | vec3i buttons = Input::GetMouseButtons(); |
---|
174 | |
---|
175 | /* Do not whip too often, the sound may become annoying */ |
---|
176 | data->whipdelay -= deltams; |
---|
177 | |
---|
178 | /* Get rid of finished mashes */ |
---|
179 | for (Mash **it = &data->mashes; *it; ) |
---|
180 | { |
---|
181 | if ((*it)->IsDead()) |
---|
182 | { |
---|
183 | Ticker::Unref(*it); |
---|
184 | *it = (*it)->nextmash; |
---|
185 | } |
---|
186 | else |
---|
187 | it = &(*it)->nextmash; |
---|
188 | } |
---|
189 | |
---|
190 | switch (data->state) |
---|
191 | { |
---|
192 | case BoardData::HUNT_IDLE: |
---|
193 | /* Should we start dragging something? */ |
---|
194 | if (buttons[0] && !data->oldbuttons[0] && mousepos.x != -1) |
---|
195 | { |
---|
196 | vec2i cell = mousepos / data->size; |
---|
197 | if (data->pairs[cell.x][cell.y].piece->Grab(vec2i(0, 0))) |
---|
198 | { |
---|
199 | Sampler::PlaySample(data->click); |
---|
200 | data->grabbed = data->pairs[cell.x][cell.y]; |
---|
201 | data->src_cell = mousepos / data->size; |
---|
202 | data->dst_cell = vec2i(-1); |
---|
203 | data->state = BoardData::HUNT_GRAB; |
---|
204 | } |
---|
205 | else |
---|
206 | data->state = BoardData::HUNT_BADCLICK; |
---|
207 | } |
---|
208 | break; |
---|
209 | |
---|
210 | case BoardData::HUNT_GRAB: |
---|
211 | if (mousepos.x != -1) |
---|
212 | { |
---|
213 | /* Mouse is still in the window, keep grabbing */ |
---|
214 | data->grabbed.piece->Grab(mousepos - data->oldmouse); |
---|
215 | vec2i cur_pos = data->grabbed.piece->GetPos() |
---|
216 | + data->grabbed.piece->GetOffset(); |
---|
217 | vec2i cur_cell = (cur_pos + data->size / 2) / data->size; |
---|
218 | if (cur_cell.i < 0 || cur_cell.i >= data->dim.i |
---|
219 | || cur_cell.j < 0 || cur_cell.j >= data->dim.j |
---|
220 | || (cur_pos - cur_cell * data->size).sqlen() > data->size.sqlen() / 8 |
---|
221 | || (cur_cell - data->src_cell).sqlen() != 1) |
---|
222 | cur_cell = vec2i(-1); |
---|
223 | /* If potential target changed, update our cache. */ |
---|
224 | if (cur_cell != data->dst_cell) |
---|
225 | { |
---|
226 | if (data->whipdelay < 0.0f) |
---|
227 | { |
---|
228 | Sampler::PlaySample(data->whip); |
---|
229 | data->whipdelay = DELAY_WHIP; |
---|
230 | } |
---|
231 | if (data->dst_cell != vec2i(-1)) |
---|
232 | data->pairs[data->dst_cell.i] |
---|
233 | [data->dst_cell.j].piece->Ungrab(data->dst_cell * data->size); |
---|
234 | if (cur_cell != vec2i(-1)) |
---|
235 | data->pairs[cur_cell.i] |
---|
236 | [cur_cell.j].piece->Ungrab(data->src_cell * data->size); |
---|
237 | data->dst_cell = cur_cell; |
---|
238 | } |
---|
239 | } |
---|
240 | |
---|
241 | /* Move initiated: if valid, perform immediately. */ |
---|
242 | if (data->dst_cell != vec2i(-1)) |
---|
243 | { |
---|
244 | vec2i cell_a = data->src_cell; |
---|
245 | vec2i cell_b = data->dst_cell; |
---|
246 | |
---|
247 | if (Switch(cell_a, cell_b)) |
---|
248 | data->state = BoardData::HUNT_IDLE; |
---|
249 | } |
---|
250 | |
---|
251 | /* Mouse released, or exited window, or dragged too far. */ |
---|
252 | if (!buttons[0] || mousepos.x == -1 |
---|
253 | || (data->src_cell * data->size - data->grabbed.piece->GetPos() |
---|
254 | - data->grabbed.piece->GetOffset()).sqlen() > 100 * 100) |
---|
255 | { |
---|
256 | vec2i cell_a = data->src_cell; |
---|
257 | BoardData::Pair a = data->pairs[cell_a.i][cell_a.j]; |
---|
258 | a.piece->Ungrab(cell_a * data->size); |
---|
259 | |
---|
260 | if (data->dst_cell != vec2i(-1)) |
---|
261 | { |
---|
262 | vec2i cell_b = data->dst_cell; |
---|
263 | BoardData::Pair b = data->pairs[cell_b.i][cell_b.j]; |
---|
264 | b.piece->Ungrab(cell_b * data->size); |
---|
265 | } |
---|
266 | |
---|
267 | data->state = BoardData::HUNT_IDLE; |
---|
268 | } |
---|
269 | break; |
---|
270 | |
---|
271 | case BoardData::HUNT_BADCLICK: |
---|
272 | if (!buttons[0]) |
---|
273 | data->state = BoardData::HUNT_IDLE; |
---|
274 | break; |
---|
275 | |
---|
276 | case BoardData::FUSION_IDLE: |
---|
277 | { |
---|
278 | int column = -1; |
---|
279 | |
---|
280 | if (clicked[2]) |
---|
281 | { |
---|
282 | column = data->current[0].piece->GetCell().x; |
---|
283 | data->rotation = (data->rotation + 1) % 2; |
---|
284 | if (column - data->rotation > data->dim.i - 1) |
---|
285 | column = data->dim.i - 1 - data->rotation; |
---|
286 | if (!data->rotation) |
---|
287 | { |
---|
288 | BoardData::Pair tmp = data->current[0]; |
---|
289 | data->current[0] = data->current[1]; |
---|
290 | data->current[1] = tmp; |
---|
291 | if (column == data->dim.i - 1) |
---|
292 | column = 6; |
---|
293 | } |
---|
294 | } |
---|
295 | |
---|
296 | if (mousepos.x != -1 && |
---|
297 | mousepos.x / data->size.x != data->current[0].piece->GetCell().x) |
---|
298 | { |
---|
299 | column = mousepos.x / data->size.x; |
---|
300 | column = column < 0 ? 0 : column > data->dim.i - 2 + data->rotation ? data->dim.i - 2 + data->rotation : column; |
---|
301 | } |
---|
302 | |
---|
303 | if (column != -1) |
---|
304 | { |
---|
305 | if (data->rotation) |
---|
306 | { |
---|
307 | data->current[0].piece->SetCell(vec2i(column, 6)); |
---|
308 | data->current[1].piece->SetCell(vec2i(column, 7)); |
---|
309 | } |
---|
310 | else |
---|
311 | { |
---|
312 | data->current[0].piece->SetCell(vec2i(column, 7)); |
---|
313 | data->current[1].piece->SetCell(vec2i(column + 1, 7)); |
---|
314 | } |
---|
315 | |
---|
316 | data->current[0].piece->Move(data->current[0].piece->GetCell() * data->size); |
---|
317 | data->current[1].piece->Move(data->current[1].piece->GetCell() * data->size); |
---|
318 | } |
---|
319 | |
---|
320 | if (clicked[0]) |
---|
321 | { |
---|
322 | for (int t = 0; t < 2; t++) |
---|
323 | { |
---|
324 | int i = data->current[t].piece->GetCell().i; |
---|
325 | for (int j = 0; j < 7; j++) |
---|
326 | if (data->pairs[i][j].id == 0) |
---|
327 | { |
---|
328 | data->current[t].piece->SetCell(vec2i(i, j)); |
---|
329 | data->current[t].piece->Move(vec2i(i, j) * data->size); |
---|
330 | data->pairs[i][j] = data->current[t]; |
---|
331 | data->thumbs->AddCount(data->current[t].id, 1); |
---|
332 | break; |
---|
333 | } |
---|
334 | } |
---|
335 | |
---|
336 | data->current[0].id = data->next[0]; |
---|
337 | data->current[1].id = data->next[1]; |
---|
338 | data->current[0].piece = new Piece(Piece::PIECE_FUSION, |
---|
339 | data->emitter, vec2i(3, 7), |
---|
340 | data->current[0].id); |
---|
341 | data->current[1].piece = new Piece(Piece::PIECE_FUSION, |
---|
342 | data->emitter, vec2i(4, 7), |
---|
343 | data->current[1].id); |
---|
344 | Ticker::Ref(data->current[0].piece); |
---|
345 | Ticker::Ref(data->current[1].piece); |
---|
346 | data->current[0].piece->SetPos(vec2i(3, 8) * data->size); |
---|
347 | data->current[1].piece->SetPos(vec2i(4, 8) * data->size); |
---|
348 | data->current[0].piece->Move(data->current[0].piece->GetCell() * data->size); |
---|
349 | data->current[1].piece->Move(data->current[1].piece->GetCell() * data->size); |
---|
350 | data->next[0] = GetRandomId(); |
---|
351 | data->next[1] = GetRandomId(); |
---|
352 | data->rotation = 0; |
---|
353 | |
---|
354 | Resolve(); |
---|
355 | } |
---|
356 | break; |
---|
357 | } |
---|
358 | } |
---|
359 | |
---|
360 | data->oldmouse = mousepos; |
---|
361 | data->oldbuttons = buttons; |
---|
362 | } |
---|
363 | |
---|
364 | void Board::TickDraw(float deltams) |
---|
365 | { |
---|
366 | Entity::TickDraw(deltams); |
---|
367 | |
---|
368 | Scene::GetDefault()->AddTile((data->board << 16) | 0, |
---|
369 | position.x, position.y, 1, 0); |
---|
370 | Scene::GetDefault()->AddTile((data->screen << 16) | 0, 0, 0, 10, 0); |
---|
371 | |
---|
372 | switch (data->game) |
---|
373 | { |
---|
374 | case GAME_HUNT: |
---|
375 | break; |
---|
376 | case GAME_FUSION: |
---|
377 | Scene::GetDefault()->AddTile((data->icons << 16) | (data->next[0] - 1), |
---|
378 | 350, 400, 11, 0); |
---|
379 | Scene::GetDefault()->AddTile((data->icons << 16) | (data->next[1] - 1), |
---|
380 | 380, 400, 11, 0); |
---|
381 | break; |
---|
382 | } |
---|
383 | } |
---|
384 | |
---|
385 | /* Fill the board with an initial position. We ensure no pieces are |
---|
386 | * aligned and at least one move is possible. */ |
---|
387 | void Board::Fill() |
---|
388 | { |
---|
389 | srand(rand() ^ time(NULL)); |
---|
390 | |
---|
391 | int list[MAX_WIDTH][MAX_HEIGHT]; |
---|
392 | do |
---|
393 | { |
---|
394 | for (int j = 0; j < data->dim.j; j++) |
---|
395 | for (int i = 0; i < data->dim.i; i++) |
---|
396 | data->pairs[i][j].id = 1 + rand() % data->npieces; |
---|
397 | } while (ListMashes(list) || !(data->nmoves = ListMoves(list))); |
---|
398 | |
---|
399 | /* Spawn pieces */ |
---|
400 | for (int j = 0; j < data->dim.j; j++) |
---|
401 | for (int i = 0; i < data->dim.i; i++) |
---|
402 | { |
---|
403 | vec2i newpos = vec2i(i, j + data->dim.j) * data->size; |
---|
404 | int id = data->pairs[i][j].id; |
---|
405 | data->pairs[i][j].piece = new Piece(Piece::PIECE_HUNT, |
---|
406 | data->emitter, vec2i(i, j), id); |
---|
407 | data->pairs[i][j].piece->SetPos(newpos); |
---|
408 | data->pairs[i][j].piece->Move(vec2i(i, j) * data->size); |
---|
409 | if (j) |
---|
410 | data->pairs[i][j].piece->SetBelow(data->pairs[i][j - 1].piece); |
---|
411 | Ticker::Ref(data->pairs[i][j].piece); |
---|
412 | } |
---|
413 | } |
---|
414 | |
---|
415 | bool Board::Switch(vec2i cell_a, vec2i cell_b) |
---|
416 | { |
---|
417 | BoardData::Pair a = data->pairs[cell_a.i][cell_a.j]; |
---|
418 | BoardData::Pair b = data->pairs[cell_b.i][cell_b.j]; |
---|
419 | data->pairs[cell_a.i][cell_a.j] = b; |
---|
420 | data->pairs[cell_b.i][cell_b.j] = a; |
---|
421 | |
---|
422 | /* Check whether this is a valid move by testing all patterns. |
---|
423 | * If the move is invalid, cancel the swap and bail out */ |
---|
424 | int list[MAX_WIDTH][MAX_HEIGHT]; |
---|
425 | |
---|
426 | if (!ListMashes(list)) |
---|
427 | { |
---|
428 | data->pairs[cell_a.i][cell_a.j] = a; |
---|
429 | data->pairs[cell_b.i][cell_b.j] = b; |
---|
430 | return false; |
---|
431 | } |
---|
432 | |
---|
433 | /* Perform the swap */ |
---|
434 | a.piece->SetCell(cell_b); |
---|
435 | a.piece->Ungrab(cell_b * data->size); |
---|
436 | b.piece->SetCell(cell_a); |
---|
437 | b.piece->Ungrab(cell_a * data->size); |
---|
438 | |
---|
439 | /* Swap above and below cells */ |
---|
440 | if (cell_a.i == cell_b.i) |
---|
441 | { |
---|
442 | Piece *tmpa = a.piece->GetAbove(); |
---|
443 | Piece *tmpb = b.piece->GetAbove(); |
---|
444 | if (tmpb == a.piece) |
---|
445 | { |
---|
446 | tmpb = b.piece->GetBelow(); |
---|
447 | b.piece->SetAbove(tmpa); |
---|
448 | b.piece->SetBelow(a.piece); |
---|
449 | a.piece->SetBelow(tmpb); |
---|
450 | } |
---|
451 | else /* tmpa == b.piece */ |
---|
452 | { |
---|
453 | tmpa = a.piece->GetBelow(); |
---|
454 | a.piece->SetAbove(tmpb); |
---|
455 | a.piece->SetBelow(b.piece); |
---|
456 | b.piece->SetBelow(tmpa); |
---|
457 | } |
---|
458 | } |
---|
459 | else |
---|
460 | { |
---|
461 | Piece *tmpa = a.piece->GetAbove(); |
---|
462 | Piece *tmpb = b.piece->GetAbove(); |
---|
463 | a.piece->SetAbove(tmpb); |
---|
464 | b.piece->SetAbove(tmpa); |
---|
465 | tmpa = a.piece->GetBelow(); |
---|
466 | tmpb = b.piece->GetBelow(); |
---|
467 | a.piece->SetBelow(tmpb); |
---|
468 | b.piece->SetBelow(tmpa); |
---|
469 | } |
---|
470 | |
---|
471 | int multiplier = 20; |
---|
472 | |
---|
473 | /* Remove matching pieces and store them in Mash objects */ |
---|
474 | do |
---|
475 | { |
---|
476 | Mash *mash = new Mash(data->emitter); |
---|
477 | Ticker::Ref(mash); |
---|
478 | |
---|
479 | for (int j = data->dim.j; j--;) for (int i = 0; i < data->dim.i; i++) |
---|
480 | { |
---|
481 | if (!list[i][j]) |
---|
482 | continue; |
---|
483 | |
---|
484 | /* The mash becomes the new owner of the disappearing piece */ |
---|
485 | mash->AddPiece(data->pairs[i][j].piece); |
---|
486 | data->thumbs->AddCount(data->pairs[i][j].id, 1); |
---|
487 | |
---|
488 | #if 0 // Test for piece creation |
---|
489 | if (list[i][j] >= 2) |
---|
490 | { |
---|
491 | Piece *old = data->pairs[i][j].piece; |
---|
492 | int id = 1 + rand() % data->npieces; |
---|
493 | data->pairs[i][j].id = id; |
---|
494 | data->pairs[i][j].piece = new Piece(Piece::PIECE_HUNT, data->emitter, vec2i(i, j), id); |
---|
495 | data->pairs[i][j].piece->SetBelow(old->GetBelow()); |
---|
496 | data->pairs[i][j].piece->SetAbove(old->GetAbove()); |
---|
497 | Ticker::Ref(data->pieces[i][j].piece); |
---|
498 | list[i][j] = 0; |
---|
499 | } |
---|
500 | else |
---|
501 | #endif |
---|
502 | { |
---|
503 | Piece *below = data->pairs[i][data->dim.j - 1].piece; |
---|
504 | |
---|
505 | /* Change coordinates for the whole column above */ |
---|
506 | for (int j2 = j + 1; j2 < data->dim.j; j2++) |
---|
507 | { |
---|
508 | data->pairs[i][j2 - 1] = data->pairs[i][j2]; |
---|
509 | data->pairs[i][j2 - 1].piece->SetCell(vec2i(i, j2 - 1)); |
---|
510 | data->pairs[i][j2 - 1].piece->Move(vec2i(i, j2 - 1) * data->size); |
---|
511 | list[i][j2 - 1] = list[i][j2]; |
---|
512 | } |
---|
513 | |
---|
514 | /* Spawn a new piece above all the others and attach it to |
---|
515 | * the board. */ |
---|
516 | vec2i newpos = vec2i(i * data->size.x, |
---|
517 | below->GetPos().y + data->size.y); |
---|
518 | vec2i newcell = vec2i(i, data->dim.j - 1); |
---|
519 | int id = 1 + rand() % data->npieces; |
---|
520 | Piece *tmp = new Piece(Piece::PIECE_HUNT, data->emitter, |
---|
521 | newcell, id); |
---|
522 | tmp->SetBelow(below); |
---|
523 | tmp->SetPos(newpos); |
---|
524 | tmp->Move(newcell * data->size); |
---|
525 | Ticker::Ref(tmp); |
---|
526 | data->pairs[i][data->dim.j - 1].id = id; |
---|
527 | data->pairs[i][data->dim.j - 1].piece = tmp; |
---|
528 | list[i][data->dim.j - 1] = 0; |
---|
529 | } |
---|
530 | } |
---|
531 | |
---|
532 | mash->nextmash = data->mashes; |
---|
533 | data->mashes = mash; |
---|
534 | |
---|
535 | data->score->Add(multiplier); |
---|
536 | multiplier *= 2; |
---|
537 | } |
---|
538 | while(ListMashes(list)); |
---|
539 | |
---|
540 | data->nmoves = ListMoves(list); |
---|
541 | |
---|
542 | if (data->nmoves == 0) |
---|
543 | { |
---|
544 | Mash *mash = new Mash(data->emitter); |
---|
545 | Ticker::Ref(mash); |
---|
546 | |
---|
547 | for (int j = data->dim.j; j--;) for (int i = 0; i < data->dim.i; i++) |
---|
548 | mash->AddPiece(data->pairs[i][j].piece); |
---|
549 | |
---|
550 | mash->nextmash = data->mashes; |
---|
551 | data->mashes = mash; |
---|
552 | |
---|
553 | Piece *below[MAX_WIDTH]; |
---|
554 | for (int i = 0; i < data->dim.i; i++) |
---|
555 | below[i] = data->pairs[i][data->dim.j - 1].piece; |
---|
556 | |
---|
557 | Fill(); |
---|
558 | |
---|
559 | for (int i = 0; i < data->dim.i; i++) |
---|
560 | data->pairs[i][0].piece->SetBelow(below[i]); |
---|
561 | } |
---|
562 | |
---|
563 | return true; |
---|
564 | } |
---|
565 | |
---|
566 | /* Fill an array with the list of pieces that should disappear due to |
---|
567 | * 3-piece or more alignments. */ |
---|
568 | int Board::ListMashes(int list[MAX_WIDTH][MAX_HEIGHT]) |
---|
569 | { |
---|
570 | int ret = 0; |
---|
571 | |
---|
572 | for (int j = 0; j < data->dim.j; j++) |
---|
573 | for (int i = 0; i < data->dim.i; i++) |
---|
574 | list[i][j] = 0; |
---|
575 | |
---|
576 | for (int j = 0; j < data->dim.j; j++) |
---|
577 | for (int i = 0; i < data->dim.i; i++) |
---|
578 | { |
---|
579 | int id = data->pairs[i][j].id; |
---|
580 | |
---|
581 | if (i + 2 < data->dim.i && data->pairs[i + 1][j].id == id |
---|
582 | && data->pairs[i + 2][j].id == id) |
---|
583 | { |
---|
584 | list[i][j]++; |
---|
585 | list[i + 1][j] += 2; |
---|
586 | list[i + 2][j]++; |
---|
587 | ret = 1; |
---|
588 | } |
---|
589 | |
---|
590 | if (j + 2 < data->dim.j && data->pairs[i][j + 1].id == id |
---|
591 | && data->pairs[i][j + 2].id == id) |
---|
592 | { |
---|
593 | list[i][j]++; |
---|
594 | list[i][j + 1] += 2; |
---|
595 | list[i][j + 2]++; |
---|
596 | ret = 1; |
---|
597 | } |
---|
598 | } |
---|
599 | |
---|
600 | return ret; |
---|
601 | } |
---|
602 | |
---|
603 | /* Fill an array with the list of pieces that can be moved. A value of 1 |
---|
604 | * indicates the piece can be moved right. A value of 2 means it can be |
---|
605 | * moved up, and a value of 3 means both moves are possible. The number |
---|
606 | * of possible moves is returned. */ |
---|
607 | int Board::ListMoves(int moves[MAX_WIDTH][MAX_HEIGHT]) |
---|
608 | { |
---|
609 | int ret = 0; |
---|
610 | |
---|
611 | for (int j = 0; j < data->dim.j; j++) |
---|
612 | for (int i = 0; i < data->dim.i; i++) |
---|
613 | moves[i][j] = 0; |
---|
614 | |
---|
615 | for (int j = 0; j < data->dim.j; j++) |
---|
616 | for (int i = 0; i < data->dim.i; i++) |
---|
617 | { |
---|
618 | /* Copy neighbourhood to a local buffer */ |
---|
619 | int tmp[6][6]; |
---|
620 | |
---|
621 | for (int dj = -2; dj <= 3; dj++) |
---|
622 | for (int di = -2; di <= 3; di++) |
---|
623 | if (j + dj >= 0 && j + dj < data->dim.j |
---|
624 | && i + di >= 0 && i + di < data->dim.i) |
---|
625 | tmp[2 + di][2 + dj] = data->pairs[i + di][j + dj].id; |
---|
626 | else |
---|
627 | tmp[2 + di][2 + dj] = 0; |
---|
628 | |
---|
629 | /* +--+--+--+--+--+--+ |
---|
630 | * | | |25| | | | |
---|
631 | * +--+--+--+--+--+--+ |
---|
632 | * | | |24|34| | | |
---|
633 | * +--+--+--+--+--+--+ |
---|
634 | * |03|13|c |33|43| | |
---|
635 | * +--+--+--+--+--+--+ |
---|
636 | * |02|12|a |b |42|52| |
---|
637 | * +--+--+--+--+--+--+ |
---|
638 | * | |11|21|31| | | |
---|
639 | * +--+--+--+--+--+--+ |
---|
640 | * | | |20|30| | | |
---|
641 | * +--+--+--+--+--+--+ */ |
---|
642 | int a = tmp[2][2]; |
---|
643 | int b = tmp[3][2] ? tmp[3][2] : -1; |
---|
644 | int c = tmp[2][3] ? tmp[2][3] : -1; |
---|
645 | |
---|
646 | /* Try moving right */ |
---|
647 | if ((a == tmp[3][0] && a == tmp[3][1]) || |
---|
648 | (a == tmp[3][1] && a == tmp[3][3]) || |
---|
649 | (a == tmp[3][3] && a == tmp[3][4]) || |
---|
650 | (a == tmp[4][2] && a == tmp[5][2]) || |
---|
651 | (b == tmp[2][0] && b == tmp[2][1]) || |
---|
652 | (b == tmp[2][1] && b == tmp[2][3]) || |
---|
653 | (b == tmp[2][3] && b == tmp[2][4]) || |
---|
654 | (b == tmp[0][2] && b == tmp[1][2])) |
---|
655 | { |
---|
656 | moves[i][j] |= 1; |
---|
657 | ret++; |
---|
658 | } |
---|
659 | |
---|
660 | /* Try moving up */ |
---|
661 | if ((a == tmp[0][3] && a == tmp[1][3]) || |
---|
662 | (a == tmp[1][3] && a == tmp[3][3]) || |
---|
663 | (a == tmp[3][3] && a == tmp[4][3]) || |
---|
664 | (a == tmp[2][4] && a == tmp[2][5]) || |
---|
665 | (c == tmp[0][2] && c == tmp[1][2]) || |
---|
666 | (c == tmp[1][2] && c == tmp[3][2]) || |
---|
667 | (c == tmp[3][2] && c == tmp[4][2]) || |
---|
668 | (c == tmp[2][0] && c == tmp[2][1])) |
---|
669 | { |
---|
670 | moves[i][j] |= 2; |
---|
671 | ret++; |
---|
672 | } |
---|
673 | } |
---|
674 | |
---|
675 | return ret; |
---|
676 | } |
---|
677 | |
---|
678 | int Board::GetRandomId() const |
---|
679 | { |
---|
680 | int max = data->npieces; |
---|
681 | |
---|
682 | if (max > data->minnpieces) |
---|
683 | max--; |
---|
684 | |
---|
685 | return 1 + rand() % max; |
---|
686 | } |
---|
687 | |
---|
688 | void Board::Resolve() |
---|
689 | { |
---|
690 | int list[MAX_PIECES][MAX_PIECES]; |
---|
691 | int count[MAX_PIECES * MAX_PIECES]; |
---|
692 | |
---|
693 | for (int j = 0; j < data->dim.j; j++) |
---|
694 | for (int i = 0; i < data->dim.i; i++) |
---|
695 | list[i][j] = -1; |
---|
696 | memset(count, 0, sizeof(count)); |
---|
697 | |
---|
698 | int seq = 0, effect = 0; |
---|
699 | |
---|
700 | /* Count connected tiles */ |
---|
701 | for (int j = 0; j < data->dim.j; j++) for (int i = 0; i < data->dim.i; i++) |
---|
702 | { |
---|
703 | if (!data->pairs[i][j].id) |
---|
704 | continue; |
---|
705 | |
---|
706 | if (data->pairs[i][j].id >= data->maxnpieces) |
---|
707 | continue; |
---|
708 | |
---|
709 | if (list[i][j] != -1) |
---|
710 | continue; |
---|
711 | |
---|
712 | list[i][j] = seq; |
---|
713 | count[seq] = TagNeighbours(list, i, j); |
---|
714 | if (count[seq] >= 3) |
---|
715 | effect = 1; |
---|
716 | seq++; |
---|
717 | } |
---|
718 | |
---|
719 | /* Only continue if there is an effect */ |
---|
720 | if (!effect) |
---|
721 | return; |
---|
722 | |
---|
723 | /* Add tiles to a mash; add mash to our list */ |
---|
724 | Mash *mash = new Mash(data->emitter); |
---|
725 | Ticker::Ref(mash); |
---|
726 | |
---|
727 | for (int j = 0; j < data->dim.j; j++) for (int i = 0; i < data->dim.i; i++) |
---|
728 | { |
---|
729 | if (list[i][j] == -1) |
---|
730 | continue; |
---|
731 | if (count[list[i][j]] < 3) |
---|
732 | continue; |
---|
733 | |
---|
734 | mash->AddPiece(data->pairs[i][j].piece); |
---|
735 | data->pairs[i][j].piece = NULL; |
---|
736 | } |
---|
737 | |
---|
738 | mash->nextmash = data->mashes; |
---|
739 | data->mashes = mash; |
---|
740 | |
---|
741 | /* Create new pieces where necessary */ |
---|
742 | for (int j = 0; j < data->dim.j; j++) for (int i = 0; i < data->dim.i; i++) |
---|
743 | { |
---|
744 | if (list[i][j] == -1) |
---|
745 | continue; |
---|
746 | if (count[list[i][j]] < 3) |
---|
747 | { |
---|
748 | if (!data->pairs[i][j].piece) |
---|
749 | data->pairs[i][j].id = 0; |
---|
750 | continue; |
---|
751 | } |
---|
752 | |
---|
753 | data->pairs[i][j].id++; |
---|
754 | if (data->pairs[i][j].id > data->npieces |
---|
755 | && data->pairs[i][j].id <= data->maxnpieces) |
---|
756 | { |
---|
757 | data->npieces++; |
---|
758 | data->thumbs->SetMax(data->npieces); |
---|
759 | } |
---|
760 | data->pairs[i][j].piece = new Piece(Piece::PIECE_FUSION, |
---|
761 | data->emitter, vec2i(i, j), |
---|
762 | data->pairs[i][j].id); |
---|
763 | Ticker::Ref(data->pairs[i][j].piece); |
---|
764 | data->pairs[i][j].piece->SetPos(vec2i(i, j) * data->size); |
---|
765 | data->thumbs->AddCount(data->pairs[i][j].id, 1); |
---|
766 | count[list[i][j]] = 0; |
---|
767 | list[i][j] = -1; |
---|
768 | } |
---|
769 | |
---|
770 | /* Move everything down */ |
---|
771 | for (int j = data->dim.j; j--;) for (int i = 0; i < data->dim.i; i++) |
---|
772 | { |
---|
773 | if (list[i][j] == -1 || data->pairs[i][j].piece) |
---|
774 | continue; |
---|
775 | |
---|
776 | for (int j2 = j + 1; j2 < data->dim.j; j2++) |
---|
777 | { |
---|
778 | data->pairs[i][j2 - 1] = data->pairs[i][j2]; |
---|
779 | if (data->pairs[i][j2 - 1].id) |
---|
780 | { |
---|
781 | data->pairs[i][j2 - 1].piece->SetCell(vec2i(i, j2 - 1)); |
---|
782 | data->pairs[i][j2 - 1].piece->Move(vec2i(i, j2 - 1) * data->size); |
---|
783 | } |
---|
784 | list[i][j2 - 1] = list[i][j2]; |
---|
785 | } |
---|
786 | |
---|
787 | data->pairs[i][data->dim.j - 1].id = 0; |
---|
788 | list[i][data->dim.j - 1] = -1; |
---|
789 | } |
---|
790 | |
---|
791 | /* Start again (FIXME: make this a while() loop) */ |
---|
792 | Resolve(); |
---|
793 | } |
---|
794 | |
---|
795 | int Board::TagNeighbours(int list[MAX_PIECES][MAX_PIECES], int i, int j) |
---|
796 | { |
---|
797 | vec2i const off[] = { vec2i(-1, 0), vec2i(1, 0), vec2i(0, -1), vec2i(0, 1) }; |
---|
798 | |
---|
799 | int count = 1; |
---|
800 | |
---|
801 | for (int n = 0; n < 4; n++) |
---|
802 | { |
---|
803 | int i2 = i + off[n].i; |
---|
804 | int j2 = j + off[n].j; |
---|
805 | |
---|
806 | if (i2 >= 0 && i2 < data->dim.i && j2 >= 0 && j2 < data->dim.j |
---|
807 | && data->pairs[i2][j2].id == data->pairs[i][j].id |
---|
808 | && list[i2][j2] == -1) |
---|
809 | { |
---|
810 | list[i2][j2] = list[i][j]; |
---|
811 | count += TagNeighbours(list, i2, j2); |
---|
812 | } |
---|
813 | } |
---|
814 | return count; |
---|
815 | } |
---|
816 | |
---|
817 | Board::~Board() |
---|
818 | { |
---|
819 | Input::UntrackMouse(this); |
---|
820 | |
---|
821 | switch (data->game) |
---|
822 | { |
---|
823 | case GAME_HUNT: |
---|
824 | for (int j = 0; j < data->dim.j; j++) |
---|
825 | for (int i = 0; i < data->dim.i; i++) |
---|
826 | { |
---|
827 | data->pairs[i][j].piece->SetBelow(NULL); |
---|
828 | Ticker::Unref(data->pairs[i][j].piece); |
---|
829 | } |
---|
830 | break; |
---|
831 | case GAME_FUSION: |
---|
832 | for (int j = 0; j < data->dim.j; j++) |
---|
833 | for (int i = 0; i < data->dim.i; i++) |
---|
834 | if (data->pairs[i][j].id) |
---|
835 | Ticker::Unref(data->pairs[i][j].piece); |
---|
836 | Ticker::Unref(data->current[0].piece); |
---|
837 | Ticker::Unref(data->current[1].piece); |
---|
838 | break; |
---|
839 | } |
---|
840 | Ticker::Unref(data->thumbs); |
---|
841 | Ticker::Unref(data->score); |
---|
842 | while (data->mashes) |
---|
843 | { |
---|
844 | Ticker::Unref(data->mashes); |
---|
845 | data->mashes = data->mashes->nextmash; |
---|
846 | } |
---|
847 | /* FIXME: the emitter may be destroyed after the Tiler is removed, |
---|
848 | * because the last Tiler tick may be done just after the emitter |
---|
849 | * scheduled its sprites! */ |
---|
850 | Ticker::Unref(data->emitter); |
---|
851 | Tiler::Deregister(data->screen); |
---|
852 | Tiler::Deregister(data->board); |
---|
853 | Tiler::Deregister(data->tiles); |
---|
854 | Tiler::Deregister(data->icons); |
---|
855 | Sampler::Deregister(data->click); |
---|
856 | Sampler::Deregister(data->whip); |
---|
857 | delete data; |
---|
858 | } |
---|
859 | |
---|