source: trunk/monsterz/board.cpp @ 339

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

Add an Interface class that displays the general interface.

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