source: trunk/monsterz/board.cpp @ 350

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

Display spawn counts in Fusion mode, and generate random pieces accordingly.

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