1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 #include "board.hh"
5 #include "panel.hh"
6 #include "tileset.hh"
7 #include "tile.hh"
8 #include "hint.hh"
9
Board(Panel * panel,Game * game,Tileset * tileset)10 Board::Board(Panel *panel, Game *game, Tileset *tileset)
11 : SwWidget(panel),
12 _panel(panel), _game(game), _tileset(0),
13 _redraw_live(true),
14 _layout_x(0), _layout_y(0),
15 _topmost_tile(-1), _botmost_tile(-1),
16 _leftmost_tile(-1), _rightmost_tile(-1),
17 _buffering(0), _buffer(None),
18 _selected(0)
19 {
20 // set up mask info
21 for (int i = 0; i < NMASK; i++) {
22 _mask[i] = None;
23 _mask_prev_mru[i] = i - 1;
24 _mask_next_mru[i] = (i == NMASK - 1 ? -1 : i + 1);
25 _mask_tile[i] = -1;
26 }
27 _mask_mru = 0;
28 _masking = -1;
29
30 set_tileset(tileset);
31 _game->add_hook(this);
32
33 _copygc = XCreateGC(display(), window(), 0, NULL);
34
35 XGCValues gcv;
36 gcv.function = GXor;
37 _orgc = XCreateGC(display(), window(), GCFunction, &gcv);
38
39 gcv.foreground = 97;
40 _erasegc = XCreateGC(display(), window(), GCForeground, &gcv);
41
42 gcv.foreground = 0UL;
43 gcv.background = ~0UL;
44 gcv.function = GXand;
45 _maskgc = XCreateGC(display(), window(),
46 GCForeground | GCBackground | GCFunction, &gcv);
47
48 gcv.function = GXandInverted;
49 gcv.foreground = ~0UL;
50 _mask_zero_gc = XCreateGC(display(), _mask[0], GCFunction | GCForeground, &gcv);
51
52 gcv.function = GXor;
53 gcv.foreground = ~0UL;
54 _mask_one_gc = XCreateGC(display(), _mask[0], GCFunction | GCForeground, &gcv);
55
56 _hint = new Hint(this);
57 }
58
~Board()59 Board::~Board()
60 {
61 delete _hint;
62
63 // free mask info
64 for (int i = 0; i < NMASK; i++) {
65 if (_mask[i])
66 XFreePixmap(display(), _mask[i]);
67 _mask[i] = 0;
68 }
69
70 XFreeGC(display(), _copygc);
71 XFreeGC(display(), _orgc);
72 XFreeGC(display(), _erasegc);
73 XFreeGC(display(), _maskgc);
74 XFreeGC(display(), _mask_one_gc);
75 XFreeGC(display(), _mask_zero_gc);
76 }
77
78 void
set_tileset(Tileset * ts)79 Board::set_tileset(Tileset *ts)
80 {
81 _tileset = ts;
82
83 _tile_width = ts->width();
84 _tile_height = ts->height();
85 _tile_xborder = ts->xborder();
86 _tile_yborder = ts->yborder();
87 _tile_shadow = ts->shadow();
88
89 assert(!_buffering);
90 _buffer_w = _tile_width + _tile_xborder;
91 _buffer_h = _tile_height + _tile_yborder;
92 if (_buffer) XFreePixmap(display(), _buffer);
93 _buffer = XCreatePixmap(display(), window(), _buffer_w, _buffer_h,
94 _panel->depth());
95
96 // create new masks
97 for (int i = 0; i < NMASK; i++) {
98 if (_mask[i]) XFreePixmap(display(), _mask[i]);
99 _mask[i] = XCreatePixmap(display(), window(), _buffer_w, _buffer_h, 1);
100 }
101 }
102
103
104 void
set_background(Pixmap background)105 Board::set_background(Pixmap background)
106 {
107 XSetTile(display(), _erasegc, background);
108 XSetFillStyle(display(), _erasegc, FillTiled);
109 }
110
111
112 #define CHECK(dr, dc, dl) do { u = g->grid(r+dr, c+dc, l+dl); \
113 if (u->marked()) display_order_dfs(g, u); } while(0)
114
115 void
display_order_dfs(Game * g,Tile * t)116 Board::display_order_dfs(Game *g, Tile *t)
117 {
118 t->unmark();
119
120 int r = t->row();
121 int c = t->col();
122 int l = t->lev();
123
124 Tile *u;
125
126 // This loop is to be careful for hypothetical strange boards, where there
127 // might be a bridge over a valley, for example: we have to check ALL
128 // levels above.
129 {
130 int dc_lft = -1, dc_rt = 1, dr_top = 0, dr_bot = 2;
131 for (int dl = 1; dl <= TILE_LEVS - 1 - l; dl++) {
132 for (int dr = dr_top; dr <= dr_bot; dr++)
133 for (int dc = dc_lft; dc <= dc_rt; dc++)
134 if (r+dr >= 0 && r+dr < TILE_ROWS && c+dc >= 0 && c+dc < TILE_COLS)
135 CHECK(dr, dc, dl);
136 (_tile_shadow & 1 ? dc_rt++ : dc_lft--);
137 (_tile_shadow & 2 ? dr_bot++ : dr_top--);
138 }
139 }
140
141 if (_tile_shadow & 1) {
142 CHECK(0, -1, 0);
143 CHECK(1, -1, 0);
144 CHECK(2, -1, 0);
145 CHECK(2, 0, 0);
146 CHECK(2, 1, 0);
147 } else {
148 CHECK(0, 2, 0);
149 CHECK(1, 2, 0);
150 CHECK(2, 2, 0);
151 CHECK(2, 1, 0);
152 CHECK(2, 0, 0);
153 }
154
155 _display_order.push_back(t);
156 }
157
158 #undef CHECK
159
160
161 void
layout_hook(Game * g)162 Board::layout_hook(Game *g)
163 {
164 assert(g == _game);
165 const Vector<Tile *> &tiles = g->tiles();
166 int ntiles = tiles.size();
167
168 _display_order.clear();
169 if (ntiles == 0) return;
170
171 // Create the display order.
172 for (int i = 0; i < ntiles; i++)
173 tiles[i]->mark();
174
175 for (int i = 0; i < ntiles; i++)
176 if (tiles[i]->marked())
177 display_order_dfs(g, tiles[i]);
178
179 assert(_display_order.size() == ntiles);
180 Vector<Tile *> d;
181 for (int j = ntiles - 1; j >= 0; j--)
182 d.push_back(_display_order[j]);
183 _display_order = d;
184
185 // Find the leftmost, topmost, rightmost, and bottommost tile positions.
186 short top = 0x7FFF, left = 0x7FFF, right = -0x8000, bottom = -0x8000;
187 for (int i = 0; i < ntiles; i++) {
188 short x, y;
189 position(tiles[i], &x, &y);
190 x -= _layout_x;
191 y -= _layout_y;
192 if (x < left)
193 left = x, _leftmost_tile = i;
194 if (y < top)
195 top = y, _topmost_tile = i;
196 if (x + _tile_width > right)
197 right = x + _tile_width, _rightmost_tile = i;
198 if (y + _tile_height > bottom)
199 bottom = y + _tile_height, _botmost_tile = i;
200 }
201
202 // Center the layout on the board.
203 center_layout();
204 }
205
206 int
topmost_tile_y() const207 Board::topmost_tile_y() const
208 {
209 short x, y;
210 position(_game->tile(_topmost_tile), &x, &y);
211 return y;
212 }
213
214 void
tile_layout_size(int * width,int * height) const215 Board::tile_layout_size(int *width, int *height) const
216 {
217 Tile *tleft = _game->tile(_leftmost_tile);
218 Tile *tright = _game->tile(_rightmost_tile);
219 Tile *ttop = _game->tile(_topmost_tile);
220 Tile *tbot = _game->tile(_botmost_tile);
221
222 short xx, yy, zz;
223 position(tleft, &xx, &yy);
224 position(tright, &zz, &yy);
225 *width = zz - xx + _tile_width + _tile_xborder;
226 position(ttop, &xx, &yy);
227 position(tbot, &xx, &zz);
228 *height = zz - yy + _tile_height + _tile_yborder;
229 }
230
231 void
center_layout()232 Board::center_layout()
233 {
234 if (_game->ntiles() == 0) return;
235
236 int layout_width, layout_height;
237 tile_layout_size(&layout_width, &layout_height);
238
239 short xx, yy;
240 _layout_x = 0;
241 position(_game->tile(_leftmost_tile), &xx, &yy);
242 _layout_x = x() + (width() - layout_width) / 2 - xx;
243
244 _layout_y = 0;
245 position(_game->tile(_topmost_tile), &xx, &yy);
246 _layout_y = y() + (height() - layout_height) / 2 - yy;
247 }
248
249
250 void
start_hook(Game * g)251 Board::start_hook(Game *g)
252 {
253 _tile_flags.assign(g->ntiles(), 0);
254 if (_panel->visible()) {
255 for (int i = 0; i < _display_order.size(); i++) {
256 Tile *t = _display_order[i];
257 if (t->real())
258 draw(t);
259 }
260 }
261 }
262
263
264 inline void
position(Tile * t,short * x,short * y) const265 Board::position(Tile *t, short *x, short *y) const
266 {
267 int r = t->row(), c = t->col(), l = t->lev();
268 *x = _layout_x
269 + _tile_width * (c >> 1)
270 + (c & 1 ? _tile_width >> 1 : 0)
271 + (_tile_shadow & 1 ? _tile_xborder * l : -_tile_xborder * l);
272 *y = _layout_y
273 + _tile_height * (r >> 1)
274 + (r & 1 ? _tile_height >> 1 : 0)
275 + (_tile_shadow & 2 ? _tile_yborder * l : -_tile_yborder * l);
276 }
277
278 void
unposition(int x,int y,short * r,short * c) const279 Board::unposition(int x, int y, short *r, short *c) const
280 {
281 *c = (x - _layout_x)*2 / _tile_width;
282 *r = (y - _layout_y)*2 / _tile_height;
283 }
284
285
286 Tile *
find_tile(short x,short y) const287 Board::find_tile(short x, short y) const
288 {
289 int rl_delta = (_tile_shadow & 2 ? 1 : 0);
290 int cl_delta = (_tile_shadow & 1 ? 1 : 0);
291 int ybord = (_tile_shadow & 2 ? _tile_yborder : -_tile_yborder);
292 int xbord = (_tile_shadow & 1 ? _tile_xborder : -_tile_xborder);
293 for (int l = TILE_LEVS - 1; l >= 0; l--) {
294 int r = (y - _layout_y - ybord * (l+rl_delta))*2 / _tile_height;
295 int c = (x - _layout_x - xbord * (l+cl_delta))*2 / _tile_width;
296 if (r < 0 || r >= TILE_ROWS) continue;
297 if (c < 0 || c >= TILE_COLS) continue;
298 Tile *t = _game->grid(r, c, l);
299 if (t->real()) {
300 short xx, yy;
301 position(t, &xx, &yy);
302 if (x >= xx && y >= yy)
303 return t;
304 }
305 }
306 return 0;
307 }
308
309
310 void
move(int x,int y)311 Board::move(int x, int y)
312 {
313 invalidate(_layout_x, _layout_y, TILE_COLS * _tile_width / 2,
314 TILE_ROWS * _tile_height / 2);
315 _layout_x = x;
316 _layout_y = y;
317 }
318
319
320 void
copy_buffer()321 Board::copy_buffer()
322 {
323 if (_buffering)
324 _panel->draw_image(_buffer, _buffer_w, _buffer_h, _buffer_x, _buffer_y);
325 }
326
327 void
buffer_on(int x,int y)328 Board::buffer_on(int x, int y)
329 {
330 _buffer_x = x;
331 _buffer_y = y;
332 _buffering = true;
333 }
334
335 void
buffer_off()336 Board::buffer_off()
337 {
338 _buffer_x = 0;
339 _buffer_y = 0;
340 _buffering = false;
341 }
342
343
344 // masking
345
346 void
mark_mask_used(int maskno)347 Board::mark_mask_used(int maskno)
348 {
349 if (_mask_mru != maskno) {
350 if (_mask_next_mru[maskno] != -1)
351 _mask_prev_mru[ _mask_next_mru[maskno] ] = _mask_prev_mru[maskno];
352 assert(_mask_prev_mru[maskno] != -1);
353 _mask_next_mru[ _mask_prev_mru[maskno] ] = _mask_next_mru[maskno];
354
355 _mask_next_mru[maskno] = _mask_mru;
356 _mask_prev_mru[maskno] = -1;
357 _mask_prev_mru[_mask_mru] = maskno;
358 _mask_mru = maskno;
359 }
360 }
361
362 int
lru_mask() const363 Board::lru_mask() const
364 {
365 int i = _mask_mru;
366 while (_mask_next_mru[i] != -1)
367 i = _mask_next_mru[i];
368 return i;
369 }
370
371
372 void
draw_subimage(Pixmap image,Pixmap mask,int src_x,int src_y,int w,int h,int x,int y)373 Board::draw_subimage(Pixmap image, Pixmap mask, int src_x, int src_y,
374 int w, int h, int x, int y)
375 {
376 if (_masking < 0 && !_buffering) {
377 _panel->draw_subimage(image, mask, src_x, src_y, w, h, x, y);
378 return;
379 }
380
381 x -= _buffer_x;
382 y -= _buffer_y;
383 if (_masking >= 0 && mask)
384 XCopyArea(display(), mask, _mask[_masking], _masking_gc,
385 src_x, src_y, w, h, x, y);
386 else if (_masking >= 0)
387 XFillRectangle(display(), _mask[_masking], _masking_gc, x, y, w, h);
388 else if (_buffering && mask) {
389 XCopyPlane(display(), mask, _buffer, _maskgc, src_x, src_y, w, h, x, y, 1);
390 XCopyArea(display(), image, _buffer, _orgc, src_x, src_y, w, h, x, y);
391 } else
392 XCopyArea(display(), image, _buffer, _copygc, src_x, src_y, w, h, x, y);
393 }
394
395
396 void
draw(Tile * t)397 Board::draw(Tile *t)
398 {
399 short x, y;
400 position(t, &x, &y);
401 if (_masking >= 0)
402 _masking_gc = (t == _masking_tile ? _mask_one_gc : _mask_zero_gc);
403
404 if (!t->real())
405 ;
406 else if (t->obscured())
407 _tileset->draw_obscured(t, this, x, y);
408 else if (lit(t))
409 _tileset->draw_lit(t, this, x, y);
410 else
411 _tileset->draw_normal(t, this, x, y);
412 }
413
414 void
draw_marked()415 Board::draw_marked()
416 {
417 for (int i = 0; i < _display_order.size(); i++) {
418 Tile *t = _display_order[i];
419 if (t->marked() && t->real()) {
420 draw(t);
421 t->unmark();
422 }
423 }
424 }
425
426 void
mark_around(Tile * t,bool up,bool down)427 Board::mark_around(Tile *t, bool up, bool down)
428 {
429 if (up) {
430 int colleft = t->col() - 1;
431 int colright = t->col() + 2;
432 int rowtop = t->row() - 1;
433 int rowbot = t->row() + 2;
434
435 for (int lev = t->lev(); lev < TILE_LEVS; lev++) {
436 for (int col = colleft; col <= colright; col++)
437 for (int row = rowtop; row <= rowbot; row++)
438 if (col >= 0 && col < TILE_COLS && row >= 0 && row < TILE_ROWS)
439 _game->grid(row, col, lev)->mark();
440 (_tile_shadow & 1 ? colright++ : colleft--);
441 (_tile_shadow & 2 ? rowbot++ : rowtop--);
442 }
443 }
444
445 if (down) {
446 int colleft = t->col() - 1;
447 int colright = t->col() + 2;
448 int rowtop = t->row() - 1;
449 int rowbot = t->row() + 2;
450
451 for (int lev = t->lev(); lev >= 0; lev--) {
452 for (int col = colleft; col <= colright; col++)
453 for (int row = rowtop; row <= rowbot; row++)
454 if (col >= 0 && col < TILE_COLS && row >= 0 && row < TILE_ROWS)
455 _game->grid(row, col, lev)->mark();
456 (_tile_shadow & 1 ? colright++ : colleft--);
457 (_tile_shadow & 2 ? rowbot++ : rowtop--);
458 }
459 }
460 }
461
462 void
draw_neighborhood(Tile * t,int erase)463 Board::draw_neighborhood(Tile *t, int erase)
464 {
465 short x, y;
466 position(t, &x, &y);
467 buffer_on(x, y);
468
469 switch (erase) {
470
471 case 0: { // drawing new tile
472 XCopyArea(display(), _panel->window(), _buffer, _copygc,
473 _buffer_x, _buffer_y, _buffer_w, _buffer_h, 0, 0);
474 mark_around(t, true, false);
475 draw_marked();
476 copy_buffer();
477 break;
478 }
479
480 case 1: { // erasing tile
481 XSetTSOrigin(display(), _erasegc, -_buffer_x, -_buffer_y);
482 XFillRectangle(display(), _buffer, _erasegc, 0, 0, _buffer_w, _buffer_h);
483 mark_around(t, true, true);
484 draw_marked();
485 copy_buffer();
486 break;
487 }
488
489 case 2: { // highlighting/unhighlighting tile
490 // find cached mask
491 int themask = -1;
492
493 for (int i = 0; i < NMASK; i++)
494 if (_mask_tile[i] == t->number())
495 themask = i;
496
497 if (themask < 0) {
498 // couldn't find a prepared mask for this tile; create one
499 _masking = themask = lru_mask();
500 XFillRectangle(display(), _mask[themask], _mask_zero_gc,
501 0, 0, _buffer_w, _buffer_h);
502 _masking_tile = t;
503
504 mark_around(t, true, false);
505 draw_marked();
506
507 _masking = -1;
508 }
509
510 // now have correct mask; use it
511 draw(t);
512
513 XSetClipMask(display(), _copygc, _mask[themask]);
514 XSetClipOrigin(display(), _copygc, _buffer_x, _buffer_y);
515 XCopyArea(display(), _buffer, _panel->window(), _copygc,
516 0, 0, _buffer_w, _buffer_h, _buffer_x, _buffer_y);
517 XSetClipMask(display(), _copygc, None);
518
519 mark_mask_used(themask);
520 _mask_tile[themask] = t->number();
521 break;
522 }
523
524 }
525 buffer_off();
526 }
527
528 void
draw_area(short rowtop,short colleft,short rowbot,short colright)529 Board::draw_area(short rowtop, short colleft, short rowbot, short colright)
530 {
531 colleft = (colleft < 0 ? 0 : colleft);
532 colright = (colright >= TILE_COLS ? TILE_COLS - 1 : colright);
533 rowtop = (rowtop < 0 ? 0 : rowtop);
534 rowbot = (rowbot >= TILE_ROWS ? TILE_ROWS - 1 : rowbot);
535 for (int lev = 0; lev < TILE_LEVS; lev++)
536 for (int col = colleft; col <= colright; col++)
537 for (int row = rowtop; row <= rowbot; row++)
538 _game->grid(row, col, lev)->mark();
539 draw_marked();
540 }
541
542
543 void
set_lit(Tile * t,bool on)544 Board::set_lit(Tile *t, bool on)
545 {
546 bool was_on = lit(t);
547 set_tile_flag(t, fLit, on);
548 if (on != was_on && t->real())
549 draw_neighborhood(t, 2);
550 }
551
552
553 void
select(Tile * t)554 Board::select(Tile *t)
555 {
556 if (_selected) deselect();
557 light(t);
558 set_tile_flag(t, fKeepLit, true);
559 _selected = t;
560 }
561
562 void
deselect()563 Board::deselect()
564 {
565 if (_selected) {
566 unlight(_selected);
567 set_tile_flag(_selected, fKeepLit, false);
568 _selected = 0;
569 }
570 }
571
572
573 void
add_tile_hook(Game * g,Tile * t)574 Board::add_tile_hook(Game *g, Tile *t)
575 {
576 assert(g == _game);
577 if (_redraw_live)
578 draw_neighborhood(t, 0);
579
580 // clear masking tiles
581 for (int i = 0; i < NMASK; i++)
582 _mask_tile[i] = -1;
583 }
584
585 void
remove_tile_hook(Game * g,Tile * t)586 Board::remove_tile_hook(Game *g, Tile *t)
587 {
588 assert(g == _game);
589 if (_selected == t) _selected = 0;
590 _tile_flags[t->number()] = 0;
591 if (_redraw_live)
592 draw_neighborhood(t, 1);
593
594 // clear masking tiles
595 for (int i = 0; i < NMASK; i++)
596 _mask_tile[i] = -1;
597 }
598