source: trunk/monsterz/board.cpp @ 316

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

Board size and number of available pieces is now a parameter of
the Board class ctor.

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