source: trunk/monsterz/board.cpp @ 324

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

Add a text object to display current score.

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