source: trunk/monsterz/board.cpp @ 307

Last change on this file since 307 was 307, checked in by sam, 10 years ago

Better move counting. We now ensure the initial board has valid moves.

  • Property svn:keywords set to Id
File size: 14.5 KB
Line 
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 <cstdio>
16#include <cmath>
17#include <cstdlib>
18#include <ctime>
19
20#include "core.h"
21#include "board.h"
22#include "piece.h"
23#include "mash.h"
24#include "monsterz.h"
25
26/*
27 * Board implementation class
28 */
29
30class BoardData
31{
32    friend class Board;
33
34private:
35    Game *game;
36    int screen, board, tiles;
37    int click, whip;
38
39    struct Pair
40    {
41        int id;
42        Piece *piece;
43    }
44    pairs[8][8], grabbed;
45    int nmoves;
46
47    Mash *mashes;
48    Emitter *emitter;
49    int2 src_cell, dst_cell;
50
51    int2 mouse;
52    int3 buttons;
53    float nextblink, whipdelay;
54
55    enum
56    {
57        IDLE,
58        BADCLICK,
59        GRAB,
60    }
61    state;
62};
63
64/*
65 * Public Board class
66 */
67
68Board::Board(Game *game)
69  : data(new BoardData())
70{
71    data->game = game;
72    Ticker::Ref(game);
73    data->screen = Tiler::Register(PNG_BACKGROUND, 640, 480, 1.0f);
74    data->board = Tiler::Register(PNG_BOARD, 384, 384, 1.0f);
75    data->tiles = Tiler::Register(PNG_TILES, 48, 48, 1.0f);
76    data->click = Sampler::Register(WAV_CLICK);
77    data->whip = Sampler::Register(WAV_WHIP);
78
79    data->emitter = new Emitter(data->tiles, float3(0, -0.0006f, 0));
80    Ticker::Ref(data->emitter);
81
82    srand(rand() ^ time(NULL));
83
84    /* Fill the board with a correct position */
85    int list[8][8];
86    do
87    {
88        for (int j = 0; j < 8; j++)
89            for (int i = 0; i < 8; i++)
90                data->pairs[i][j].id = 1 + rand() % 7;
91    } while (ListMashes(list) || !(data->nmoves = ListMoves(list)));
92
93    /* Spawn pieces */
94    for (int j = 0; j < 8; j++)
95        for (int i = 0; i < 8; i++)
96        {
97            int2 newpos = int2(i, j + 8) * 48;
98            int id = 80 + 20 * data->pairs[i][j].id;
99            data->pairs[i][j].piece = new Piece(data->emitter, int2(i, j), id);
100            data->pairs[i][j].piece->SetPos(newpos);
101            data->pairs[i][j].piece->Move(int2(i, j) * 48);
102            if (j)
103                data->pairs[i][j].piece->SetBelow(data->pairs[i][j - 1].piece);
104            Ticker::Ref(data->pairs[i][j].piece);
105        }
106
107    data->mashes = NULL;
108    data->nextblink = 0.0f;
109    data->whipdelay = 0.0f;
110    data->state = BoardData::IDLE;
111}
112
113void Board::TickGame(float deltams)
114{
115    Entity::TickGame(deltams);
116
117    int2 mouse = Input::GetMousePos();
118    int3 buttons = Input::GetMouseButtons();
119
120    /* If possible, make a random monster blink */
121    if ((data->nextblink -= deltams) < 0.0f)
122    {
123        data->pairs[rand() % 8][rand() % 8].piece->Blink();
124        data->nextblink = (float)(200 + rand() % 500);
125    }
126
127    /* Do not whip too often, the sound may become annoying */
128    data->whipdelay -= deltams;
129
130    /* Get rid of finished mashes */
131    for (Mash **it = &data->mashes; *it; )
132    {
133        if ((*it)->IsDead())
134        {
135            Ticker::Unref(*it);
136            *it = (*it)->nextmash;
137        }
138        else
139            it = &(*it)->nextmash;
140    }
141
142    switch (data->state)
143    {
144    case BoardData::IDLE:
145        /* Should we start dragging something? */
146        if (buttons[0] && !data->buttons[0])
147        {
148            int x = mouse.x - 24;
149            int y = mouse.y - 72;
150            if (x >= 0 && x < 8 * 48 && y >= 0 && y < 8 * 48)
151            {
152                if (data->pairs[x / 48][y / 48].piece->Grab(int2(0, 0)))
153                {
154                    Sampler::PlaySample(data->click);
155                    data->grabbed = data->pairs[x / 48][y / 48];
156                    data->src_cell = int2(x / 48, y / 48);
157                    data->dst_cell = int2(-1);
158                    data->state = BoardData::GRAB;
159                }
160                else
161                    data->state = BoardData::BADCLICK;
162            }
163            else
164                data->state = BoardData::BADCLICK;
165        }
166        break;
167    case BoardData::GRAB:
168        if (mouse.x >= 0 && mouse.y >= 0)
169        {
170            /* Mouse is still in the window, keep grabbing */
171            data->grabbed.piece->Grab(mouse - data->mouse);
172            int2 cur_pos = data->grabbed.piece->GetPos();
173            int2 cur_cell = (cur_pos + 24) / 48;
174            if (cur_cell.i < 0 || cur_cell.i >= 8
175                 || cur_cell.j < 0 || cur_cell.j >= 8
176                 || (cur_pos - cur_cell * 48).sqlen() > 24 * 24
177                 || (cur_cell - data->src_cell).sqlen() != 1)
178                cur_cell = int2(-1);
179            /* If potential target changed, update our cache. */
180            if (cur_cell != data->dst_cell)
181            {
182                if (data->whipdelay < 0.0f)
183                {
184                    Sampler::PlaySample(data->whip);
185                    data->whipdelay = DELAY_WHIP;
186                }
187                if (data->dst_cell != int2(-1))
188                    data->pairs[data->dst_cell.i]
189                               [data->dst_cell.j].piece->Ungrab(data->dst_cell * 48);
190                if (cur_cell != int2(-1))
191                    data->pairs[cur_cell.i]
192                               [cur_cell.j].piece->Ungrab(data->src_cell * 48);
193                data->dst_cell = cur_cell;
194            }
195        }
196        if (!buttons[0] || mouse.x < 0 || mouse.y < 0
197             || (data->src_cell * 48
198                  - data->grabbed.piece->GetPos()).sqlen() > 100 * 100)
199        {
200            /* Mouse released, or exited window, or dragged too far. */
201            data->grabbed.piece->Ungrab(data->grabbed.piece->GetCell() * 48);
202            if (data->dst_cell != int2(-1))
203                Switch(data->src_cell, data->dst_cell);
204            data->state = BoardData::IDLE;
205        }
206        break;
207    case BoardData::BADCLICK:
208        if (!buttons[0])
209            data->state = BoardData::IDLE;
210        break;
211    }
212
213    data->mouse = mouse;
214    data->buttons = buttons;
215}
216
217void Board::TickDraw(float deltams)
218{
219    Entity::TickDraw(deltams);
220
221    Scene::GetDefault()->AddTile((data->screen << 16) | 0, 0, 1050, 0, 0);
222    Scene::GetDefault()->AddTile((data->board << 16) | 0, 24, 912, 1, 0);
223
224    if (data->mouse.x >= 0 && data->mouse.y >= 0)
225    {
226        int x = data->mouse.x - 2;
227        int y = data->mouse.y + 59;
228        Scene::GetDefault()->AddTile((data->tiles << 16) | 22, x, y, 10, 0);
229    }
230}
231
232void Board::Switch(int2 cell_a, int2 cell_b)
233{
234    BoardData::Pair a = data->pairs[cell_a.i][cell_a.j];
235    BoardData::Pair b = data->pairs[cell_b.i][cell_b.j];
236    data->pairs[cell_a.i][cell_a.j] = b;
237    data->pairs[cell_b.i][cell_b.j] = a;
238
239    /* Check whether this is a valid move by testing all patterns.
240     * If the move is invalid, cancel the swap and bail out */
241    int list[8][8];
242
243    if (!ListMashes(list))
244    {
245        data->pairs[cell_a.i][cell_a.j] = a;
246        data->pairs[cell_b.i][cell_b.j] = b;
247        a.piece->Ungrab(cell_a * 48);
248        b.piece->Ungrab(cell_b * 48);
249        return;
250    }
251
252    /* Perform the swap */
253    a.piece->SetCell(cell_b);
254    a.piece->Ungrab(cell_b * 48);
255    b.piece->SetCell(cell_a);
256    b.piece->Ungrab(cell_a * 48);
257
258    /* Swap above and below cells */
259    if (cell_a.i == cell_b.i)
260    {
261        Piece *tmpa = a.piece->GetAbove();
262        Piece *tmpb = b.piece->GetAbove();
263        if (tmpb == a.piece)
264        {
265            tmpb = b.piece->GetBelow();
266            b.piece->SetAbove(tmpa);
267            b.piece->SetBelow(a.piece);
268            a.piece->SetBelow(tmpb);
269        }
270        else /* tmpa == b.piece */
271        {
272            tmpa = a.piece->GetBelow();
273            a.piece->SetAbove(tmpb);
274            a.piece->SetBelow(b.piece);
275            b.piece->SetBelow(tmpa);
276        }
277    }
278    else
279    {
280        Piece *tmpa = a.piece->GetAbove();
281        Piece *tmpb = b.piece->GetAbove();
282        a.piece->SetAbove(tmpb);
283        b.piece->SetAbove(tmpa);
284        tmpa = a.piece->GetBelow();
285        tmpb = b.piece->GetBelow();
286        a.piece->SetBelow(tmpb);
287        b.piece->SetBelow(tmpa);
288    }
289
290    /* Remove matching pieces and store them in Mash objects */
291    do
292    {
293        Mash *mash = new Mash(data->emitter);
294        Ticker::Ref(mash);
295
296        for (int j = 8; j--;) for (int i = 0; i < 8; i++)
297        {
298            if (!list[i][j])
299                continue;
300
301            /* The mash becomes the new owner of the disappearing piece */
302            mash->AddPiece(data->pairs[i][j].piece);
303
304#if 0 // Test for piece creation
305            if (list[i][j] >= 2)
306            {
307                Piece *old = data->pairs[i][j].piece;
308                int id = 1 + rand() % 7;
309                data->pairs[i][j].id = id;
310                data->pairs[i][j].piece = new Piece(data->emitter, int2(i, j), 80 + 20 * id);
311                data->pairs[i][j].piece->SetBelow(old->GetBelow());
312                data->pairs[i][j].piece->SetAbove(old->GetAbove());
313                Ticker::Ref(data->pieces[i][j].piece);
314                list[i][j] = 0;
315            }
316            else
317#endif
318            {
319                Piece *below = data->pairs[i][7].piece;
320
321                /* Change coordinates for the whole column above */
322                for (int j2 = j + 1; j2 < 8; j2++)
323                {
324                    data->pairs[i][j2 - 1] = data->pairs[i][j2];
325                    data->pairs[i][j2 - 1].piece->SetCell(int2(i, j2 - 1));
326                    data->pairs[i][j2 - 1].piece->Move(int2(i, j2 - 1) * 48);
327                    list[i][j2 - 1] = list[i][j2];
328                }
329
330                /* Spawn a new piece above all the others and attach it to
331                 * the board. */
332                int2 newpos = int2(i * 48, below->GetPos().y + 48);
333                int id = 1 + rand() % 7;
334                data->pairs[i][7].id = id;
335                data->pairs[i][7].piece = new Piece(data->emitter, int2(i, 7), 80 + 20 * id);
336                data->pairs[i][7].piece->SetBelow(below);
337                data->pairs[i][7].piece->SetPos(newpos);
338                data->pairs[i][7].piece->Move(int2(i, 7) * 48);
339                Ticker::Ref(data->pairs[i][7].piece);
340                list[i][7] = 0;
341            }
342        }
343
344        mash->nextmash = data->mashes;
345        data->mashes = mash;
346    }
347    while(ListMashes(list));
348
349    data->nmoves = ListMoves(list);
350}
351
352/* Fill an array with the list of pieces that should disappear due to
353 * 3-piece or more alignments. */
354int Board::ListMashes(int list[8][8])
355{
356    int ret = 0;
357
358    for (int j = 0; j < 8; j++)
359        for (int i = 0; i < 8; i++)
360            list[i][j] = 0;
361
362    for (int j = 0; j < 8; j++)
363        for (int i = 0; i < 8; i++)
364        {
365            int id = data->pairs[i][j].id;
366
367            if (i + 2 < 8 && data->pairs[i + 1][j].id == id
368                          && data->pairs[i + 2][j].id == id)
369            {
370                list[i][j]++;
371                list[i + 1][j] += 2;
372                list[i + 2][j]++;
373                ret = 1;
374            }
375
376            if (j + 2 < 8 && data->pairs[i][j + 1].id == id
377                          && data->pairs[i][j + 2].id == id)
378            {
379                list[i][j]++;
380                list[i][j + 1] += 2;
381                list[i][j + 2]++;
382                ret = 1;
383            }
384        }
385
386    return ret;
387}
388
389/* Fill an array with the list of pieces that can be moved. A value of 1
390 * indicates the piece can be moved right. A value of 2 means it can be
391 * moved up, and a value of 3 means both moves are possible. The number
392 * of possible moves is returned. */
393int Board::ListMoves(int moves[8][8])
394{
395    int ret = 0;
396
397    for (int j = 0; j < 8; j++)
398        for (int i = 0; i < 8; i++)
399            moves[i][j] = 0;
400
401    for (int j = 0; j < 8; j++)
402        for (int i = 0; i < 8; i++)
403        {
404            /* Copy neighbourhood to a local buffer */
405            int tmp[6][6];
406
407            for (int dj = -2; dj <= 3; dj++)
408                for (int di = -2; di <= 3; di++)
409                    if (j + dj >= 0 && j + dj < 8
410                         && i + di >= 0 && i + di < 8)
411                        tmp[2 + di][2 + dj] = data->pairs[i + di][j + dj].id;
412                    else
413                        tmp[2 + di][2 + dj] = 0;
414
415            /* +--+--+--+--+--+--+
416             * |  |  |25|  |  |  |
417             * +--+--+--+--+--+--+
418             * |  |  |24|34|  |  |
419             * +--+--+--+--+--+--+
420             * |03|13|c |33|43|  |
421             * +--+--+--+--+--+--+
422             * |02|12|a |b |42|52|
423             * +--+--+--+--+--+--+
424             * |  |11|21|31|  |  |
425             * +--+--+--+--+--+--+
426             * |  |  |20|30|  |  |
427             * +--+--+--+--+--+--+ */
428            int a = tmp[2][2];
429            int b = tmp[3][2] ? tmp[3][2] : -1;
430            int c = tmp[2][3] ? tmp[2][3] : -1;
431
432            /* Try moving right */
433            if ((a == tmp[3][0] && a == tmp[3][1]) ||
434                (a == tmp[3][1] && a == tmp[3][3]) ||
435                (a == tmp[3][3] && a == tmp[3][4]) ||
436                (a == tmp[4][2] && a == tmp[5][2]) ||
437                (b == tmp[2][0] && b == tmp[2][1]) ||
438                (b == tmp[2][1] && b == tmp[2][3]) ||
439                (b == tmp[2][3] && b == tmp[2][4]) ||
440                (b == tmp[0][2] && b == tmp[1][2]))
441            {
442                moves[i][j] |= 1;
443                ret++;
444            }
445
446            /* Try moving up */
447            if ((a == tmp[0][3] && a == tmp[1][3]) ||
448                (a == tmp[1][3] && a == tmp[3][3]) ||
449                (a == tmp[3][3] && a == tmp[4][3]) ||
450                (a == tmp[2][4] && a == tmp[2][5]) ||
451                (c == tmp[0][2] && c == tmp[1][2]) ||
452                (c == tmp[1][2] && c == tmp[3][2]) ||
453                (c == tmp[3][2] && c == tmp[4][2]) ||
454                (c == tmp[2][0] && c == tmp[2][1]))
455            {
456                moves[i][j] |= 2;
457                ret++;
458            }
459        }
460
461    return ret;
462}
463
464Board::~Board()
465{
466    Ticker::Unref(data->game);
467    for (int j = 0; j < 8; j++)
468        for (int i = 0; i < 8; i++)
469        {
470            data->pairs[i][j].piece->SetBelow(NULL);
471            Ticker::Unref(data->pairs[i][j].piece);
472        }
473    while (data->mashes)
474    {
475        Ticker::Unref(data->mashes);
476        data->mashes = data->mashes->nextmash;
477    }
478    /* FIXME: the emitter may be destroyed after the Tiler is removed,
479     * because the last Tiler tick may be done just after the emitter
480     * scheduled its sprites! */
481    Ticker::Unref(data->emitter);
482    Tiler::Deregister(data->board);
483    Tiler::Deregister(data->screen);
484    Tiler::Deregister(data->tiles);
485    Sampler::Deregister(data->click);
486    Sampler::Deregister(data->whip);
487    delete data;
488}
489
Note: See TracBrowser for help on using the repository browser.