1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 #include "game.hh"
5 #include "solvable.hh"
6 #include "tile.hh"
7 #include "tileset.hh"
8 #include <cstdio>
9 #include <cstring>
10 #include <cctype>
11 
12 Tile Game::the_null_tile;
13 
14 
Game(Tileset * ts)15 Game::Game(Tileset *ts)
16   : _tileset(ts), _nmatches(ts->nmatches()),
17     _taken(0), _left(0), _possible_moves(0),
18     _user_move_pos(0),
19     _grid(new Tile *[TILE_ROWS * TILE_COLS * TILE_LEVS]),
20     _bad_free_count(1), _free_count(new int[_nmatches]),
21     _left_count(new int[_nmatches])
22 {
23   _user_moves.push_back(0);
24 }
25 
26 
~Game()27 Game::~Game()
28 {
29   clear_layout();
30   delete[] _grid;
31   delete[] _free_count;
32   delete[] _left_count;
33 }
34 
35 
36 void
remove_hook(GameHooks * gh)37 Game::remove_hook(GameHooks *gh)
38 {
39   for (int i = 0; i < _hooks.size(); i++)
40     if (_hooks[i] == gh) {
41       _hooks[i] = _hooks.back();
42       _hooks.pop_back();
43       return;
44     }
45 }
46 
47 
48 inline void
wipe_info()49 Game::wipe_info()
50 {
51   _bad_free_count = 1;
52   _possible_moves = -1;
53 }
54 
55 
56 void
check_level_blockage(int r,int c,int l) const57 Game::check_level_blockage(int r, int c, int l) const
58 {
59   Tile *t = grid(r, c, l);
60   if (t->real()) {
61     r = t->row();
62     c = t->col();
63     bool left_blocked = grid(r, c-1, l)->real() || grid(r+1, c-1, l)->real();
64     bool right_blocked = grid(r, c+2, l)->real() || grid(r+1, c+2, l)->real();
65     t->set_level_blockage(left_blocked && right_blocked);
66   }
67 }
68 
69 void
init_blockage()70 Game::init_blockage()
71 {
72   // reset all tiles
73   for (int i = 0; i < _tiles.size(); i++)
74     _tiles[i]->reset_blockage();
75 
76   for (int i = 0; i < _tiles.size(); i++)
77     if (_tiles[i]->real()) {
78       Tile *t = _tiles[i];
79       int rr = t->row();
80       int cc = t->col();
81       int ll = t->lev();
82 
83       // blockage from tiles above
84       if (ll > 0) {
85 	for (int r = rr; r < rr + 2; r++)
86 	  for (int c = cc; c < cc + 2; c++)
87 	    grid(r, c, ll-1)->block_upper();
88       }
89 
90       check_level_blockage(rr, cc, ll);
91     }
92 
93   // clear null tile blockage just for kicks
94   null_tile()->reset(false);
95 }
96 
97 
98 uint32_t
seed_to_board_number(uint32_t seed,bool solvable)99 Game::seed_to_board_number(uint32_t seed, bool solvable)
100 {
101   assert((seed & 0xC0000000U) == 0);
102   return ((seed & 0x3FFF0000) << 1)
103     | (solvable ? 0x10000 : 0)
104     | (seed & 0xFFFF);
105 }
106 
107 void
board_number_to_seed(uint32_t board_number,uint32_t & seed,bool & solvable)108 Game::board_number_to_seed(uint32_t board_number, uint32_t &seed,
109 			   bool &solvable)
110 {
111   assert((board_number & 0x80000000U) == 0);
112   solvable = (board_number & 0x10000) != 0;
113   seed = ((board_number & 0x7FFE0000) >> 1)
114     | (board_number & 0xFFFF);
115 }
116 
117 
118 void
assign(uint32_t seed)119 Game::assign(uint32_t seed)
120 {
121   int ntiles = _tiles.size();
122   int *permuted_tiles = new int[ntiles];
123 
124   for (int i = 0; i < ntiles; i++)
125     permuted_tiles[i] = i;
126 
127   seed &= 0x3FFFFFFFU;
128   zrand_seed(seed);
129   _board_number = seed_to_board_number(seed, false);
130   fprintf(stderr, "board number %u\n", _board_number);
131   for (int i = ntiles - 1; i > 0; i--) {
132     int j = zrand() % (i+1);
133     int tmp = permuted_tiles[i];
134     permuted_tiles[i] = permuted_tiles[j];
135     permuted_tiles[j] = tmp;
136   }
137 
138   for (int i = 0; i < ntiles; i++)
139     _tiles[i]->assign( permuted_tiles[i],
140 		       _tileset->match( permuted_tiles[i] ) );
141 
142   delete[] permuted_tiles;
143 }
144 
145 void
assign_solvable(uint32_t seed)146 Game::assign_solvable(uint32_t seed)
147 {
148   seed &= 0x3FFFFFFFU;
149   _board_number = seed_to_board_number(seed, true);
150   fprintf(stderr, "board number %u\n", _board_number);
151 
152   SolvableMaker sm(this);
153   sm.assign(seed);
154   _solution = sm.solution();
155 }
156 
157 
158 void
start(uint32_t the_seed,bool solvable)159 Game::start(uint32_t the_seed, bool solvable)
160 {
161   _taken = 0;
162   _left = _tiles.size();
163   _solution.clear();
164 
165   if (solvable)
166     assign_solvable(the_seed);
167   else
168     assign(the_seed);
169 
170   // Make all the tiles real and initialize the blockage.
171   for (int i = 0; i < _left; i++)
172     _tiles[i]->make_real();
173   init_blockage();
174 
175   for (int i = 0; i < _nmatches; i++)
176     _left_count[i] = 0;
177   for (int i = 0; i < _left; i++)
178     _left_count[_tiles[i]->match()]++;
179 
180   _moves.clear();
181   _user_moves.resize(1);
182   _user_move_pos = 0;
183   wipe_info();
184 
185   for (int i = 0; i < _hooks.size(); i++)
186     _hooks[i]->start_hook(this);
187 }
188 
189 
190 void
start_specific(uint32_t board_number)191 Game::start_specific(uint32_t board_number)
192 {
193   if (board_number & 0x80000000U)
194     fatal_error("bad board number");
195   uint32_t seed;
196   bool solvable;
197   board_number_to_seed(board_number, seed, solvable);
198   start(seed, solvable);
199 }
200 
201 
202 void
add(Tile * t)203 Game::add(Tile *t)
204 {
205   assert(!t->real());
206   t->make_real();
207 
208   int rr = t->row(), cc = t->col(), ll = t->lev();
209   for (int r = rr; r < rr + 2; r++) {
210     if (ll > 0) {
211       grid(r, cc, ll-1)->block_upper();
212       grid(r, cc+1, ll-1)->block_upper();
213     }
214     check_level_blockage(r, cc-1, ll);
215     check_level_blockage(r, cc+2, ll);
216   }
217 
218   _left++;
219   _taken--;
220   _left_count[t->match()]++;
221   wipe_info();
222 
223   for (int i = 0; i < _hooks.size(); i++)
224     _hooks[i]->add_tile_hook(this, t);
225 }
226 
227 
228 void
remove(Tile * t)229 Game::remove(Tile *t)
230 {
231   assert(t->real());
232   t->make_unreal();
233 
234   int rr = t->row(), cc = t->col(), ll = t->lev();
235   for (int r = rr; r < rr + 2; r++) {
236     if (ll > 0) {
237       grid(r, cc, ll-1)->unblock_upper();
238       grid(r, cc+1, ll-1)->unblock_upper();
239     }
240     check_level_blockage(r, cc-1, ll);
241     check_level_blockage(r, cc+2, ll);
242   }
243 
244   _left--;
245   _taken++;
246   _left_count[t->match()]--;
247   wipe_info();
248 
249   for (int i = 0; i < _hooks.size(); i++)
250     _hooks[i]->remove_tile_hook(this, t);
251 }
252 
253 
254 void
mark_user_move()255 Game::mark_user_move()
256 {
257   _user_moves.push_back(_moves.size());
258   _user_move_pos++;
259 
260   for (int i = 0; i < _hooks.size(); i++)
261     _hooks[i]->move_made_hook(this);
262 }
263 
264 void
move(Tile * t1,Tile * t2,bool was_user)265 Game::move(Tile *t1, Tile *t2, bool was_user)
266 {
267   if (_user_move_pos < _user_moves.size() - 1) {
268     int move_pos = _user_moves[_user_move_pos];
269     _moves.resize(move_pos);
270     _user_moves.resize(_user_move_pos + 1);
271   }
272   _moves.push_back(Move(t1, t2));
273   remove(t1);
274   remove(t2);
275   if (was_user) mark_user_move();
276 }
277 
278 bool
undo()279 Game::undo()
280 {
281   if (_user_move_pos == 0) return false;
282 
283   int end_move = _user_moves[_user_move_pos] - 1;
284   int start_move = _user_moves[_user_move_pos - 1];
285   _user_move_pos--;
286 
287   for (int i = end_move; i >= start_move; i--) {
288     // Add tiles in opposite order to removing them or blockage values will
289     // get screwed up
290     add(_moves[i].m2);
291     add(_moves[i].m1);
292   }
293 
294   for (int i = 0; i < _hooks.size(); i++)
295     _hooks[i]->move_made_hook(this);
296   return true;
297 }
298 
299 bool
redo()300 Game::redo()
301 {
302   if (_user_move_pos == _user_moves.size() - 1) return false;
303 
304   _user_move_pos++;
305   int start_move = _user_moves[_user_move_pos - 1];
306   int end_move = _user_moves[_user_move_pos] - 1;
307 
308   for (int i = start_move; i <= end_move; i++) {
309     remove(_moves[i].m1);
310     remove(_moves[i].m2);
311   }
312 
313   for (int i = 0; i < _hooks.size(); i++)
314     _hooks[i]->move_made_hook(this);
315   return true;
316 }
317 
318 void
make_free_count()319 Game::make_free_count()
320 {
321   int i;
322   int nmatch = _tileset->nmatches();
323 
324   for (i = 0; i < nmatch; i++)
325     _free_count[i] = 0;
326 
327   for (i = 0; i < _tiles.size(); i++)
328     if (_tiles[i]->real() && _tiles[i]->open()) {
329       int mc = _tiles[i]->match();
330       _free_count[mc]++;
331     }
332 
333   _bad_free_count = false;
334 }
335 
336 void
count_possible_moves()337 Game::count_possible_moves()
338 {
339   int nmatch = nmatches();
340   if (_bad_free_count) make_free_count();
341 
342   int i, j = 0;
343   for (i = 0; i < nmatch; i++)
344     if (_free_count[i] == 2) j += 1;
345     else if (_free_count[i] == 3) j += 3;
346     else if (_free_count[i] == 4) j += 6;
347 
348   _possible_moves = j;
349 }
350 
351 
352 
353 void
clear_layout()354 Game::clear_layout()
355 {
356   int i;
357   for (i = 0; i < _tiles.size(); i++)
358     delete _tiles[i];
359   _tiles.clear();
360 }
361 
362 static int
tile_sorter(const void * v1,const void * v2)363 tile_sorter(const void *v1, const void *v2)
364 {
365   Tile *t1 = *(Tile **)v1;
366   Tile *t2 = *(Tile **)v2;
367   if (t1->row() != t2->row())
368     return t1->row() - t2->row();
369   if (t1->col() != t2->col())
370     return t1->col() - t2->col();
371   return t2->lev() - t1->lev();
372 }
373 
374 bool
init_grid()375 Game::init_grid()
376 {
377   // Clear the grid.
378   for (int r = 0; r < TILE_ROWS; r++)
379     for (int c = 0; c < TILE_COLS; c++)
380       for (int l = 0; l < TILE_LEVS; l++)
381 	grid(r, c, l) = null_tile();
382 
383   int tile_count = _tiles.size();
384   for (int i = 0; i < tile_count; i++) {
385     Tile *t = _tiles[i];
386     for (int r = t->row(); r < t->row() + 2; r++)
387       for (int c = t->col(); c < t->col() + 2; c++) {
388 	// Ensure that any 2 tiles occupy disjoint space.
389 	if (grid(r, c, t->lev()) != null_tile()) {
390 	  fprintf(stderr, "tile overlaps existing tile at %d,%d,%d\n", r, c, t->lev());
391 	  return false;
392 	}
393 	grid(r, c, t->lev()) = t;
394       }
395   }
396 
397   // Sort the tiles in row, then column, then reverse-level order.
398   qsort(&_tiles[0], tile_count, sizeof(Tile *), &tile_sorter);
399 
400   // Done
401   return true;
402 }
403 
404 
405 bool
place_tile(int r,int c,int l)406 Game::place_tile(int r, int c, int l)
407 {
408   if (r > 1 && c > 1 && l >= 0
409       && r < TILE_ROWS - 3 && c < TILE_COLS - 3 && l < TILE_LEVS - 1) {
410     _tiles.push_back(new Tile(r, c, l));
411     return true;
412 
413   } else {
414     fprintf(stderr, "tile out of bounds at %d,%d,%d\n", r, c, l);
415     return false;
416   }
417 }
418 
419 
420 void
layout_default()421 Game::layout_default()
422 {
423   clear_layout();
424 
425   int i, j;
426   for (j = 2; j <= 13; j++) {
427     place_tile(2, j*2, 0);
428     place_tile(8, j*2, 0);
429     place_tile(10, j*2, 0);
430     place_tile(16, j*2, 0);
431   }
432 
433   for (j = 3; j <= 12; j++) {
434     place_tile(6, j*2, 0);
435     place_tile(12, j*2, 0);
436   }
437 
438   for (j = 4; j <= 11; j++) {
439     place_tile(4, j*2, 0);
440     place_tile(14, j*2, 0);
441   }
442 
443   for (j = 5; j <= 10; j++)
444     for (i = 2; i <= 7; i++)
445       place_tile(i*2, j*2, 1);
446 
447   for (j = 6; j <= 9; j++)
448     for (i = 3; i <= 6; i++)
449       place_tile(i*2, j*2, 2);
450 
451   for (j = 7; j <= 8; j++)
452     for (i = 4; i <= 5; i++)
453       place_tile(i*2, j*2, 3);
454 
455   place_tile(9, 2, 0);
456   place_tile(9, 28, 0);
457   place_tile(9, 30, 0);
458   place_tile(9, 15, 4);
459 
460   init_grid();
461   for (j = 0; j < _hooks.size(); j++)
462     _hooks[j]->layout_hook(this);
463 }
464 
465 bool
layout_kyodai_file(FILE * f)466 Game::layout_kyodai_file(FILE *f)
467 {
468   // skip `Kyodai 3.0'
469   int c = getc(f);
470   while (c != '\n' && c != EOF) c = getc(f);
471   // skip board identifier
472   c = getc(f);
473   while (c != '\n' && c != EOF) c = getc(f);
474 
475   for (int lev = 0; lev < 5; lev++)
476     for (int row = 0; row < 20; row++)
477       for (int col = 0; col < 34; col++) {
478 	c = ' ';
479 	while (isspace(c)) c = getc(f);
480 	if (c == '0')
481 	  /* don't place a tile */;
482 	else if (c == '1') {
483 	  if (!place_tile(row + 2, col + 2, lev))
484 	    return false;
485 	} else {
486 	  fprintf(stderr, "bad character");
487 	  return false;
488 	}
489       }
490 
491   return true;
492 }
493 
494 bool
layout_young_file(FILE * f)495 Game::layout_young_file(FILE *f)
496 {
497   char buffer[BUFSIZ];
498   while (!feof(f)) {
499     buffer[0] = 0;
500     fgets(buffer, BUFSIZ, f);
501     int r, c, l;
502     if (sscanf(buffer, " %d %d %d", &r, &c, &l) == 3)
503       if (!place_tile(r+2, c+2, l))
504 	return false;
505   }
506   return true;
507 }
508 
509 bool
layout_kmahjongg_file(FILE * f)510 Game::layout_kmahjongg_file(FILE *f)
511 {
512   char buf[BUFSIZ];
513 
514   // check for `kmahjongg-layout'
515   buf[0] = 0;
516   fgets(buf, BUFSIZ, f);
517   if (memcmp(buf, "kmahjongg-layout-v1", 19) != 0) {
518     fprintf(stderr, "not a kmahjongg layout file\n");
519     return false;
520   }
521 
522   // now read file
523   int l = 0;
524   while (!feof(f) && l < TILE_LEVS - 1) {
525     for (int r = 0; r < 16 && !feof(f); r++) {
526       buf[0] = 0;
527       fgets(buf, BUFSIZ, f);
528       for (int c = 0; c < TILE_COLS - 3 && buf[c] && !isspace(buf[c]); c++)
529 	if (buf[c] == '1') {
530 	  if (!place_tile(r + 2, c + 2, l))
531 	    return false;
532 	} else if (buf[c] == '2' || buf[c] == '3' || buf[c] == '4'
533 		   || buf[c] == '.')
534 	  /* ok */;
535 	else if (buf[c] == '#')
536 	  break;
537 	else {
538 	  fprintf(stderr, "bad character\n");
539 	  return false;
540 	}
541     }
542     l++;
543   }
544 
545   return true;
546 }
547 
548 int
layout_file(const char * filename)549 Game::layout_file(const char *filename)
550   /* returns -1 on system error, 0 on other error, 1 on no error */
551 {
552   clear_layout();
553 
554   FILE *f = fopen(filename, "r");
555   if (!f) return -1;
556 
557   bool ok;
558   int c = getc(f);
559   ungetc(c, f);
560   if (c == 'K')
561     ok = layout_kyodai_file(f);
562   else if (c == 'k')
563     ok = layout_kmahjongg_file(f);
564   else
565     ok = layout_young_file(f);
566 
567   fclose(f);
568 
569   if (!ok)
570     return 0;
571   else if (_tiles.size() != _tileset->ntiles()) {
572     fprintf(stderr, "%d tiles (should be %d)\n", _tiles.size(), _tileset->ntiles());
573     return 0;
574   } else if (!init_grid()) {
575     fprintf(stderr, "some tiles occupied the same space\n");
576     return 0;
577   }
578 
579   for (int j = 0; j < _hooks.size(); j++)
580     _hooks[j]->layout_hook(this);
581   return 1;
582 }
583 
584 void
relayout()585 Game::relayout()
586 {
587   for (int j = 0; j < _hooks.size(); j++)
588     _hooks[j]->layout_hook(this);
589 }
590