X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/9dc3c55b68c3e3e373336657f2036dc673e90c13..390cfad8fccd779eef64566015d891efc7d98d15:/slant.c diff --git a/slant.c b/slant.c index eb77268..c4f9a9f 100644 --- a/slant.c +++ b/slant.c @@ -76,12 +76,13 @@ struct game_params { typedef struct game_clues { int w, h; signed char *clues; - signed char *tmpsoln; + int *tmpdsf; int refcount; } game_clues; #define ERR_VERTEX 1 #define ERR_SQUARE 2 +#define ERR_SQUARE_TMP 4 struct game_state { struct game_params p; @@ -1103,7 +1104,7 @@ static char *validate_desc(game_params *params, char *desc) return NULL; } -static game_state *new_game(midend_data *me, game_params *params, char *desc) +static game_state *new_game(midend *me, game_params *params, char *desc) { int w = params->w, h = params->h, W = w+1, H = h+1; game_state *state = snew(game_state); @@ -1122,7 +1123,7 @@ static game_state *new_game(midend_data *me, game_params *params, char *desc) state->clues->h = h; state->clues->clues = snewn(W*H, signed char); state->clues->refcount = 1; - state->clues->tmpsoln = snewn(w*h, signed char); + state->clues->tmpdsf = snewn(W*H, int); memset(state->clues->clues, -1, W*H); while (*desc) { int n = *desc++; @@ -1165,7 +1166,7 @@ static void free_game(game_state *state) assert(state->clues); if (--state->clues->refcount <= 0) { sfree(state->clues->clues); - sfree(state->clues->tmpsoln); + sfree(state->clues->tmpdsf); sfree(state->clues); } sfree(state); @@ -1216,62 +1217,161 @@ static int vertex_degree(int w, int h, signed char *soln, int x, int y, static int check_completion(game_state *state) { int w = state->p.w, h = state->p.h, W = w+1, H = h+1; - int x, y, err = FALSE; - signed char *ts; + int i, x, y, err = FALSE; + int *dsf; memset(state->errors, 0, W*H); /* - * An easy way to do loop checking would be by means of the - * same dsf technique we've used elsewhere (loop over all edges - * in the grid, joining vertices together into equivalence - * classes when connected by an edge, and raise the alarm when - * an edge joins two already-equivalent vertices). However, a - * better approach is to repeatedly remove the single edge - * connecting to any degree-1 vertex, and then see if there are - * any edges left over; if so, precisely those edges are part - * of loops, which means we can highlight them as errors for - * the user. + * To detect loops in the grid, we iterate through each edge + * building up a dsf of connected components, and raise the + * alarm whenever we find an edge that connects two + * already-connected vertices. * - * We use the `tmpsoln' scratch space in the shared clues + * We use the `tmpdsf' scratch space in the shared clues * structure, to avoid mallocing too often. + * + * When we find such an edge, we then search around the grid to + * find the loop it is a part of, so that we can highlight it + * as an error for the user. We do this by the hand-on-one-wall + * technique: the search will follow branches off the inside of + * the loop, discover they're dead ends, and unhighlight them + * again when returning to the actual loop. + * + * This technique guarantees that every loop it tracks will + * surround a disjoint area of the grid (since if an existing + * loop appears on the boundary of a new one, so that there are + * multiple possible paths that would come back to the starting + * point, it will pick the one that allows it to turn right + * most sharply and hence the one that does not re-surround the + * area of the previous one). Thus, the total time taken in + * searching round loops is linear in the grid area since every + * edge is visited at most twice. */ - ts = state->clues->tmpsoln; - memcpy(ts, state->soln, w*h); - for (y = 0; y < H; y++) - for (x = 0; x < W; x++) { - int vx = x, vy = y; - int sx, sy; + dsf = state->clues->tmpdsf; + for (i = 0; i < W*H; i++) + dsf[i] = i; /* initially all distinct */ + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) { + int i1, i2; + + if (state->soln[y*w+x] == 0) + continue; + if (state->soln[y*w+x] < 0) { + i1 = y*W+x; + i2 = (y+1)*W+(x+1); + } else { + i1 = y*W+(x+1); + i2 = (y+1)*W+x; + } + /* - * Every time we disconnect a vertex like this, there - * is precisely one other vertex which might have - * become degree 1; so we follow the trail as far as it - * leads. This ensures that we don't have to make more - * than one loop over the grid, because whenever a - * degree-1 vertex comes into existence somewhere we've - * already looked, we immediately remove it again. - * Hence one loop over the grid is adequate; and - * moreover, this algorithm visits every vertex at most - * twice (once in the loop and possibly once more as a - * result of following a trail) so it has linear time - * in the area of the grid. + * Our edge connects i1 with i2. If they're already + * connected, flag an error. Otherwise, link them. */ - while (vertex_degree(w, h, ts, vx, vy, FALSE, &sx, &sy) == 1) { - ts[sy*w+sx] = 0; - vx = vx + 1 + (sx - vx) * 2; - vy = vy + 1 + (sy - vy) * 2; - } - } + if (dsf_canonify(dsf, i1) == dsf_canonify(dsf, i2)) { + int x1, y1, x2, y2, dx, dy, dt, pass; - /* - * Now mark any remaining edges with ERR_SQUARE. - */ - for (y = 0; y < h; y++) - for (x = 0; x < w; x++) - if (ts[y*w+x]) { - state->errors[y*W+x] |= ERR_SQUARE; - err = TRUE; - } + err = TRUE; + + /* + * Now search around the boundary of the loop to + * highlight it. + * + * We have to do this in two passes. The first + * time, we toggle ERR_SQUARE_TMP on each edge; + * this pass terminates with ERR_SQUARE_TMP set on + * exactly the loop edges. In the second pass, we + * trace round that loop again and turn + * ERR_SQUARE_TMP into ERR_SQUARE. We have to do + * this because otherwise we might cancel part of a + * loop highlighted in a previous iteration of the + * outer loop. + */ + + for (pass = 0; pass < 2; pass++) { + + x1 = i1 % W; + y1 = i1 / W; + x2 = i2 % W; + y2 = i2 / W; + + do { + /* Mark this edge. */ + if (pass == 0) { + state->errors[min(y1,y2)*W+min(x1,x2)] ^= + ERR_SQUARE_TMP; + } else { + state->errors[min(y1,y2)*W+min(x1,x2)] |= + ERR_SQUARE; + state->errors[min(y1,y2)*W+min(x1,x2)] &= + ~ERR_SQUARE_TMP; + } + + /* + * Progress to the next edge by turning as + * sharply right as possible. In fact we do + * this by facing back along the edge and + * turning _left_ until we see an edge we + * can follow. + */ + dx = x1 - x2; + dy = y1 - y2; + + for (i = 0; i < 4; i++) { + /* + * Rotate (dx,dy) to the left. + */ + dt = dx; dx = dy; dy = -dt; + + /* + * See if (x2,y2) has an edge in direction + * (dx,dy). + */ + if (x2+dx < 0 || x2+dx >= W || + y2+dy < 0 || y2+dy >= H) + continue; /* off the side of the grid */ + /* In the second pass, ignore unmarked edges. */ + if (pass == 1 && + !(state->errors[(y2-(dy<0))*W+x2-(dx<0)] & + ERR_SQUARE_TMP)) + continue; + if (state->soln[(y2-(dy<0))*w+x2-(dx<0)] == + (dx==dy ? -1 : +1)) + break; + } + + /* + * In pass 0, we expect to have found + * _some_ edge we can follow, even if it + * was found by rotating all the way round + * and going back the way we came. + * + * In pass 1, because we're removing the + * mark on each edge that allows us to + * follow it, we expect to find _no_ edge + * we can follow when we've come all the + * way round the loop. + */ + if (pass == 1 && i == 4) + break; + assert(i < 4); + + /* + * Set x1,y1 to x2,y2, and x2,y2 to be the + * other end of the new edge. + */ + x1 = x2; + y1 = y2; + x2 += dx; + y2 += dy; + } while (y2*W+x2 != i2); + + } + + } else + dsf_merge(dsf, i1, i2); + } /* * Now go through and check the degree of each clue vertex, and @@ -1601,13 +1701,13 @@ static void game_compute_size(game_params *params, int tilesize, *y = 2 * BORDER + params->h * TILESIZE + 1; } -static void game_set_size(game_drawstate *ds, game_params *params, - int tilesize) +static void game_set_size(drawing *dr, game_drawstate *ds, + game_params *params, int tilesize) { ds->tilesize = tilesize; } -static float *game_colours(frontend *fe, game_state *state, int *ncolours) +static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); @@ -1637,7 +1737,7 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours) return ret; } -static game_drawstate *game_new_drawstate(game_state *state) +static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) { int w = state->p.w, h = state->p.h; int i; @@ -1653,79 +1753,80 @@ static game_drawstate *game_new_drawstate(game_state *state) return ds; } -static void game_free_drawstate(game_drawstate *ds) +static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->todraw); sfree(ds->grid); sfree(ds); } -static void draw_clue(frontend *fe, game_drawstate *ds, - int x, int y, int v, int err) +static void draw_clue(drawing *dr, game_drawstate *ds, + int x, int y, long v, long err, int bg, int colour) { char p[2]; - int ccol = ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2; - int tcol = err ? COL_ERROR : COL_INK; + int ccol = colour >= 0 ? colour : ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2; + int tcol = colour >= 0 ? colour : err ? COL_ERROR : COL_INK; if (v < 0) return; p[0] = v + '0'; p[1] = '\0'; - draw_circle(fe, COORD(x), COORD(y), CLUE_RADIUS, COL_BACKGROUND, ccol); - draw_text(fe, COORD(x), COORD(y), FONT_VARIABLE, + draw_circle(dr, COORD(x), COORD(y), CLUE_RADIUS, + bg >= 0 ? bg : COL_BACKGROUND, ccol); + draw_text(dr, COORD(x), COORD(y), FONT_VARIABLE, CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE, tcol, p); } -static void draw_tile(frontend *fe, game_drawstate *ds, game_clues *clues, - int x, int y, int v) +static void draw_tile(drawing *dr, game_drawstate *ds, game_clues *clues, + int x, int y, long v) { int w = clues->w, h = clues->h, W = w+1 /*, H = h+1 */; int chesscolour = (x ^ y) & 1; int fscol = chesscolour ? COL_SLANT2 : COL_SLANT1; int bscol = chesscolour ? COL_SLANT1 : COL_SLANT2; - clip(fe, COORD(x), COORD(y), TILESIZE, TILESIZE); + clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); - draw_rect(fe, COORD(x), COORD(y), TILESIZE, TILESIZE, + draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE, (v & FLASH) ? COL_GRID : COL_BACKGROUND); /* * Draw the grid lines. */ if (x >= 0 && x < w && y >= 0) - draw_rect(fe, COORD(x), COORD(y), TILESIZE+1, 1, COL_GRID); + draw_rect(dr, COORD(x), COORD(y), TILESIZE+1, 1, COL_GRID); if (x >= 0 && x < w && y < h) - draw_rect(fe, COORD(x), COORD(y+1), TILESIZE+1, 1, COL_GRID); + draw_rect(dr, COORD(x), COORD(y+1), TILESIZE+1, 1, COL_GRID); if (y >= 0 && y < h && x >= 0) - draw_rect(fe, COORD(x), COORD(y), 1, TILESIZE+1, COL_GRID); + draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE+1, COL_GRID); if (y >= 0 && y < h && x < w) - draw_rect(fe, COORD(x+1), COORD(y), 1, TILESIZE+1, COL_GRID); + draw_rect(dr, COORD(x+1), COORD(y), 1, TILESIZE+1, COL_GRID); if (x == -1 && y == -1) - draw_rect(fe, COORD(x+1), COORD(y+1), 1, 1, COL_GRID); + draw_rect(dr, COORD(x+1), COORD(y+1), 1, 1, COL_GRID); if (x == -1 && y == h) - draw_rect(fe, COORD(x+1), COORD(y), 1, 1, COL_GRID); + draw_rect(dr, COORD(x+1), COORD(y), 1, 1, COL_GRID); if (x == w && y == -1) - draw_rect(fe, COORD(x), COORD(y+1), 1, 1, COL_GRID); + draw_rect(dr, COORD(x), COORD(y+1), 1, 1, COL_GRID); if (x == w && y == h) - draw_rect(fe, COORD(x), COORD(y), 1, 1, COL_GRID); + draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID); /* * Draw the slash. */ if (v & BACKSLASH) { int scol = (v & ERRSLASH) ? COL_ERROR : bscol; - draw_line(fe, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol); - draw_line(fe, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1, + draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol); + draw_line(dr, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1, scol); - draw_line(fe, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1), + draw_line(dr, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1), scol); } else if (v & FORWSLASH) { int scol = (v & ERRSLASH) ? COL_ERROR : fscol; - draw_line(fe, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol); - draw_line(fe, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1, + draw_line(dr, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol); + draw_line(dr, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1, scol); - draw_line(fe, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1), + draw_line(dr, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1), scol); } @@ -1734,38 +1835,39 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_clues *clues, * neighbouring cell. */ if (v & (L_T | BACKSLASH)) - draw_rect(fe, COORD(x), COORD(y)+1, 1, 1, - (v & (ERR_L_T | ERRSLASH) ? COL_ERROR : bscol)); + draw_rect(dr, COORD(x), COORD(y)+1, 1, 1, + (v & ERR_L_T ? COL_ERROR : bscol)); if (v & (L_B | FORWSLASH)) - draw_rect(fe, COORD(x), COORD(y+1)-1, 1, 1, - (v & (ERR_L_B | ERRSLASH) ? COL_ERROR : fscol)); + draw_rect(dr, COORD(x), COORD(y+1)-1, 1, 1, + (v & ERR_L_B ? COL_ERROR : fscol)); if (v & (T_L | BACKSLASH)) - draw_rect(fe, COORD(x)+1, COORD(y), 1, 1, - (v & (ERR_T_L | ERRSLASH) ? COL_ERROR : bscol)); + draw_rect(dr, COORD(x)+1, COORD(y), 1, 1, + (v & ERR_T_L ? COL_ERROR : bscol)); if (v & (T_R | FORWSLASH)) - draw_rect(fe, COORD(x+1)-1, COORD(y), 1, 1, - (v & (ERR_T_R | ERRSLASH) ? COL_ERROR : fscol)); + draw_rect(dr, COORD(x+1)-1, COORD(y), 1, 1, + (v & ERR_T_R ? COL_ERROR : fscol)); if (v & (C_TL | BACKSLASH)) - draw_rect(fe, COORD(x), COORD(y), 1, 1, - (v & (ERR_C_TL | ERRSLASH) ? COL_ERROR : bscol)); + draw_rect(dr, COORD(x), COORD(y), 1, 1, + (v & ERR_C_TL ? COL_ERROR : bscol)); /* * And finally the clues at the corners. */ if (x >= 0 && y >= 0) - draw_clue(fe, ds, x, y, clues->clues[y*W+x], v & ERR_TL); + draw_clue(dr, ds, x, y, clues->clues[y*W+x], v & ERR_TL, -1, -1); if (x < w && y >= 0) - draw_clue(fe, ds, x+1, y, clues->clues[y*W+(x+1)], v & ERR_TR); + draw_clue(dr, ds, x+1, y, clues->clues[y*W+(x+1)], v & ERR_TR, -1, -1); if (x >= 0 && y < h) - draw_clue(fe, ds, x, y+1, clues->clues[(y+1)*W+x], v & ERR_BL); + draw_clue(dr, ds, x, y+1, clues->clues[(y+1)*W+x], v & ERR_BL, -1, -1); if (x < w && y < h) - draw_clue(fe, ds, x+1, y+1, clues->clues[(y+1)*W+(x+1)], v & ERR_BR); + draw_clue(dr, ds, x+1, y+1, clues->clues[(y+1)*W+(x+1)], v & ERR_BR, + -1, -1); - unclip(fe); - draw_update(fe, COORD(x), COORD(y), TILESIZE, TILESIZE); + unclip(dr); + draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); } -static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, +static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, game_state *state, int dir, game_ui *ui, float animtime, float flashtime) { @@ -1781,8 +1883,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, if (!ds->started) { int ww, wh; game_compute_size(&state->p, TILESIZE, &ww, &wh); - draw_rect(fe, 0, 0, ww, wh, COL_BACKGROUND); - draw_update(fe, 0, 0, ww, wh); + draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND); + draw_update(dr, 0, 0, ww, wh); ds->started = TRUE; } @@ -1809,7 +1911,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, ds->todraw[(y+1)*(w+2)+(x+2)] |= L_B; ds->todraw[(y+2)*(w+2)+(x+2)] |= C_TL; if (err) { - ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH; + ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH | + ERR_T_L | ERR_L_T | ERR_C_TL; ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_R; ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_B; ds->todraw[(y+2)*(w+2)+(x+2)] |= ERR_C_TL; @@ -1819,7 +1922,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, ds->todraw[(y+1)*(w+2)+(x+2)] |= L_T | C_TL; ds->todraw[(y+2)*(w+2)+(x+1)] |= T_L | C_TL; if (err) { - ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH; + ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH | + ERR_L_B | ERR_T_R; ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_T | ERR_C_TL; ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_L | ERR_C_TL; } @@ -1842,7 +1946,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, for (y = -1; y <= h; y++) { for (x = -1; x <= w; x++) { if (ds->todraw[(y+1)*(w+2)+(x+1)] != ds->grid[(y+1)*(w+2)+(x+1)]) { - draw_tile(fe, ds, state->clues, x, y, + draw_tile(dr, ds, state->clues, x, y, ds->todraw[(y+1)*(w+2)+(x+1)]); ds->grid[(y+1)*(w+2)+(x+1)] = ds->todraw[(y+1)*(w+2)+(x+1)]; } @@ -1866,14 +1970,80 @@ static float game_flash_length(game_state *oldstate, game_state *newstate, return 0.0F; } -static int game_wants_statusbar(void) +static int game_timing_state(game_state *state, game_ui *ui) { - return FALSE; + return TRUE; } -static int game_timing_state(game_state *state, game_ui *ui) +static void game_print_size(game_params *params, float *x, float *y) { - return TRUE; + int pw, ph; + + /* + * I'll use 6mm squares by default. + */ + game_compute_size(params, 600, &pw, &ph); + *x = pw / 100.0; + *y = ph / 100.0; +} + +static void game_print(drawing *dr, game_state *state, int tilesize) +{ + int w = state->p.w, h = state->p.h, W = w+1; + int ink = print_mono_colour(dr, 0); + int paper = print_mono_colour(dr, 1); + int x, y; + + /* Ick: fake up `ds->tilesize' for macro expansion purposes */ + game_drawstate ads, *ds = &ads; + game_set_size(dr, ds, NULL, tilesize); + + /* + * Border. + */ + print_line_width(dr, TILESIZE / 16); + draw_rect_outline(dr, COORD(0), COORD(0), w*TILESIZE, h*TILESIZE, ink); + + /* + * Grid. + */ + print_line_width(dr, TILESIZE / 24); + for (x = 1; x < w; x++) + draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink); + for (y = 1; y < h; y++) + draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink); + + /* + * Solution. + */ + print_line_width(dr, TILESIZE / 12); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + if (state->soln[y*w+x]) { + int ly, ry; + /* + * To prevent nasty line-ending artefacts at + * corners, I'll do something slightly cunning + * here. + */ + clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE); + if (state->soln[y*w+x] < 0) + ly = y-1, ry = y+2; + else + ry = y-1, ly = y+2; + draw_line(dr, COORD(x-1), COORD(ly), COORD(x+2), COORD(ry), + ink); + unclip(dr); + } + + /* + * Clues. + */ + print_line_width(dr, TILESIZE / 24); + for (y = 0; y <= h; y++) + for (x = 0; x <= w; x++) + draw_clue(dr, ds, x, y, state->clues->clues[y*W+x], + FALSE, paper, ink); } #ifdef COMBINED @@ -1911,54 +2081,16 @@ const struct game thegame = { game_redraw, game_anim_length, game_flash_length, - game_wants_statusbar, + TRUE, FALSE, game_print_size, game_print, + FALSE, /* wants_statusbar */ FALSE, game_timing_state, - 0, /* mouse_priorities */ + 0, /* flags */ }; #ifdef STANDALONE_SOLVER #include -/* - * gcc -DSTANDALONE_SOLVER -o slantsolver slant.c malloc.c - */ - -void frontend_default_colour(frontend *fe, float *output) {} -void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, - int align, int colour, char *text) {} -void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) {} -void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) {} -void draw_polygon(frontend *fe, int *coords, int npoints, - int fillcolour, int outlinecolour) {} -void draw_circle(frontend *fe, int cx, int cy, int radius, - int fillcolour, int outlinecolour) {} -void clip(frontend *fe, int x, int y, int w, int h) {} -void unclip(frontend *fe) {} -void start_draw(frontend *fe) {} -void draw_update(frontend *fe, int x, int y, int w, int h) {} -void end_draw(frontend *fe) {} -unsigned long random_bits(random_state *state, int bits) -{ assert(!"Shouldn't get randomness"); return 0; } -unsigned long random_upto(random_state *state, unsigned long limit) -{ assert(!"Shouldn't get randomness"); return 0; } -void shuffle(void *array, int nelts, int eltsize, random_state *rs) -{ assert(!"Shouldn't get randomness"); } - -void fatal(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "fatal error: "); - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fprintf(stderr, "\n"); - exit(1); -} - int main(int argc, char **argv) { game_params *p;