source: trunk/monsterz/board.cpp @ 313

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

Rework tile handling. This possibly breaks deushax pretty rudely.

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