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