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