Beginnings of a GTK framework. (And I do mean _beginnings_; it opens
[sgt/puzzles] / net.c
CommitLineData
720a8fb7 1/*
2 * net.c: Net game.
3 */
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <assert.h>
9
10#include "puzzles.h"
11#include "tree234.h"
12
13/* Direction bitfields */
14#define R 0x01
15#define U 0x02
16#define L 0x04
17#define D 0x08
18#define LOCKED 0x10
19
20/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
21#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
22#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) )
23#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) )
24#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \
25 ((n)&3) == 1 ? A(x) : \
26 ((n)&3) == 2 ? F(x) : C(x) )
27
28/* X and Y displacements */
29#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 )
30#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 )
31
32/* Bit count */
33#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \
34 (((x) & 0x02) >> 1) + ((x) & 0x01) )
35
36#define TILE_SIZE 32
37#define TILE_BORDER 1
38#define WINDOW_OFFSET 16
39
40struct game_params {
41 int width;
42 int height;
43 int wrapping;
44 float barrier_probability;
45};
46
47struct game_state {
48 int width, height, wrapping, completed;
49 unsigned char *tiles;
50 unsigned char *barriers;
51};
52
53#define OFFSET(x2,y2,x1,y1,dir,state) \
54 ( (x2) = ((x1) + (state)->width + X((dir))) % (state)->width, \
55 (y2) = ((y1) + (state)->height + Y((dir))) % (state)->height)
56
57#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
58#define tile(state, x, y) index(state, (state)->tiles, x, y)
59#define barrier(state, x, y) index(state, (state)->barriers, x, y)
60
61struct xyd {
62 int x, y, direction;
63};
64
65static int xyd_cmp(void *av, void *bv) {
66 struct xyd *a = (struct xyd *)av;
67 struct xyd *b = (struct xyd *)bv;
68 if (a->x < b->x)
69 return -1;
70 if (a->x > b->x)
71 return +1;
72 if (a->y < b->y)
73 return -1;
74 if (a->y > b->y)
75 return +1;
76 if (a->direction < b->direction)
77 return -1;
78 if (a->direction > b->direction)
79 return +1;
80 return 0;
81};
82
83static struct xyd *new_xyd(int x, int y, int direction)
84{
85 struct xyd *xyd = snew(struct xyd);
86 xyd->x = x;
87 xyd->y = y;
88 xyd->direction = direction;
89 return xyd;
90}
91
92/* ----------------------------------------------------------------------
93 * Randomly select a new game seed.
94 */
95
96char *new_game_seed(game_params *params)
97{
98 /*
99 * The full description of a Net game is far too large to
100 * encode directly in the seed, so by default we'll have to go
101 * for the simple approach of providing a random-number seed.
102 *
103 * (This does not restrict me from _later on_ inventing a seed
104 * string syntax which can never be generated by this code -
105 * for example, strings beginning with a letter - allowing me
106 * to type in a precise game, and have new_game detect it and
107 * understand it and do something completely different.)
108 */
109 char buf[40];
110 sprintf(buf, "%d", rand());
111 return dupstr(buf);
112}
113
114/* ----------------------------------------------------------------------
115 * Construct an initial game state, given a seed and parameters.
116 */
117
118game_state *new_game(game_params *params, char *seed)
119{
120 random_state *rs;
121 game_state *state;
122 tree234 *possibilities, *barriers;
123 int w, h, x, y, nbarriers;
124
125 assert(params->width > 2);
126 assert(params->height > 2);
127
128 /*
129 * Create a blank game state.
130 */
131 state = snew(game_state);
132 w = state->width = params->width;
133 h = state->height = params->height;
134 state->wrapping = params->wrapping;
135 state->completed = FALSE;
136 state->tiles = snewn(state->width * state->height, unsigned char);
137 memset(state->tiles, 0, state->width * state->height);
138 state->barriers = snewn(state->width * state->height, unsigned char);
139 memset(state->barriers, 0, state->width * state->height);
140
141 /*
142 * Set up border barriers if this is a non-wrapping game.
143 */
144 if (!state->wrapping) {
145 for (x = 0; x < state->width; x++) {
146 barrier(state, x, 0) |= U;
147 barrier(state, x, state->height-1) |= D;
148 }
149 for (y = 0; y < state->height; y++) {
150 barrier(state, y, 0) |= L;
151 barrier(state, y, state->width-1) |= R;
152 }
153 }
154
155 /*
156 * Seed the internal random number generator.
157 */
158 rs = random_init(seed, strlen(seed));
159
160 /*
161 * Construct the unshuffled grid.
162 *
163 * To do this, we simply start at the centre point, repeatedly
164 * choose a random possibility out of the available ways to
165 * extend a used square into an unused one, and do it. After
166 * extending the third line out of a square, we remove the
167 * fourth from the possibilities list to avoid any full-cross
168 * squares (which would make the game too easy because they
169 * only have one orientation).
170 *
171 * The slightly worrying thing is the avoidance of full-cross
172 * squares. Can this cause our unsophisticated construction
173 * algorithm to paint itself into a corner, by getting into a
174 * situation where there are some unreached squares and the
175 * only way to reach any of them is to extend a T-piece into a
176 * full cross?
177 *
178 * Answer: no it can't, and here's a proof.
179 *
180 * Any contiguous group of such unreachable squares must be
181 * surrounded on _all_ sides by T-pieces pointing away from the
182 * group. (If not, then there is a square which can be extended
183 * into one of the `unreachable' ones, and so it wasn't
184 * unreachable after all.) In particular, this implies that
185 * each contiguous group of unreachable squares must be
186 * rectangular in shape (any deviation from that yields a
187 * non-T-piece next to an `unreachable' square).
188 *
189 * So we have a rectangle of unreachable squares, with T-pieces
190 * forming a solid border around the rectangle. The corners of
191 * that border must be connected (since every tile connects all
192 * the lines arriving in it), and therefore the border must
193 * form a closed loop around the rectangle.
194 *
195 * But this can't have happened in the first place, since we
196 * _know_ we've avoided creating closed loops! Hence, no such
197 * situation can ever arise, and the naive grid construction
198 * algorithm will guaranteeably result in a complete grid
199 * containing no unreached squares, no full crosses _and_ no
200 * closed loops. []
201 */
202 possibilities = newtree234(xyd_cmp);
203 add234(possibilities, new_xyd(w/2, h/2, R));
204 add234(possibilities, new_xyd(w/2, h/2, U));
205 add234(possibilities, new_xyd(w/2, h/2, L));
206 add234(possibilities, new_xyd(w/2, h/2, D));
207
208 while (count234(possibilities) > 0) {
209 int i;
210 struct xyd *xyd;
211 int x1, y1, d1, x2, y2, d2, d;
212
213 /*
214 * Extract a randomly chosen possibility from the list.
215 */
216 i = random_upto(rs, count234(possibilities));
217 xyd = delpos234(possibilities, i);
218 x1 = xyd->x;
219 y1 = xyd->y;
220 d1 = xyd->direction;
221 sfree(xyd);
222
223 OFFSET(x2, y2, x1, y1, d1, state);
224 d2 = F(d1);
225#ifdef DEBUG
226 printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
227 x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
228#endif
229
230 /*
231 * Make the connection. (We should be moving to an as yet
232 * unused tile.)
233 */
234 tile(state, x1, y1) |= d1;
235 assert(tile(state, x2, y2) == 0);
236 tile(state, x2, y2) |= d2;
237
238 /*
239 * If we have created a T-piece, remove its last
240 * possibility.
241 */
242 if (COUNT(tile(state, x1, y1)) == 3) {
243 struct xyd xyd1, *xydp;
244
245 xyd1.x = x1;
246 xyd1.y = y1;
247 xyd1.direction = 0x0F ^ tile(state, x1, y1);
248
249 xydp = find234(possibilities, &xyd1, NULL);
250
251 if (xydp) {
252#ifdef DEBUG
253 printf("T-piece; removing (%d,%d,%c)\n",
254 xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
255#endif
256 del234(possibilities, xydp);
257 sfree(xydp);
258 }
259 }
260
261 /*
262 * Remove all other possibilities that were pointing at the
263 * tile we've just moved into.
264 */
265 for (d = 1; d < 0x10; d <<= 1) {
266 int x3, y3, d3;
267 struct xyd xyd1, *xydp;
268
269 OFFSET(x3, y3, x2, y2, d, state);
270 d3 = F(d);
271
272 xyd1.x = x3;
273 xyd1.y = y3;
274 xyd1.direction = d3;
275
276 xydp = find234(possibilities, &xyd1, NULL);
277
278 if (xydp) {
279#ifdef DEBUG
280 printf("Loop avoidance; removing (%d,%d,%c)\n",
281 xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
282#endif
283 del234(possibilities, xydp);
284 sfree(xydp);
285 }
286 }
287
288 /*
289 * Add new possibilities to the list for moving _out_ of
290 * the tile we have just moved into.
291 */
292 for (d = 1; d < 0x10; d <<= 1) {
293 int x3, y3;
294
295 if (d == d2)
296 continue; /* we've got this one already */
297
298 if (!state->wrapping) {
299 if (d == U && y2 == 0)
300 continue;
301 if (d == D && y2 == state->height-1)
302 continue;
303 if (d == L && x2 == 0)
304 continue;
305 if (d == R && x2 == state->width-1)
306 continue;
307 }
308
309 OFFSET(x3, y3, x2, y2, d, state);
310
311 if (tile(state, x3, y3))
312 continue; /* this would create a loop */
313
314#ifdef DEBUG
315 printf("New frontier; adding (%d,%d,%c)\n",
316 x2, y2, "0RU3L567D9abcdef"[d]);
317#endif
318 add234(possibilities, new_xyd(x2, y2, d));
319 }
320 }
321 /* Having done that, we should have no possibilities remaining. */
322 assert(count234(possibilities) == 0);
323 freetree234(possibilities);
324
325 /*
326 * Now compute a list of the possible barrier locations.
327 */
328 barriers = newtree234(xyd_cmp);
329 for (y = 0; y < state->height - (!state->wrapping); y++) {
330 for (x = 0; x < state->width - (!state->wrapping); x++) {
331
332 if (!(tile(state, x, y) & R))
333 add234(barriers, new_xyd(x, y, R));
334 if (!(tile(state, x, y) & D))
335 add234(barriers, new_xyd(x, y, D));
336 }
337 }
338
339 /*
340 * Now shuffle the grid.
341 */
342 for (y = 0; y < state->height - (!state->wrapping); y++) {
343 for (x = 0; x < state->width - (!state->wrapping); x++) {
344 int orig = tile(state, x, y);
345 int rot = random_upto(rs, 4);
346 tile(state, x, y) = ROT(orig, rot);
347 }
348 }
349
350 /*
351 * And now choose barrier locations. (We carefully do this
352 * _after_ shuffling, so that changing the barrier rate in the
353 * params while keeping the game seed the same will give the
354 * same shuffled grid and _only_ change the barrier locations.
355 * Also the way we choose barrier locations, by repeatedly
356 * choosing one possibility from the list until we have enough,
357 * is designed to ensure that raising the barrier rate while
358 * keeping the seed the same will provide a superset of the
359 * previous barrier set - i.e. if you ask for 10 barriers, and
360 * then decide that's still too hard and ask for 20, you'll get
361 * the original 10 plus 10 more, rather than getting 20 new
362 * ones and the chance of remembering your first 10.)
363 */
364 nbarriers = params->barrier_probability * count234(barriers);
365 assert(nbarriers >= 0 && nbarriers <= count234(barriers));
366
367 while (nbarriers > 0) {
368 int i;
369 struct xyd *xyd;
370 int x1, y1, d1, x2, y2, d2;
371
372 /*
373 * Extract a randomly chosen barrier from the list.
374 */
375 i = random_upto(rs, count234(barriers));
376 xyd = delpos234(barriers, i);
377
378 assert(xyd != NULL);
379
380 x1 = xyd->x;
381 y1 = xyd->y;
382 d1 = xyd->direction;
383 sfree(xyd);
384
385 OFFSET(x2, y2, x1, y1, d1, state);
386 d2 = F(d1);
387
388 barrier(state, x1, y1) |= d1;
389 barrier(state, x2, y2) |= d2;
390
391 nbarriers--;
392 }
393
394 /*
395 * Clean up the rest of the barrier list.
396 */
397 {
398 struct xyd *xyd;
399
400 while ( (xyd = delpos234(barriers, 0)) != NULL)
401 sfree(xyd);
402
403 freetree234(barriers);
404 }
405
406 random_free(rs);
407
408 return state;
409}
410
411game_state *dup_game(game_state *state)
412{
413 game_state *ret;
414
415 ret = snew(game_state);
416 ret->width = state->width;
417 ret->height = state->height;
418 ret->wrapping = state->wrapping;
419 ret->completed = state->completed;
420 ret->tiles = snewn(state->width * state->height, unsigned char);
421 memcpy(ret->tiles, state->tiles, state->width * state->height);
422 ret->barriers = snewn(state->width * state->height, unsigned char);
423 memcpy(ret->barriers, state->barriers, state->width * state->height);
424
425 return ret;
426}
427
428void free_game(game_state *state)
429{
430 sfree(state->tiles);
431 sfree(state->barriers);
432 sfree(state);
433}
434
435/* ----------------------------------------------------------------------
436 * Utility routine.
437 */
438
439/*
440 * Compute which squares are reachable from the centre square, as a
441 * quick visual aid to determining how close the game is to
442 * completion. This is also a simple way to tell if the game _is_
443 * completed - just call this function and see whether every square
444 * is marked active.
445 */
446static unsigned char *compute_active(game_state *state)
447{
448 unsigned char *active;
449 tree234 *todo;
450 struct xyd *xyd;
451
452 active = snewn(state->width * state->height, unsigned char);
453 memset(active, 0, state->width * state->height);
454
455 /*
456 * We only store (x,y) pairs in todo, but it's easier to reuse
457 * xyd_cmp and just store direction 0 every time.
458 */
459 todo = newtree234(xyd_cmp);
460 add234(todo, new_xyd(state->width / 2, state->height / 2, 0));
461
462 while ( (xyd = delpos234(todo, 0)) != NULL) {
463 int x1, y1, d1, x2, y2, d2;
464
465 x1 = xyd->x;
466 y1 = xyd->y;
467 sfree(xyd);
468
469 for (d1 = 1; d1 < 0x10; d1 <<= 1) {
470 OFFSET(x2, y2, x1, y1, d1, state);
471 d2 = F(d1);
472
473 /*
474 * If the next tile in this direction is connected to
475 * us, and there isn't a barrier in the way, and it
476 * isn't already marked active, then mark it active and
477 * add it to the to-examine list.
478 */
479 if ((tile(state, x1, y1) & d1) &&
480 (tile(state, x2, y2) & d2) &&
481 !(barrier(state, x1, y1) & d1) &&
482 !index(state, active, x2, y2)) {
483 index(state, active, x2, y2) = 1;
484 add234(todo, new_xyd(x2, y2, 0));
485 }
486 }
487 }
488 /* Now we expect the todo list to have shrunk to zero size. */
489 assert(count234(todo) == 0);
490 freetree234(todo);
491
492 return active;
493}
494
495/* ----------------------------------------------------------------------
496 * Process a move.
497 */
498game_state *make_move(game_state *state, int x, int y, int button)
499{
500 game_state *ret;
501 int tx, ty, orig;
502
503 /*
504 * All moves in Net are made with the mouse.
505 */
506 if (button != LEFT_BUTTON &&
507 button != MIDDLE_BUTTON &&
508 button != RIGHT_BUTTON)
509 return NULL;
510
511 /*
512 * The button must have been clicked on a valid tile.
513 */
514 x -= WINDOW_OFFSET;
515 y -= WINDOW_OFFSET;
516 if (x < 0 || y < 0)
517 return NULL;
518 tx = x / TILE_SIZE;
519 ty = y / TILE_SIZE;
520 if (tx >= state->width || ty >= state->height)
521 return NULL;
522 if (tx % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
523 ty % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
524 return NULL;
525
526 /*
527 * The middle button locks or unlocks a tile. (A locked tile
528 * cannot be turned, and is visually marked as being locked.
529 * This is a convenience for the player, so that once they are
530 * sure which way round a tile goes, they can lock it and thus
531 * avoid forgetting later on that they'd already done that one;
532 * and the locking also prevents them turning the tile by
533 * accident. If they change their mind, another middle click
534 * unlocks it.)
535 */
536 if (button == MIDDLE_BUTTON) {
537 ret = dup_game(state);
538 tile(ret, tx, ty) ^= LOCKED;
539 return ret;
540 }
541
542 /*
543 * The left and right buttons have no effect if clicked on a
544 * locked tile.
545 */
546 if (tile(state, tx, ty) & LOCKED)
547 return NULL;
548
549 /*
550 * Otherwise, turn the tile one way or the other. Left button
551 * turns anticlockwise; right button turns clockwise.
552 */
553 ret = dup_game(state);
554 orig = tile(ret, tx, ty);
555 if (button == LEFT_BUTTON)
556 tile(ret, tx, ty) = A(orig);
557 else
558 tile(ret, tx, ty) = C(orig);
559
560 /*
561 * Check whether the game has been completed.
562 */
563 {
564 unsigned char *active = compute_active(ret);
565 int x1, y1;
566 int complete = TRUE;
567
568 for (x1 = 0; x1 < ret->width; x1++)
569 for (y1 = 0; y1 < ret->height; y1++)
570 if (!index(ret, active, x1, y1)) {
571 complete = FALSE;
572 goto break_label; /* break out of two loops at once */
573 }
574 break_label:
575
576 sfree(active);
577
578 if (complete)
579 ret->completed = TRUE;
580 }
581
582 return ret;
583}
584
585/* ----------------------------------------------------------------------
586 * Routines for drawing the game position on the screen.
587 */
588
83680571 589/* ----------------------------------------------------------------------
590 * Test code.
591 */
592
593#ifdef TESTMODE
720a8fb7 594
595int main(void)
596{
597 game_params params = { 13, 11, TRUE, 0.1 };
598 char *seed;
599 game_state *state;
600 unsigned char *active;
601
602 seed = "123";
603 state = new_game(&params, seed);
604 active = compute_active(state);
605
606 {
607 int x, y;
608
609 printf("\033)0\016");
610 for (y = 0; y < state->height; y++) {
611 for (x = 0; x < state->width; x++) {
612 if (index(state, active, x, y))
613 printf("\033[1;32m");
614 else
615 printf("\033[0;31m");
616 putchar("~``m`qjv`lxtkwua"[tile(state, x, y)]);
617 }
618 printf("\033[m\n");
619 }
620 printf("\017");
621 }
622
623 free_game(state);
624
625 return 0;
626}
627
628#endif