source: trunk/monsterz/board.cpp @ 323

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

Clamp tiles to the board, using a PNG alpha trick.

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