source: trunk/monsterz/board.cpp @ 304

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

Add a FIXME for a known bug.

  • Property svn:keywords set to Id
File size: 11.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    Game *game;
36    int screen, board, tiles;
37    int click, whip;
38
39    struct Pair
40    {
41        int id;
42        Piece *piece;
43    }
44    pairs[8][8], grabbed;
45
46    Mash *mashes;
47    Emitter *emitter;
48    int2 src_cell, dst_cell;
49
50    int2 mouse;
51    int3 buttons;
52    float nextblink, whipdelay;
53
54    enum
55    {
56        IDLE,
57        BADCLICK,
58        GRAB,
59    }
60    state;
61};
62
63/*
64 * Public Board class
65 */
66
67Board::Board(Game *game)
68  : data(new BoardData())
69{
70    data->game = game;
71    Ticker::Ref(game);
72    data->screen = Tiler::Register(PNG_BACKGROUND, 640, 480, 1.0f);
73    data->board = Tiler::Register(PNG_BOARD, 384, 384, 1.0f);
74    data->tiles = Tiler::Register(PNG_TILES, 48, 48, 1.0f);
75    data->click = Sampler::Register(WAV_CLICK);
76    data->whip = Sampler::Register(WAV_WHIP);
77
78    data->emitter = new Emitter(data->tiles, float3(0, -0.0006f, 0));
79    Ticker::Ref(data->emitter);
80
81    srand(rand() ^ time(NULL));
82
83    /* Fill the board with a correct position */
84    int list[8][8];
85    do
86    {
87        for (int j = 0; j < 8; j++)
88            for (int i = 0; i < 8; i++)
89                data->pairs[i][j].id = 1 + rand() % 7;
90    } while (ListMashes(list));
91
92    /* Spawn pieces */
93    for (int j = 0; j < 8; j++)
94        for (int i = 0; i < 8; i++)
95        {
96            int2 newpos = int2(i, j + 8) * 48;
97            int id = 80 + 20 * data->pairs[i][j].id;
98            data->pairs[i][j].piece = new Piece(data->emitter, int2(i, j), id);
99            data->pairs[i][j].piece->SetPos(newpos);
100            data->pairs[i][j].piece->Move(int2(i, j) * 48);
101            if (j)
102                data->pairs[i][j].piece->SetBelow(data->pairs[i][j - 1].piece);
103            Ticker::Ref(data->pairs[i][j].piece);
104        }
105
106    data->mashes = NULL;
107    data->nextblink = 0.0f;
108    data->whipdelay = 0.0f;
109    data->state = BoardData::IDLE;
110}
111
112void Board::TickGame(float deltams)
113{
114    Entity::TickGame(deltams);
115
116    int2 mouse = Input::GetMousePos();
117    int3 buttons = Input::GetMouseButtons();
118
119    /* If possible, make a random monster blink */
120    if ((data->nextblink -= deltams) < 0.0f)
121    {
122        data->pairs[rand() % 8][rand() % 8].piece->Blink();
123        data->nextblink = (float)(200 + rand() % 500);
124    }
125
126    /* Do not whip too often, the sound may become annoying */
127    data->whipdelay -= deltams;
128
129    /* Get rid of finished mashes */
130    for (Mash **it = &data->mashes; *it; )
131    {
132        if ((*it)->IsDead())
133        {
134            Ticker::Unref(*it);
135            *it = (*it)->nextmash;
136        }
137        else
138            it = &(*it)->nextmash;
139    }
140
141    switch (data->state)
142    {
143    case BoardData::IDLE:
144        /* Should we start dragging something? */
145        if (buttons[0] && !data->buttons[0])
146        {
147            int x = mouse.x - 24;
148            int y = mouse.y - 72;
149            if (x >= 0 && x < 8 * 48 && y >= 0 && y < 8 * 48)
150            {
151                if (data->pairs[x / 48][y / 48].piece->Grab(int2(0, 0)))
152                {
153                    Sampler::PlaySample(data->click);
154                    data->grabbed = data->pairs[x / 48][y / 48];
155                    data->src_cell = int2(x / 48, y / 48);
156                    data->dst_cell = int2(-1);
157                    data->state = BoardData::GRAB;
158                }
159                else
160                    data->state = BoardData::BADCLICK;
161            }
162            else
163                data->state = BoardData::BADCLICK;
164        }
165        break;
166    case BoardData::GRAB:
167        if (mouse.x >= 0 && mouse.y >= 0)
168        {
169            /* Mouse is still in the window, keep grabbing */
170            data->grabbed.piece->Grab(mouse - data->mouse);
171            int2 cur_pos = data->grabbed.piece->GetPos();
172            int2 cur_cell = (cur_pos + 24) / 48;
173            if (cur_cell.i < 0 || cur_cell.i >= 8
174                 || cur_cell.j < 0 || cur_cell.j >= 8
175                 || (cur_pos - cur_cell * 48).sqlen() > 24 * 24
176                 || (cur_cell - data->src_cell).sqlen() != 1)
177                cur_cell = int2(-1);
178            /* If potential target changed, update our cache. */
179            if (cur_cell != data->dst_cell)
180            {
181                if (data->whipdelay < 0.0f)
182                {
183                    Sampler::PlaySample(data->whip);
184                    data->whipdelay = DELAY_WHIP;
185                }
186                if (data->dst_cell != int2(-1))
187                    data->pairs[data->dst_cell.i]
188                               [data->dst_cell.j].piece->Ungrab(data->dst_cell * 48);
189                if (cur_cell != int2(-1))
190                    data->pairs[cur_cell.i]
191                               [cur_cell.j].piece->Ungrab(data->src_cell * 48);
192                data->dst_cell = cur_cell;
193            }
194        }
195        if (!buttons[0] || mouse.x < 0 || mouse.y < 0
196             || (data->src_cell * 48
197                  - data->grabbed.piece->GetPos()).sqlen() > 100 * 100)
198        {
199            /* Mouse released, or exited window, or dragged too far. */
200            data->grabbed.piece->Ungrab(data->grabbed.piece->GetCell() * 48);
201            if (data->dst_cell != int2(-1))
202                Switch(data->src_cell, data->dst_cell);
203            data->state = BoardData::IDLE;
204        }
205        break;
206    case BoardData::BADCLICK:
207        if (!buttons[0])
208            data->state = BoardData::IDLE;
209        break;
210    }
211
212    data->mouse = mouse;
213    data->buttons = buttons;
214}
215
216void Board::TickDraw(float deltams)
217{
218    Entity::TickDraw(deltams);
219
220    Scene::GetDefault()->AddTile((data->screen << 16) | 0, 0, 1050, 0, 0);
221    Scene::GetDefault()->AddTile((data->board << 16) | 0, 24, 912, 1, 0);
222
223    if (data->mouse.x >= 0 && data->mouse.y >= 0)
224    {
225        int x = data->mouse.x - 2;
226        int y = data->mouse.y + 59;
227        Scene::GetDefault()->AddTile((data->tiles << 16) | 22, x, y, 10, 0);
228    }
229}
230
231void Board::Switch(int2 cell_a, int2 cell_b)
232{
233    BoardData::Pair a = data->pairs[cell_a.i][cell_a.j];
234    BoardData::Pair b = data->pairs[cell_b.i][cell_b.j];
235    data->pairs[cell_a.i][cell_a.j] = b;
236    data->pairs[cell_b.i][cell_b.j] = a;
237
238    /* Check whether this is a valid move by testing all patterns.
239     * If the move is invalid, cancel the swap and bail out */
240    int list[8][8];
241
242    if (!ListMashes(list))
243    {
244        data->pairs[cell_a.i][cell_a.j] = a;
245        data->pairs[cell_b.i][cell_b.j] = b;
246        a.piece->Ungrab(cell_a * 48);
247        b.piece->Ungrab(cell_b * 48);
248        return;
249    }
250
251    /* Perform the swap */
252    a.piece->SetCell(cell_b);
253    a.piece->Ungrab(cell_b * 48);
254    b.piece->SetCell(cell_a);
255    b.piece->Ungrab(cell_a * 48);
256
257    /* Swap above and below cells */
258    if (cell_a.i == cell_b.i)
259    {
260        Piece *tmpa = a.piece->GetAbove();
261        Piece *tmpb = b.piece->GetAbove();
262        if (tmpb == a.piece)
263        {
264            tmpb = b.piece->GetBelow();
265            b.piece->SetAbove(tmpa);
266            b.piece->SetBelow(a.piece);
267            a.piece->SetBelow(tmpb);
268        }
269        else /* tmpa == b.piece */
270        {
271            tmpa = a.piece->GetBelow();
272            a.piece->SetAbove(tmpb);
273            a.piece->SetBelow(b.piece);
274            b.piece->SetBelow(tmpa);
275        }
276    }
277    else
278    {
279        Piece *tmpa = a.piece->GetAbove();
280        Piece *tmpb = b.piece->GetAbove();
281        a.piece->SetAbove(tmpb);
282        b.piece->SetAbove(tmpa);
283        tmpa = a.piece->GetBelow();
284        tmpb = b.piece->GetBelow();
285        a.piece->SetBelow(tmpb);
286        b.piece->SetBelow(tmpa);
287    }
288
289    /* Remove matching pieces and store them in Mash objects */
290    do
291    {
292        Mash *mash = new Mash(data->emitter);
293        Ticker::Ref(mash);
294
295        for (int j = 8; j--;) for (int i = 0; i < 8; i++)
296        {
297            if (!list[i][j])
298                continue;
299
300            /* The mash becomes the new owner of the disappearing piece */
301            mash->AddPiece(data->pairs[i][j].piece);
302
303#if 0 // Test for piece creation
304            if (list[i][j] >= 2)
305            {
306                Piece *old = data->pairs[i][j].piece;
307                int id = 1 + rand() % 7;
308                data->pairs[i][j].id = id;
309                data->pairs[i][j].piece = new Piece(data->emitter, int2(i, j), 80 + 20 * id);
310                data->pairs[i][j].piece->SetBelow(old->GetBelow());
311                data->pairs[i][j].piece->SetAbove(old->GetAbove());
312                Ticker::Ref(data->pieces[i][j].piece);
313                list[i][j] = 0;
314            }
315            else
316#endif
317            {
318                Piece *below = data->pairs[i][7].piece;
319
320                /* Change coordinates for the whole column above */
321                for (int j2 = j + 1; j2 < 8; j2++)
322                {
323                    data->pairs[i][j2 - 1] = data->pairs[i][j2];
324                    data->pairs[i][j2 - 1].piece->SetCell(int2(i, j2 - 1));
325                    data->pairs[i][j2 - 1].piece->Move(int2(i, j2 - 1) * 48);
326                    list[i][j2 - 1] = list[i][j2];
327                }
328
329                /* Spawn a new piece above all the others and attach it to
330                 * the board. */
331                int2 newpos = int2(i * 48, below->GetPos().y + 48);
332                int id = 1 + rand() % 7;
333                data->pairs[i][7].id = id;
334                data->pairs[i][7].piece = new Piece(data->emitter, int2(i, 7), 80 + 20 * id);
335                data->pairs[i][7].piece->SetBelow(below);
336                data->pairs[i][7].piece->SetPos(newpos);
337                data->pairs[i][7].piece->Move(int2(i, 7) * 48);
338                Ticker::Ref(data->pairs[i][7].piece);
339                list[i][7] = 0;
340            }
341        }
342
343        mash->nextmash = data->mashes;
344        data->mashes = mash;
345    }
346    while(ListMashes(list));
347}
348
349int Board::ListMashes(int list[8][8])
350{
351    int ret = 0;
352
353    for (int j = 0; j < 8; j++)
354        for (int i = 0; i < 8; i++)
355            list[i][j] = 0;
356
357    for (int j = 0; j < 8; j++)
358        for (int i = 0; i < 8; i++)
359        {
360            int id = data->pairs[i][j].id;
361
362            if (i + 2 < 8 && data->pairs[i + 1][j].id == id
363                          && data->pairs[i + 2][j].id == id)
364            {
365                list[i][j]++;
366                list[i + 1][j] += 2;
367                list[i + 2][j]++;
368                ret = 1;
369            }
370
371            if (j + 2 < 8 && data->pairs[i][j + 1].id == id
372                          && data->pairs[i][j + 2].id == id)
373            {
374                list[i][j]++;
375                list[i][j + 1] += 2;
376                list[i][j + 2]++;
377                ret = 1;
378            }
379        }
380
381    return ret;
382}
383
384Board::~Board()
385{
386    Ticker::Unref(data->game);
387    for (int j = 0; j < 8; j++)
388        for (int i = 0; i < 8; i++)
389        {
390            data->pairs[i][j].piece->SetBelow(NULL);
391            Ticker::Unref(data->pairs[i][j].piece);
392        }
393    while (data->mashes)
394    {
395        Ticker::Unref(data->mashes);
396        data->mashes = data->mashes->nextmash;
397    }
398    /* FIXME: the emitter may be destroyed after the Tiler is removed,
399     * because the last Tiler tick may be done just after the emitter
400     * scheduled its sprites! */
401    Ticker::Unref(data->emitter);
402    Tiler::Deregister(data->board);
403    Tiler::Deregister(data->screen);
404    Tiler::Deregister(data->tiles);
405    Sampler::Deregister(data->click);
406    Sampler::Deregister(data->whip);
407    delete data;
408}
409
Note: See TracBrowser for help on using the repository browser.