source: trunk/monsterz/board.cpp @ 330

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

First implementation of the mouse tracker.

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