source: trunk/monsterz/board.cpp @ 866

Last change on this file since 866 was 866, checked in by sam, 12 years ago

core: more vec?i -> ?veci renames.

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