chiark / gitweb /
Substantial infrastructure upheaval. I've separated the drawing API
authorSimon Tatham <anakin@pobox.com>
Thu, 18 Aug 2005 17:50:14 +0000 (17:50 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 18 Aug 2005 17:50:14 +0000 (17:50 +0000)
as seen by the back ends from the one implemented by the front end,
and shoved a piece of middleware (drawing.c) in between to permit
interchange of multiple kinds of the latter. I've also added a
number of functions to the drawing API to permit printing as well as
on-screen drawing, and retired print.py in favour of integrated
printing done by means of that API.

The immediate visible change is that print.py is dead, and each
puzzle now does its own printing: where you would previously have
typed `print.py solo 2x3', you now type `solo --print 2x3' and it
should work in much the same way.

Advantages of the new mechanism available right now:
 - Map is now printable, because the new print function can make use
   of the output from the existing game ID decoder rather than me
   having to replicate all those fiddly algorithms in Python.
 - the new print functions can cope with non-initial game states,
   which means each puzzle supporting --print also supports
   --with-solutions.
 - there's also a --scale option permitting users to adjust the size
   of the printed puzzles.

Advantages which will be available at some point:
 - the new API should permit me to implement native printing
   mechanisms on Windows and OS X.

[originally from svn r6190]

34 files changed:
CHECKLST.txt
Recipe
blackbox.c
cube.c
devel.but
dominosa.c
drawing.c [new file with mode: 0644]
fifteen.c
flip.c
gtk.c
guess.c
lightup.c
map.c
midend.c
mines.c
misc.c
net.c
netslide.c
nullgame.c
osx.m
pattern.c
pegs.c
print.py [deleted file]
printing.c [new file with mode: 0644]
ps.c [new file with mode: 0644]
puzzles.h
rect.c
samegame.c
sixteen.c
slant.c
solo.c
twiddle.c
untangle.c
windows.c

index 9d6800937769df5d8ec835ce403971149da4b9e2..748104ed7f89ec40d0dbe684a9a1c40a0a5f6bb4 100644 (file)
@@ -6,8 +6,6 @@ Things to remember when adding a new puzzle
 
 Write the source file for the new puzzle (duhh).
 
-Write a section in print.py, if applicable.
-
 Add it to Recipe in _four_ places:
  - the `ALL' definition, to ensure it is compiled into the OS X binary
  - as a GTK build target
diff --git a/Recipe b/Recipe
index 81d4e9d8e02568563b76b70623adfd2efae79ce5..c65ee60886efba4b89503f40f6d86118c205f342 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -14,7 +14,7 @@
 !makefile osx Makefile.osx
 
 WINDOWS  = windows user32.lib gdi32.lib comctl32.lib comdlg32.lib
-COMMON   = midend misc malloc random version
+COMMON   = midend drawing misc malloc random version
 NET      = net tree234 dsf
 NETSLIDE = netslide tree234
 MINES    = mines tree234
@@ -28,26 +28,28 @@ ALL      = list NET NETSLIDE cube fifteen sixteen rect pattern solo twiddle
          + MINES samegame FLIP guess PEGS dominosa UNTANGLE blackbox SLANT
          + lightup MAP
 
-net      : [X] gtk COMMON NET
-netslide : [X] gtk COMMON NETSLIDE
-cube     : [X] gtk COMMON cube
-fifteen  : [X] gtk COMMON fifteen
-sixteen  : [X] gtk COMMON sixteen
-rect     : [X] gtk COMMON rect
-pattern  : [X] gtk COMMON pattern
-solo     : [X] gtk COMMON solo
-twiddle  : [X] gtk COMMON twiddle
-mines    : [X] gtk COMMON MINES
-samegame : [X] gtk COMMON samegame
-flip     : [X] gtk COMMON FLIP
-guess    : [X] gtk COMMON guess
-pegs     : [X] gtk COMMON PEGS
-dominosa : [X] gtk COMMON dominosa
-untangle : [X] gtk COMMON UNTANGLE
-blackbox : [X] gtk COMMON blackbox
-slant    : [X] gtk COMMON SLANT
-lightup  : [X] gtk COMMON lightup
-map      : [X] gtk COMMON MAP
+GTK      = gtk printing ps
+
+net      : [X] GTK COMMON NET
+netslide : [X] GTK COMMON NETSLIDE
+cube     : [X] GTK COMMON cube
+fifteen  : [X] GTK COMMON fifteen
+sixteen  : [X] GTK COMMON sixteen
+rect     : [X] GTK COMMON rect
+pattern  : [X] GTK COMMON pattern
+solo     : [X] GTK COMMON solo
+twiddle  : [X] GTK COMMON twiddle
+mines    : [X] GTK COMMON MINES
+samegame : [X] GTK COMMON samegame
+flip     : [X] GTK COMMON FLIP
+guess    : [X] GTK COMMON guess
+pegs     : [X] GTK COMMON PEGS
+dominosa : [X] GTK COMMON dominosa
+untangle : [X] GTK COMMON UNTANGLE
+blackbox : [X] GTK COMMON blackbox
+slant    : [X] GTK COMMON SLANT
+lightup  : [X] GTK COMMON lightup
+map      : [X] GTK COMMON MAP
 
 # Auxiliary command-line programs.
 solosolver :    [U] solo[STANDALONE_SOLVER] malloc
@@ -123,7 +125,7 @@ Puzzles.dmg: Puzzles
 # it in the Makefile because it will be worse than useless if it
 # ever fails to compile, so it's important that it should actually
 # be built on a regular basis.
-nullgame : [X] gtk COMMON nullgame
+nullgame : [X] GTK COMMON nullgame
 nullgame : [G] WINDOWS COMMON nullgame
 
 # Version management.
index 817004ff8cb52d84d65196cac2447b897de1ea6c..dda036c8689498db153895201de10e920968be22 100644 (file)
@@ -389,7 +389,7 @@ static int grid2range(game_state *state, int x, int y, int *rangeno)
     return 1;
 }
 
-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)
 {
     game_state *state = snew(game_state);
     int dlen = strlen(desc), i;
@@ -475,7 +475,7 @@ struct game_ui {
 
 static game_ui *new_ui(game_state *state)
 {
-    game_ui *ui = snew(struct game_ui);
+    game_ui *ui = snew(game_ui);
     ui->flash_laserno = LASER_EMPTY;
     ui->errors = 0;
     ui->newmove = FALSE;
@@ -1052,8 +1052,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = (params->h + 3) * tilesize;
 }
 
-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;
     ds->crad = (tilesize-1)/2;
@@ -1102,7 +1102,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -1117,13 +1117,13 @@ 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->grid);
     sfree(ds);
 }
 
-static void draw_arena_tile(frontend *fe, game_state *gs, game_drawstate *ds,
+static void draw_arena_tile(drawing *dr, game_state *gs, game_drawstate *ds,
                             int ax, int ay, int force, int isflash)
 {
     int gx = ax+1, gy = ay+1;
@@ -1136,8 +1136,8 @@ static void draw_arena_tile(frontend *fe, game_state *gs, game_drawstate *ds,
         bg = (gs->reveal ? COL_BACKGROUND :
               (gs_tile & BALL_LOCK) ? COL_LOCK : COL_COVER);
 
-        draw_rect(fe, dx, dy, TILE_SIZE, TILE_SIZE, bg);
-        draw_rect_outline(fe, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, bg);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
 
         if (gs->reveal) {
             /* Guessed balls are always black; if they're incorrect they'll
@@ -1159,7 +1159,7 @@ static void draw_arena_tile(frontend *fe, game_state *gs, game_drawstate *ds,
             }
         }
 
-        draw_circle(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-1,
+        draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-1,
                     bcol, bcol);
 
         if (gs->reveal &&
@@ -1178,7 +1178,7 @@ static void draw_arena_tile(frontend *fe, game_state *gs, game_drawstate *ds,
            coords[5] = y2-1;
            coords[6] = x2-1;
            coords[7] = y2+1;
-           draw_polygon(fe, coords, 4, COL_WRONG, COL_WRONG);
+           draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG);
            coords[0] = x2+1;
            coords[1] = y1+1;
            coords[2] = x2-1;
@@ -1187,14 +1187,14 @@ static void draw_arena_tile(frontend *fe, game_state *gs, game_drawstate *ds,
            coords[5] = y2-1;
            coords[6] = x1+1;
            coords[7] = y2+1;
-           draw_polygon(fe, coords, 4, COL_WRONG, COL_WRONG);
+           draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG);
         }
-        draw_update(fe, dx, dy, TILE_SIZE, TILE_SIZE);
+        draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
     }
     GRID(ds,gx,gy) = gs_tile;
 }
 
-static void draw_laser_tile(frontend *fe, game_state *gs, game_drawstate *ds,
+static void draw_laser_tile(drawing *dr, game_state *gs, game_drawstate *ds,
                             game_ui *ui, int lno, int force)
 {
     int gx, gy, dx, dy, unused;
@@ -1227,8 +1227,8 @@ static void draw_laser_tile(frontend *fe, game_state *gs, game_drawstate *ds,
     gs_tile |= wrong | omitted;
 
     if (gs_tile != ds_tile || force) {
-        draw_rect(fe, dx, dy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
-        draw_rect_outline(fe, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
 
         if (gs_tile &~ (LASER_WRONG | LASER_OMITTED)) {
             char str[10];
@@ -1240,25 +1240,25 @@ static void draw_laser_tile(frontend *fe, game_state *gs, game_drawstate *ds,
                 sprintf(str, "%d", laserval);
 
             if (wrong) {
-                draw_circle(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
                             ds->rrad,
                             COL_WRONG, COL_WRONG);
-                draw_circle(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
                             ds->rrad - TILE_SIZE/16,
                             COL_BACKGROUND, COL_WRONG);
             }
 
-            draw_text(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+            draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
                       FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
                       tcol, str);
         }
-        draw_update(fe, dx, dy, TILE_SIZE, TILE_SIZE);
+        draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
     }
     GRID(ds, gx, gy) = gs_tile;
 }
 
 
-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)
 {
@@ -1274,26 +1274,26 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         int x0 = TODRAW(0)-1, y0 = TODRAW(0)-1;
         int x1 = TODRAW(state->w+2), y1 = TODRAW(state->h+2);
 
-        draw_rect(fe, 0, 0,
+        draw_rect(dr, 0, 0,
                   TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3),
                   COL_BACKGROUND);
 
         /* clockwise around the outline starting at pt behind (1,1). */
-        draw_line(fe, x0+ts, y0+ts, x0+ts, y0,    COL_HIGHLIGHT);
-        draw_line(fe, x0+ts, y0,    x1-ts, y0,    COL_HIGHLIGHT);
-        draw_line(fe, x1-ts, y0,    x1-ts, y0+ts, COL_LOWLIGHT);
-        draw_line(fe, x1-ts, y0+ts, x1,    y0+ts, COL_HIGHLIGHT);
-        draw_line(fe, x1,    y0+ts, x1,    y1-ts, COL_LOWLIGHT);
-        draw_line(fe, x1,    y1-ts, x1-ts, y1-ts, COL_LOWLIGHT);
-        draw_line(fe, x1-ts, y1-ts, x1-ts, y1,    COL_LOWLIGHT);
-        draw_line(fe, x1-ts, y1,    x0+ts, y1,    COL_LOWLIGHT);
-        draw_line(fe, x0+ts, y1,    x0+ts, y1-ts, COL_HIGHLIGHT);
-        draw_line(fe, x0+ts, y1-ts, x0,    y1-ts, COL_LOWLIGHT);
-        draw_line(fe, x0,    y1-ts, x0,    y0+ts, COL_HIGHLIGHT);
-        draw_line(fe, x0,    y0+ts, x0+ts, y0+ts, COL_HIGHLIGHT);
+        draw_line(dr, x0+ts, y0+ts, x0+ts, y0,    COL_HIGHLIGHT);
+        draw_line(dr, x0+ts, y0,    x1-ts, y0,    COL_HIGHLIGHT);
+        draw_line(dr, x1-ts, y0,    x1-ts, y0+ts, COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y0+ts, x1,    y0+ts, COL_HIGHLIGHT);
+        draw_line(dr, x1,    y0+ts, x1,    y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x1,    y1-ts, x1-ts, y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y1-ts, x1-ts, y1,    COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y1,    x0+ts, y1,    COL_LOWLIGHT);
+        draw_line(dr, x0+ts, y1,    x0+ts, y1-ts, COL_HIGHLIGHT);
+        draw_line(dr, x0+ts, y1-ts, x0,    y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x0,    y1-ts, x0,    y0+ts, COL_HIGHLIGHT);
+        draw_line(dr, x0,    y0+ts, x0+ts, y0+ts, COL_HIGHLIGHT);
         /* phew... */
 
-        draw_update(fe, 0, 0,
+        draw_update(dr, 0, 0,
                     TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3));
         force = 1;
         ds->started = 1;
@@ -1304,26 +1304,26 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     /* draw the arena */
     for (x = 0; x < state->w; x++) {
         for (y = 0; y < state->h; y++) {
-            draw_arena_tile(fe, state, ds, x, y, force, isflash);
+            draw_arena_tile(dr, state, ds, x, y, force, isflash);
         }
     }
 
     /* draw the lasers */
     for (i = 0; i < 2*(state->w+state->h); i++) {
-        draw_laser_tile(fe, state, ds, ui, i, force);
+        draw_laser_tile(dr, state, ds, ui, i, force);
     }
 
     /* draw the 'finish' button */
     if (CAN_REVEAL(state)) {
-        clip(fe, TODRAW(0), TODRAW(0), TILE_SIZE-1, TILE_SIZE-1);
-        draw_circle(fe, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad,
+        clip(dr, TODRAW(0), TODRAW(0), TILE_SIZE-1, TILE_SIZE-1);
+        draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad,
                     COL_BUTTON, COL_BALL);
-       unclip(fe);
+       unclip(dr);
     } else {
-        draw_rect(fe, TODRAW(0), TODRAW(0),
+        draw_rect(dr, TODRAW(0), TODRAW(0),
                  TILE_SIZE-1, TILE_SIZE-1, COL_BACKGROUND);
     }
-    draw_update(fe, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE);
+    draw_update(dr, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE);
     ds->reveal = state->reveal;
     ds->flash_laserno = ui->flash_laserno;
     ds->isflash = isflash;
@@ -1359,7 +1359,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            sprintf(buf + strlen(buf), " (%d error%s)",
                    ui->errors, ui->errors > 1 ? "s" : "");
        }
-        status_bar(fe, buf);
+        status_bar(dr, buf);
     }
 }
 
@@ -1388,6 +1388,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame blackbox
 #endif
@@ -1423,6 +1431,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/cube.c b/cube.c
index e271ce3ca8617ac99c9cf73d3c7f8382ba0ef91a..59be9600f31189ed604a7ff800898824b6735ea1 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -863,7 +863,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)
 {
     game_state *state = snew(game_state);
     int area;
@@ -1466,8 +1466,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = YSIZE(tilesize, bb, solids[params->solid]);
 }
 
-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)
 {
     struct bbox bb = find_bbox(params);
 
@@ -1494,7 +1494,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -1503,12 +1503,12 @@ 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);
 }
 
-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)
 {
@@ -1521,7 +1521,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     game_state *newstate;
     int square;
 
-    draw_rect(fe, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
+    draw_rect(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
              YSIZE(GRID_SCALE, bb, state->solid), COL_BACKGROUND);
 
     if (dir < 0) {
@@ -1565,7 +1565,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                             + ds->oy);
         }
 
-        draw_polygon(fe, coords, state->squares[i].npoints,
+        draw_polygon(dr, coords, state->squares[i].npoints,
                      state->squares[i].blue ? COL_BLUE : COL_BACKGROUND,
                     COL_BORDER);
     }
@@ -1647,13 +1647,13 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 continue;
         }
 
-        draw_polygon(fe, coords, poly->order,
+        draw_polygon(dr, coords, poly->order,
                      state->facecolours[i] ? COL_BLUE : COL_BACKGROUND,
                     COL_BORDER);
     }
     sfree(poly);
 
-    draw_update(fe, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
+    draw_update(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
                YSIZE(GRID_SCALE, bb, state->solid));
 
     /*
@@ -1666,7 +1666,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                (state->completed ? "COMPLETED! " : ""),
                (state->completed ? state->completed : state->movecount));
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 }
 
@@ -1692,6 +1692,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame cube
 #endif
@@ -1727,6 +1735,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index a717fc271cc784691ff4b49360376f9c56284e9a..62ec6597a8e38f67de9c787e8b379f2e3b74bdea 100644 (file)
--- a/devel.but
+++ b/devel.but
@@ -32,7 +32,7 @@ Puzzle Collection (henceforth referred to simply as \q{Puzzles}),
 for use by anyone attempting to implement a new puzzle or port to a
 new platform.
 
-This guide is believed correct as of r6140. Hopefully it will be
+This guide is believed correct as of r6190. Hopefully it will be
 updated along with the code in future, but if not, I've at least
 left this version number in here so you can figure out what's
 changed by tracking commit comments from there onwards.
@@ -718,7 +718,7 @@ non-dynamically-allocated C string containing an error message.
 
 \S{backend-new-game} \cw{new_game()}
 
-\c game_state *(*new_game)(midend_data *me, game_params *params,
+\c game_state *(*new_game)(midend *me, game_params *params,
 \c                         char *desc);
 
 This function takes a game description as input, together with its
@@ -1024,7 +1024,7 @@ drawing.
 
 \S{backend-new-drawstate} \cw{new_drawstate()}
 
-\c game_drawstate *(*new_drawstate)(game_state *state);
+\c game_drawstate *(*new_drawstate)(drawing *dr, game_state *state);
 
 This function allocates and returns a new \c{game_drawstate}
 structure for drawing a particular puzzle. It is passed a pointer to
@@ -1037,13 +1037,22 @@ requests a forced redraw. For games like Pattern, in which initial
 game states are much simpler than general ones, this might be
 important to keep in mind.
 
+The parameter \c{dr} is a drawing object (see \k{drawing}) which the
+function might need to use to allocate blitters. (However, this
+isn't recommended; it's usually more sensible to wait to allocate a
+blitter until \cw{set_size()} is called, because that way you can
+tailor it to the scale at which the puzzle is being drawn.)
+
 \S{backend-free-drawstate} \cw{free_drawstate()}
 
-\c void (*free_drawstate)(game_drawstate *ds);
+\c void (*free_drawstate)(drawing *dr, game_drawstate *ds);
 
 This function frees a \c{game_drawstate} structure, and any
 subsidiary allocations contained within it.
 
+The parameter \c{dr} is a drawing object (see \k{drawing}), which
+might be required if you are freeing a blitter.
+
 \S{backend-preferred-tilesize} \c{preferred_tilesize}
 
 \c int preferred_tilesize;
@@ -1075,8 +1084,8 @@ at that tile size.
 
 \S{backend-set-size} \cw{set_size()}
 
-\c void (*set_size)(game_drawstate *ds, game_params *params,
-\c                  int tilesize);
+\c void (*set_size)(drawing *dr, game_drawstate *ds,
+\c                  game_params *params, int tilesize);
 
 This function is responsible for setting up a \c{game_drawstate} to
 draw at a given tile size. Typically this will simply involve
@@ -1085,6 +1094,9 @@ field inside the draw state; for some more complex games it might
 also involve setting up other dimension fields, or possibly
 allocating a blitter (see \k{drawing-blitter}).
 
+The parameter \c{dr} is a drawing object (see \k{drawing}), which is
+required if a blitter needs to be allocated.
+
 \S{backend-colours} \cw{colours()}
 
 \c float *(*colours)(frontend *fe, game_state *state, int *ncolours);
@@ -1119,6 +1131,10 @@ end's default colour as their background, apart from a few which
 depend on drawing relief highlights so they adjust the background
 colour if it's too light for highlights to show up against it.
 
+Note that the colours returned from this function are for
+\e{drawing}, not for printing. Printing has an entirely different
+colour allocation policy.
+
 \S{backend-anim-length} \cw{anim_length()}
 
 \c float (*anim_length)(game_state *oldstate, game_state *newstate,
@@ -1212,7 +1228,7 @@ flag in the \c{game_ui} to indicate which flash type is required.)
 
 \S{backend-redraw} \cw{redraw()}
 
-\c void (*redraw)(frontend *fe, game_drawstate *ds,
+\c void (*redraw)(drawing *dr, game_drawstate *ds,
 \c                game_state *oldstate, game_state *newstate, int dir,
 \c                game_ui *ui, float anim_time, float flash_time);
 
@@ -1220,9 +1236,9 @@ This function is responsible for actually drawing the contents of
 the game window, and for redrawing every time the game state or the
 \c{game_ui} changes.
 
-The parameter \c{fe} is a front end handle which may be passed to
-the drawing API functions (see \k{drawing} for documentation of the
-drawing API). This function may not save \c{fe} and use it
+The parameter \c{dr} is a drawing object which may be passed to the
+drawing API functions (see \k{drawing} for documentation of the
+drawing API). This function may not save \c{dr} and use it
 elsewhere; it must only use it for calling back to the drawing API
 functions within its own lifetime.
 
@@ -1250,6 +1266,105 @@ never subsequently altered, it is often simplest to arrange this by
 having a special \q{first time} flag in the draw state, and
 resetting it after the first redraw.
 
+When this function (or any subfunction) calls the drawing API, it is
+expected to pass colour indices which were previously defined by the
+\cw{colours()} function.
+
+\H{backend-printing} Printing functions
+
+This section discusses the back end functions that deal with
+printing puzzles out on paper.
+
+\S{backend-can-print} \c{can_print}
+
+\c int can_print;
+
+This flag is set to \cw{TRUE} if the puzzle is capable of printing
+itself on paper. (This makes sense for some puzzles, such as Solo,
+which can be filled in with a pencil. Other puzzles, such as
+Twiddle, inherently involve moving things around and so would not
+make sense to print.)
+
+If this flag is \cw{FALSE}, then the functions \cw{print_size()}
+and \cw{print()} will never be called.
+
+\S{backend-can-print-in-colour} \c{can_print_in_colour}
+
+\c int can_print_in_colour;
+
+This flag is set to \cw{TRUE} if the puzzle is capable of printing
+itself differently when colour is available. For example, Map can
+actually print coloured regions in different \e{colours} rather than
+resorting to cross-hatching.
+
+If the \c{can_print} flag is \cw{FALSE}, then this flag will be
+ignored.
+
+\S{backend-print-size} \cw{print_size()}
+
+\c void (*print_size)(game_params *params, float *x, float *y);
+
+This function is passed a \c{game_params} structure and a tile size.
+It returns, in \c{*x} and \c{*y}, the preferred size in
+\e{millimetres} of that puzzle if it were to be printed out on paper.
+
+If the \c{can_print} flag is \cw{FALSE}, this function will never be
+called.
+
+\S{backend-print} \cw{print()}
+
+\c void (*print)(drawing *dr, game_state *state, int tilesize);
+
+This function is called when a puzzle is to be printed out on paper.
+It should use the drawing API functions (see \k{drawing}) to print
+itself.
+
+This function is separate from \cw{redraw()} because it is often
+very different:
+
+\b The printing function may not depend on pixel accuracy, since
+printer resolution is variable. Draw as if your canvas had infinite
+resolution.
+
+\b The printing function sometimes needs to display things in a
+completely different style. Net, for example, is very different as
+an on-screen puzzle and as a printed one.
+
+\b The printing function is often much simpler since it has no need
+to deal with repeated partial redraws.
+
+However, there's no reason the printing and redraw functions can't
+share some code if they want to.
+
+When this function (or any subfunction) calls the drawing API, the
+colour indices it passes should be colours which have been allocated
+by the \cw{print_*_colour()} functions within this execution of
+\cw{print()}. This is very different from the fixed small number of
+colours used in \cw{redraw()}, because printers do not have a
+limitation on the total number of colours that may be used. Some
+puzzles' printing functions might wish to allocate only one \q{ink}
+colour and use it for all drawing; others might wish to allocate
+\e{more} colours than are used on screen.
+
+One possible colour policy worth mentioning specifically is that a
+puzzle's printing function might want to allocate the \e{same}
+colour indices as are used by the redraw function, so that code
+shared between drawing and printing does not have to keep switching
+its colour indices. In order to do this, the simplest thing is to
+make use of the fact that colour indices returned from
+\cw{print_*_colour()} are guaranteed to be in increasing order from
+zero. So if you have declared an \c{enum} defining three colours
+\cw{COL_BACKGROUND}, \cw{COL_THIS} and \cw{COL_THAT}, you might then
+write
+
+\c int c;
+\c c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+\c c = print_mono_colour(dr, 0); assert(c == COL_THIS);
+\c c = print_mono_colour(dr, 0); assert(c == COL_THAT);
+
+If the \c{can_print} flag is \cw{FALSE}, this function will never be
+called.
+
 \H{backend-misc} Miscellaneous
 
 \S{backend-can-format-as-text} \c{can_format_as_text}
@@ -1378,7 +1493,7 @@ in getting different random numbers.
 In response to a move, a back end is (reluctantly) permitted to call
 \cw{midend_supersede_game_desc()}:
 
-\c void midend_supersede_game_desc(midend_data *me,
+\c void midend_supersede_game_desc(midend *me,
 \c                                 char *desc, char *privdesc);
 
 When the user selects \q{New Game}, the mid-end calls
@@ -1412,31 +1527,63 @@ should use it if they're not Mines; if you think you need to use it,
 think again repeatedly in the hope of finding a better way to do
 whatever it was you needed to do.
 
-\C{drawing} The drawing API: front-end functions called from the
-back end
+\C{drawing} The drawing API
 
 The back end function \cw{redraw()} (\k{backend-redraw}) is required
-to draw the puzzle's graphics on the window's drawing area. To do
-this portably, it is provided with a drawing API allowing it to talk
-directly to the front end. In this chapter I document that API, both
-for the benefit of back end authors trying to use it and for front
-end authors trying to implement it.
-
-All of the drawing functions take a pointer to a \c{frontend}
-structure, which is passed in to \cw{redraw()}.
-
-The puzzle window is indexed by pixel coordinates, with the top left
-pixel defined as \cw{(0,0)} and the bottom right pixel
-\cw{(w-1,h-1)}, where \c{w} and \c{h} are the width and height
+to draw the puzzle's graphics on the window's drawing area, or on
+paper if the puzzle is printable. To do this portably, it is
+provided with a drawing API allowing it to talk directly to the
+front end. In this chapter I document that API, both for the benefit
+of back end authors trying to use it and for front end authors
+trying to implement it.
+
+The drawing API as seen by the back end is a collection of global
+functions, each of which takes a pointer to a \c{drawing} structure
+(a \q{drawing object}). These objects are supplied as parameters to
+the back end's \cw{redraw()} and \cw{print()} functions.
+
+In fact these global functions are not implemented directly by the
+front end; instead, they are implemented centrally in \c{drawing.c}
+and form a small piece of middleware. The drawing API as supplied by
+the front end is a structure containing a set of function pointers,
+plus a \cq{void *} handle which is passed to each of those
+functions. This enables a single front end to switch between
+multiple implementations of the drawing API if necessary. For
+example, the Windows API supplies a printing mechanism integrated
+into the same GDI which deals with drawing in windows, and therefore
+it is likely (although as yet unimplemented in Puzzles) that the
+same API implementation can handle both drawing and printing; but on
+Unix, the most common way for applications to print is by producing
+PostScript output directly, and although it would be \e{possible} to
+write a single (say) \cw{draw_rect()} function which checked a
+global flag to decide whether to do GTK drawing operations or output
+PostScript to a file, it's much nicer to have two separate functions
+and switch between them as appropriate.
+
+When drawing, the puzzle window is indexed by pixel coordinates,
+with the top left pixel defined as \cw{(0,0)} and the bottom right
+pixel \cw{(w-1,h-1)}, where \c{w} and \c{h} are the width and height
 values returned by the back end function \cw{compute_size()}
 (\k{backend-compute-size}).
 
-\e{Puzzles may assume that the surface they draw on is persistent}.
-It is the responsibility of every front end to preserve the puzzle's
-window contents in the face of GUI window expose issues and similar.
-It is not permissible to request the back end redraw any part of a
-window that it has already drawn, unless something has actually
-changed as a result of making moves in the puzzle.
+When printing, the puzzle's print area is indexed in exactly the
+same way (with an arbitrary tile size provided by the printing
+module \c{printing.c}), to facilitate sharing of code between the
+drawing and printing routines. However, when printing, puzzles may
+no longer assume that the coordinate unit has any relationship to a
+pixel; the printer's actual resolution might very well not even be
+known at print time, so the coordinate unit might be smaller or
+larger than a pixel. Puzzles' print functions should restrict
+themselves to drawing geometric shapes rather than fiddly pixel
+manipulation.
+
+\e{Puzzles' redraw functions may assume that the surface they draw
+on is persistent}. It is the responsibility of every front end to
+preserve the puzzle's window contents in the face of GUI window
+expose issues and similar. It is not permissible to request the back
+end redraw any part of a window that it has already drawn, unless
+something has actually changed as a result of making moves in the
+puzzle.
 
 Most front ends accomplish this by having the drawing routines draw
 on a stored bitmap rather than directly on the window, and copying
@@ -1446,9 +1593,18 @@ end does any drawing it informs the front end of which parts of the
 window it has accessed, and hence which parts need repainting. This
 is done by calling \cw{draw_update()} (\k{drawing-draw-update}).
 
-\H{drawing-draw-rect} \cw{draw_rect()}
+In the following sections I first discuss the drawing API as seen by
+the back end, and then the \e{almost} identical function-pointer
+form seen by the front end.
+
+\H{drawing-backend} Drawing API as seen by the back end
 
-\c void draw_rect(frontend *fe, int x, int y, int w, int h,
+This section documents the back-end drawing API, in the form of
+functions which take a \c{drawing} object as an argument.
+
+\S{drawing-draw-rect} \cw{draw_rect()}
+
+\c void draw_rect(drawing *dr, int x, int y, int w, int h,
 \c                int colour);
 
 Draws a filled rectangle in the puzzle window.
@@ -1470,9 +1626,11 @@ Unlike many of the other drawing functions, this function is
 guaranteed to be pixel-perfect: the rectangle will be sharply
 defined and not anti-aliased or anything like that.
 
-\H{drawing-draw-rect-outline} \cw{draw_rect_outline()}
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-rect-outline} \cw{draw_rect_outline()}
 
-\c void draw_rect_outline(frontend *fe, int x, int y, int w, int h,
+\c void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
 \c                        int colour);
 
 Draws an outline rectangle in the puzzle window.
@@ -1489,11 +1647,13 @@ the back end function \cw{colours()} (\k{backend-colours}).
 From a back end perspective, this function may be considered to be
 part of the drawing API. However, front ends are not required to
 implement it, since it is actually implemented centrally (in
-\cw{misc.c}) as a wrapper on four calls to \cw{draw_line()}.
+\cw{misc.c}) as a wrapper on \cw{draw_polygon()}.
 
-\H{drawing-draw-line} \cw{draw_line()}
+This function may be used for both drawing and printing.
 
-\c void draw_line(frontend *fe, int x1, int y1, int x2, int y2,
+\S{drawing-draw-line} \cw{draw_line()}
+
+\c void draw_line(drawing *dr, int x1, int y1, int x2, int y2,
 \c                int colour);
 
 Draws a straight line in the puzzle window.
@@ -1510,9 +1670,11 @@ Therefore, do not assume that you can erase a line by drawing the
 same line over it in the background colour; anti-aliasing might
 lead to perceptible ghost artefacts around the vanished line.
 
-\H{drawing-draw-polygon} \cw{draw_polygon()}
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-polygon} \cw{draw_polygon()}
 
-\c void draw_polygon(frontend *fe, int *coords, int npoints,
+\c void draw_polygon(drawing *dr, int *coords, int npoints,
 \c                   int fillcolour, int outlinecolour);
 
 Draws an outlined or filled polygon in the puzzle window.
@@ -1545,9 +1707,11 @@ result of this; if you really need it not to do this to avoid
 interfering with other delicate graphics, you should probably use
 \cw{clip()} (\k{drawing-clip}).
 
-\H{drawing-draw-circle} \cw{draw_circle()}
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-circle} \cw{draw_circle()}
 
-\c void draw_circle(frontend *fe, int cx, int cy, int radius,
+\c void draw_circle(drawing *dr, int cx, int cy, int radius,
 \c                  int fillcolour, int outlinecolour);
 
 Draws an outlined or filled circle in the puzzle window.
@@ -1581,9 +1745,11 @@ result of this; if you really need it not to do this to avoid
 interfering with other delicate graphics, you should probably use
 \cw{clip()} (\k{drawing-clip}).
 
-\H{drawing-draw-text} \cw{draw_text()}
+This function may be used for both drawing and printing.
 
-\c void draw_text(frontend *fe, int x, int y, int fonttype,
+\S{drawing-draw-text} \cw{draw_text()}
+
+\c void draw_text(drawing *dr, int x, int y, int fonttype,
 \c                int fontsize, int align, int colour, char *text);
 
 Draws text in the puzzle window.
@@ -1632,9 +1798,11 @@ internal dimension such as the cap-height.
 \c{colour} is an integer index into the colours array returned by
 the back end function \cw{colours()} (\k{backend-colours}).
 
-\H{drawing-clip} \cw{clip()}
+This function may be used for both drawing and printing.
+
+\S{drawing-clip} \cw{clip()}
 
-\c void clip(frontend *fe, int x, int y, int w, int h);
+\c void clip(drawing *dr, int x, int y, int w, int h);
 
 Establishes a clipping rectangle in the puzzle window.
 
@@ -1654,17 +1822,21 @@ automatically cleared up by the front end if it's left lying around;
 that might work on current front ends, but shouldn't be relied upon.
 Always explicitly call \cw{unclip()}.
 
-\H{drawing-unclip} \cw{unclip()}
+This function may be used for both drawing and printing.
 
-\c void unclip(frontend *fe);
+\S{drawing-unclip} \cw{unclip()}
+
+\c void unclip(drawing *dr);
 
 Reverts the effect of a previous call to \cw{clip()}. After this
 call, all drawing operations will be able to affect the entire
 puzzle window again.
 
-\H{drawing-draw-update} \cw{draw_update()}
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-update} \cw{draw_update()}
 
-\c void draw_update(frontend *fe, int x, int y, int w, int h);
+\c void draw_update(drawing *dr, int x, int y, int w, int h);
 
 Informs the front end that a rectangular portion of the puzzle
 window has been drawn on and needs to be updated.
@@ -1682,9 +1854,14 @@ not become immediately visible, and may then appear at an
 unpredictable subsequent time such as the next time the window is
 covered and re-exposed.
 
-\H{drawing-status-bar} \cw{status_bar()}
+This function is only important when drawing. It may be called when
+printing as well, but doing so is not compulsory, and has no effect.
+(So if you have a shared piece of code between the drawing and
+printing routines, that code may safely call \cw{draw_update()}.)
 
-\c void status_bar(frontend *fe, char *text);
+\S{drawing-status-bar} \cw{status_bar()}
+
+\c void status_bar(drawing *dr, char *text);
 
 Sets the text in the game's status bar to \c{text}. The text is copied
 from the supplied buffer, so the caller is free to deallocate or
@@ -1695,21 +1872,10 @@ with the drawing API the property that it may only be called from
 within the back end redraw function, so this is as good a place as
 any to document it.)
 
-Front ends implementing this function should not use the provided
-text directly; they should call \cw{midend_rewrite_statusbar()}
-(\k{midend-rewrite-statusbar}) to process it first.
-
-In a game which has a timer, this function is likely to be called
-every time the timer goes off, i.e. many times a second. It is
-therefore likely to be common that this function is called with
-precisely the same text as the last time it was called. Front ends
-may well wish to detect this common case and avoid bothering to do
-anything. If they do, however, they \e{must} perform this check on
-the value \e{returned} from \cw{midend_rewrite_statusbar()}, rather
-than the value passed in to it (because the mid-end will frequently
-update the status-bar timer without the back end's intervention).
+This function is for drawing only; it must never be called during
+printing.
 
-\H{drawing-blitter} Blitter functions
+\S{drawing-blitter} Blitter functions
 
 This section describes a group of related functions which save and
 restore a section of the puzzle window. This is most commonly used
@@ -1723,9 +1889,12 @@ restore the background.
 The front end defines an opaque type called a \c{blitter}, which is
 capable of storing a rectangular area of a specified size.
 
-\S{drawing-blitter-new} \cw{blitter_new()}
+Blitter functions are for drawing only; they must never be called
+during printing.
+
+\S2{drawing-blitter-new} \cw{blitter_new()}
 
-\c blitter *blitter_new(int w, int h);
+\c blitter *blitter_new(drawing *dr, int w, int h);
 
 Creates a new blitter object which stores a rectangle of size \c{w}
 by \c{h} pixels. Returns a pointer to the blitter object.
@@ -1735,9 +1904,9 @@ time to create them is in the \cw{set_size()} function
 (\k{backend-set-size}), since it is at this point that you first
 know how big a rectangle they will need to save.
 
-\S{drawing-blitter-free} \cw{blitter_free()}
+\S2{drawing-blitter-free} \cw{blitter_free()}
 
-\c void blitter_free(blitter *bl);
+\c void blitter_free(drawing *dr, blitter *bl);
 
 Disposes of a blitter object. Best called in \cw{free_drawstate()}.
 (However, check that the blitter object is not \cw{NULL} before
@@ -1745,9 +1914,9 @@ attempting to free it; it is possible that a draw state might be
 created and freed without ever having \cw{set_size()} called on it
 in between.)
 
-\S{drawing-blitter-save} \cw{blitter_save()}
+\S2{drawing-blitter-save} \cw{blitter_save()}
 
-\c void blitter_save(frontend *fe, blitter *bl, int x, int y);
+\c void blitter_save(drawing *dr, blitter *bl, int x, int y);
 
 This is a true drawing API function, in that it may only be called
 from within the game redraw routine. It saves a rectangular portion
@@ -1762,9 +1931,9 @@ and \c{y} are out of range. (The right thing probably means saving
 whatever part of the blitter rectangle overlaps with the visible
 area of the puzzle window.)
 
-\S{drawing-blitter-load} \cw{blitter_load()}
+\S2{drawing-blitter-load} \cw{blitter_load()}
 
-\c void blitter_load(frontend *fe, blitter *bl, int x, int y);
+\c void blitter_load(drawing *dr, blitter *bl, int x, int y);
 
 This is a true drawing API function, in that it may only be called
 from within the game redraw routine. It restores a rectangular
@@ -1792,42 +1961,438 @@ saved bitmap which were not visible at save time are undefined. If
 the blitter is restored to a different position so as to make those
 parts visible, the effect on the drawing area is undefined.
 
-\H{drawing-midend} Additional functions only called by the mid-end
+\S{print-mono-colour} \cw{print_mono_colour()}
+
+\c int print_mono_colour(drawing *dr, int grey);
+
+This function allocates a colour index for a simple monochrome
+colour during printing.
+
+\c{grey} must be 0 or 1. If \c{grey} is 0, the colour returned is
+black; if \c{grey} is 1, the colour is white.
+
+\S{print-grey-colour} \cw{print_grey_colour()}
+
+\c int print_grey_colour(drawing *dr, int hatch, float grey);
+
+This function allocates a colour index for a grey-scale colour
+during printing.
+
+\c{grey} may be any number between 0 (black) and 1 (white); for
+example, 0.5 indicates a medium grey.
+
+If printing in black and white only, the \c{grey} value will not be
+used; instead, regions shaded in this colour will be hatched with
+parallel lines. The \c{hatch} parameter defines what type of
+hatching should be used in place of this colour:
+
+\dt \cw{HATCH_SOLID}
+
+\dd In black and white, this colour will be replaced by solid black.
+
+\dt \cw{HATCH_CLEAR}
+
+\dd In black and white, this colour will be replaced by solid white.
+
+\dt \cw{HATCH_SLASH}
+
+\dd This colour will be hatched by lines slanting to the right at 45
+degrees. 
+
+\dt \cw{HATCH_BACKSLASH}
+
+\dd This colour will be hatched by lines slanting to the left at 45
+degrees.
+
+\dt \cw{HATCH_HORIZ}
+
+\dd This colour will be hatched by horizontal lines.
+
+\dt \cw{HATCH_VERT}
+
+\dd This colour will be hatched by vertical lines.
+
+\dt \cw{HATCH_PLUS}
+
+\dd This colour will be hatched by criss-crossing horizontal and
+vertical lines.
+
+\dt \cw{HATCH_X}
+
+\dd This colour will be hatched by criss-crossing diagonal lines.
+
+Colours defined to use hatching may not be used for drawing lines;
+they may only be used for filling areas. That is, they may be used
+as the \c{fillcolour} parameter to \cw{draw_circle()} and
+\cw{draw_polygon()}, and as the colour parameter to
+\cw{draw_rect()}, but may not be used as the \c{outlinecolour}
+parameter to \cw{draw_circle()} or \cw{draw_polygon()}, or with
+\cw{draw_line()}.
+
+\S{print-rgb-colour} \cw{print_rgb_colour()}
+
+\c int print_rgb_colour(drawing *dr, int hatch,
+\c                      float r, float g, float b);
+
+This function allocates a colour index for a fully specified RGB
+colour during printing.
+
+\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, the \c{grey} value will not be
+used; instead, regions shaded in this colour will be hatched with
+parallel lines. The \c{hatch} parameter defines what type of
+hatching should be used in place of this colour; see
+\k{print-grey-colour} for its definition.
+
+\S{print-line-width} \cw{print_line_width()}
+
+\c void print_line_width(drawing *dr, int width);
+
+This function is called to set the thickness of lines drawn during
+printing. It is meaningless in drawing: all lines drawn by
+\cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} are one
+pixel in thickness. However, in printing there is no clear
+definition of a pixel and so line widths must be explicitly
+specified.
+
+The line width is specified in the usual coordinate system. Note,
+however, that it is a hint only: the central printing system may
+choose to vary line thicknesses at user request or due to printer
+capabilities.
+
+\H{drawing-frontend} The drawing API as implemented by the front end
+
+This section describes the drawing API in the function-pointer form
+in which it is implemented by a front end.
+
+(It isn't only platform-specific front ends which implement this
+API; the platform-independent module \c{ps.c} also provides an
+implementation of it which outputs PostScript. Thus, any platform
+which wants to do PS printing can do so with minimum fuss.)
+
+The following entries all describe function pointer fields in a
+structure called \c{drawing_api}. Each of the functions takes a
+\cq{void *} context pointer, which it should internally cast back to
+a more useful type. Thus, a drawing \e{object} (\c{drawing *)}
+suitable for passing to the back end redraw or printing functions
+is constructed by passing a \c{drawing_api} and a \cq{void *} to the
+function \cw{drawing_init()} (see \k{drawing-init}).
+
+\S{drawingapi-draw-text} \cw{draw_text()}
+
+\c void (*draw_text)(void *handle, int x, int y, int fonttype,
+\c                   int fontsize, int align, int colour, char *text);
+
+This function behaves exactly like the back end \cw{draw_text()}
+function; see \k{drawing-draw-text}.
+
+\S{drawingapi-draw-rect} \cw{draw_rect()}
+
+\c void (*draw_rect)(void *handle, int x, int y, int w, int h,
+\c                   int colour);
+
+This function behaves exactly like the back end \cw{draw_rect()}
+function; see \k{drawing-draw-rect}.
+
+\S{drawingapi-draw-line} \cw{draw_line()}
+
+\c void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
+\c                   int colour);
+
+This function behaves exactly like the back end \cw{draw_line()}
+function; see \k{drawing-draw-line}.
+
+\S{drawingapi-draw-polygon} \cw{draw_polygon()}
+
+\c void (*draw_polygon)(void *handle, int *coords, int npoints,
+\c                      int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end \cw{draw_polygon()}
+function; see \k{drawing-draw-polygon}.
+
+\S{drawingapi-draw-circle} \cw{draw_circle()}
+
+\c void (*draw_circle)(void *handle, int cx, int cy, int radius,
+\c                     int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end \cw{draw_circle()}
+function; see \k{drawing-draw-circle}.
+
+\S{drawingapi-draw-update} \cw{draw_update()}
+
+\c void (*draw_update)(void *handle, int x, int y, int w, int h);
+
+This function behaves exactly like the back end \cw{draw_text()}
+function; see \k{drawing-draw-text}.
+
+An implementation of this API which only supports printing is
+permitted to define this function pointer to be \cw{NULL} rather
+than bothering to define an empty function. The middleware in
+\cw{drawing.c} will notice and avoid calling it.
+
+\S{drawingapi-clip} \cw{clip()}
+
+\c void (*clip)(void *handle, int x, int y, int w, int h);
 
-The two functions documented in this section are part of the drawing
-API as seen by a front end, but are not needed by the back end. The
-mid-end calls these functions before and after calling the back end
-redraw function.
+This function behaves exactly like the back end \cw{clip()}
+function; see \k{drawing-clip}.
 
-\S{drawing-start-draw} \cw{start_draw()}
+\S{drawingapi-unclip} \cw{unclip()}
 
-\c void start_draw(frontend *fe);
+\c void (*unclip)(void *handle);
 
-This function is called before any drawing takes place. It allows
-the front end to initialise any temporary data required to draw
-with, such as device contexts.
+This function behaves exactly like the back end \cw{unclip()}
+function; see \k{drawing-unclip}.
 
-\S{drawing-end-draw} \cw{end_draw()}
+\S{drawingapi-start-draw} \cw{start_draw()}
 
-\c void end_draw(frontend *fe);
+\c void (*start_draw)(void *handle);
+
+This function is called at the start of drawing. It allows the front
+end to initialise any temporary data required to draw with, such as
+device contexts.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-end-draw} \cw{end_draw()}
+
+\c void (*end_draw)(void *handle);
 
 This function is called at the end of drawing. It allows the front
 end to do cleanup tasks such as deallocating device contexts and
 scheduling appropriate GUI redraw events.
 
-\H{frontend-default-colour} \cw{frontend_default_colour()}
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
 
-\c void frontend_default_colour(frontend *fe, float *output);
+\S{drawingapi-status-bar} \cw{status_bar()}
 
-This function expects to be passed a pointer to an array of three
-\cw{float}s. It returns the platform's local preferred background
-colour in those three floats, as red, green and blue values (in that
-order) ranging from \cw{0.0} to \cw{1.0}.
+\c void (*status_bar)(void *handle, char *text);
 
-This function should only ever be called by the back end function
-\cw{colours()} (\k{backend-colours}). (Thus, it isn't a drawing API
-function as such, but it's a front end function of interest to
-puzzle implementors so it's probably best in this section.)
+This function behaves exactly like the back end \cw{status_bar()}
+function; see \k{drawing-status-bar}.
+
+Front ends implementing this function should not use the provided
+text directly; they should call \cw{midend_rewrite_statusbar()}
+(\k{midend-rewrite-statusbar}) to process it first.
+
+In a game which has a timer, this function is likely to be called
+every time the timer goes off, i.e. many times a second. It is
+therefore likely to be common that this function is called with
+precisely the same text as the last time it was called. Front ends
+may well wish to detect this common case and avoid bothering to do
+anything. If they do, however, they \e{must} perform this check on
+the value \e{returned} from \cw{midend_rewrite_statusbar()}, rather
+than the value passed in to it (because the mid-end will frequently
+update the status-bar timer without the back end's intervention).
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-new} \cw{blitter_new()}
+
+\c blitter *(*blitter_new)(void *handle, int w, int h);
+
+This function behaves exactly like the back end \cw{blitter_new()}
+function; see \k{drawing-blitter-new}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-free} \cw{blitter_free()}
+
+\c void (*blitter_free)(void *handle, blitter *bl);
+
+This function behaves exactly like the back end \cw{blitter_free()}
+function; see \k{drawing-blitter-free}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-save} \cw{blitter_save()}
+
+\c void (*blitter_save)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end \cw{blitter_save()}
+function; see \k{drawing-blitter-save}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-load} \cw{blitter_load()}
+
+\c void (*blitter_load)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end \cw{blitter_load()}
+function; see \k{drawing-blitter-load}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-begin-doc} \cw{begin_doc()}
+
+\c void (*begin_doc)(void *handle, int pages);
+
+This function is called at the beginning of a printing run. It gives
+the front end an opportunity to initialise any required printing
+subsystem. It also provides the number of pages in advance.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-begin-page} \cw{begin_page()}
+
+\c void (*begin_page)(void *handle, int number);
+
+This function is called during printing, at the beginning of each
+page. It gives the page number (numbered from 1 rather than 0, so
+suitable for use in user-visible contexts).
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-begin-puzzle} \cw{begin_puzzle()}
+
+\c void (*begin_puzzle)(void *handle, float xm, float xc,
+\c                      float ym, float yc, int pw, int ph, float wmm);
+
+This function is called during printing, just before printing a
+single puzzle on a page. It specifies the size and location of the
+puzzle on the page.
+
+\c{xm} and \c{xc} specify the horizontal position of the puzzle on
+the page, as a linear function of the page width. The front end is
+expected to multiply the page width by \c{xm}, add \c{xc} (measured
+in millimetres), and use the resulting x-coordinate as the left edge
+of the puzzle.
+
+Similarly, \c{ym} and \c{yc} specify the vertical position of the
+puzzle as a function of the page height: the page height times
+\c{xm}, plus \c{xc} millimetres, equals the desired distance from
+the top of the page to the top of the puzzle.
+
+(This unwieldy mechanism is required because not all printing
+systems can communicate the page size back to the software. The
+PostScript back end, for example, writes out PS which determines the
+page size at print time by means of calling \cq{clippath}, and
+centres the puzzles within that. Thus, exactly the same PS file
+works on A4 or on US Letter paper without needing local
+configuration, which simplifies matters.)
+
+\cw{pw} and \cw{ph} give the size of the puzzle in drawing API
+coordinates. The printing system will subsequently call the puzzle's
+own print function, which will in turn call drawing API functions in
+the expectation that an area \cw{pw} by \cw{ph} units is available
+to draw the puzzle on.
+
+Finally, \cw{wmm} gives the desired width of the puzzle in
+millimetres. (The aspect ratio is expected to be preserved, so if
+the desired puzzle height is also needed then it can be computed as
+\cw{wmm*ph/pw}.)
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-puzzle} \cw{end_puzzle()}
+
+\c void (*end_puzzle)(void *handle);
+
+This function is called after the printing of a specific puzzle is
+complete.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-page} \cw{end_page()}
+
+\c void (*end_page)(void *handle, int number);
+
+This function is called after the printing of a page is finished.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-doc} \cw{end_doc()}
+
+\c void (*end_doc)(void *handle);
+
+This function is called after the printing of the entire document is
+finished. This is the moment to close files, send things to the
+print spooler, or whatever the local convention is.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-line-width} \cw{line_width()}
+
+\c void (*line_width)(void *handle, float width);
+
+This function is called to set the line thickness, during printing
+only. Note that the width is a \cw{float} here, where it was an
+\cw{int} as seen by the back end. This is because \cw{drawing.c} may
+have scaled it on the way past.
+
+However, the width is still specified in the same coordinate system
+as the rest of the drawing.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\H{drawingapi-frontend} The drawing API as called by the front end
+
+There are a small number of functions provided in \cw{drawing.c}
+which the front end needs to \e{call}, rather than helping to
+implement. They are described in this section.
+
+\S{drawing-init} \cw{drawing_init()}
+
+\c drawing *drawing_init(const drawing_api *api, void *handle);
+
+This function creates a drawing object. It is passed a
+\c{drawing_api}, which is a structure containing nothing but
+function pointers; and also a \cq{void *} handle. The handle is
+passed back to each function pointer when it is called.
+
+\S{drawing-free} \cw{drawing_free()}
+
+\c void drawing_free(drawing *dr);
+
+This function frees a drawing object. Note that the \cq{void *}
+handle is not freed; if that needs cleaning up it must be done by
+the front end.
+
+\S{drawing-print-get-colour} \cw{print_get_colour()}
+
+\c void print_get_colour(drawing *dr, int colour, int *hatch,
+\c                       float *r, float *g, float *b)
+
+This function is called by the implementations of the drawing API
+functions when they are called in a printing context. It takes a
+colour index as input, and returns the description of the colour as
+requested by the back end.
+
+\c{*r}, \c{*g} and \c{*b} are filled with the RGB values of the
+desired colour if printing in colour.
+
+\c{*hatch} is filled with the type of hatching (or not) desired if
+printing in black and white. See \k{print-grey-colour} for details
+of the values this integer can take.
 
 \C{midend} The API provided by the mid-end
 
@@ -1838,24 +2403,29 @@ platform. If you're only interested in writing new puzzles, you can
 safely skip this chapter.
 
 All the persistent state in the mid-end is encapsulated within a
-\c{midend_data} structure, to facilitate having multiple mid-ends in
-any port which supports multiple puzzle windows open simultaneously.
-Each \c{midend_data} is intended to handle the contents of a single
+\c{midend} structure, to facilitate having multiple mid-ends in any
+port which supports multiple puzzle windows open simultaneously.
+Each \c{midend} is intended to handle the contents of a single
 puzzle window.
 
 \H{midend-new} \cw{midend_new()}
 
-\c midend_data *midend_new(frontend *fe, const game *ourgame);
+\c midend *midend_new(frontend *fe, const game *ourgame,
+\c                    const drawing_api *drapi, void *drhandle)
 
 Allocates and returns a new mid-end structure.
 
 The \c{fe} argument is stored in the mid-end. It will be used when
 calling back to functions such as \cw{activate_timer()}
-(\k{frontend-activate-timer}), and will be passed on to back end
-functions such as \cw{colours()} (\k{backend-colours}) and
-\cw{redraw()} (\k{backend-redraw}). The latter, of course, means
-that the front end can expect to receive this pointer in calls to
-the entire drawing API (\k{drawing}).
+(\k{frontend-activate-timer}), and will be passed on to the back end
+function \cw{colours()} (\k{backend-colours}).
+
+The parameters \c{drapi} and \c{drhandle} are passed to
+\cw{drawing_init()} (\k{drawing-init}) to construct a drawing object
+which will be passed to the back end function \cw{redraw()}
+(\k{backend-redraw}). Hence, all drawing-related function pointers
+defined in \c{drapi} can expect to be called with \c{drhandle} as
+their first argument.
 
 The \c{ourgame} argument points to a container structure describing
 a game back end. The mid-end thus created will only be capable of
@@ -1867,13 +2437,13 @@ without closing the window...)
 
 \H{midend-free} \cw{midend_free()}
 
-\c void midend_free(midend_data *me);
+\c void midend_free(midend *me);
 
 Frees a mid-end structure and all its associated data.
 
 \H{midend-set-params} \cw{midend_set_params()}
 
-\c void midend_set_params(midend_data *me, game_params *params);
+\c void midend_set_params(midend *me, game_params *params);
 
 Sets the current game parameters for a mid-end. Subsequent games
 generated by \cw{midend_new_game()} (\k{midend-new-game}) will use
@@ -1887,7 +2457,7 @@ response to the user making a selection from the presets menu.
 
 \H{midend-size} \cw{midend_size()}
 
-\c void midend_size(midend_data *me, int *x, int *y, int expand);
+\c void midend_size(midend *me, int *x, int *y, int expand);
 
 Tells the mid-end to figure out its window size.
 
@@ -1937,7 +2507,7 @@ that \e{and} set the \c{expand} flag, though!
 
 \H{midend-new-game} \cw{midend_new_game()}
 
-\c void midend_new_game(midend_data *me);
+\c void midend_new_game(midend *me);
 
 Causes the mid-end to begin a new game. Normally the game will be a
 new randomly generated puzzle. However, if you have previously
@@ -1963,7 +2533,7 @@ a fresh one already. It would work, but it's usually excessive.)
 
 \H{midend-restart-game} \cw{midend_restart_game()}
 
-\c void midend_restart_game(midend_data *me);
+\c void midend_restart_game(midend *me);
 
 This function causes the current game to be restarted. This is done
 by placing a new copy of the original game state on the end of the
@@ -1975,7 +2545,7 @@ function.
 
 \H{midend-force-redraw} \cw{midend_force_redraw()}
 
-\c void midend_force_redraw(midend_data *me);
+\c void midend_force_redraw(midend *me);
 
 Forces a complete redraw of the puzzle window, by means of
 discarding the current \c{game_drawstate} and creating a new one
@@ -1986,7 +2556,7 @@ call to this function.
 
 \H{midend-redraw} \cw{midend_redraw()}
 
-\c void midend_redraw(midend_data *me);
+\c void midend_redraw(midend *me);
 
 Causes a partial redraw of the puzzle window, by means of simply
 calling the game's \cw{redraw()} function. (That is, the only things
@@ -1997,7 +2567,7 @@ call to this function.
 
 \H{midend-process-key} \cw{midend_process_key()}
 
-\c int midend_process_key(midend_data *me, int x, int y, int button);
+\c int midend_process_key(midend *me, int x, int y, int button);
 
 The front end calls this function to report a mouse or keyboard
 event. The parameters \c{x}, \c{y} and \c{button} are almost
@@ -2032,7 +2602,7 @@ front end's drawing API and/or \cw{activate_timer()}
 
 \H{midend-colours} \cw{midend_colours()}
 
-\c float *midend_colours(midend_data *me, int *ncolours);
+\c float *midend_colours(midend *me, int *ncolours);
 
 Returns an array of the colours required by the game, in exactly the
 same format as that returned by the back end function \cw{colours()}
@@ -2045,7 +2615,7 @@ more full and formal in future.)
 
 \H{midend-timer} \cw{midend_timer()}
 
-\c void midend_timer(midend_data *me, float tplus);
+\c void midend_timer(midend *me, float tplus);
 
 If the mid-end has called \cw{activate_timer()}
 (\k{frontend-activate-timer}) to request regular callbacks for
@@ -2060,7 +2630,7 @@ result in calls back to the front end's drawing API.
 
 \H{midend-num-presets} \cw{midend_num_presets()}
 
-\c int midend_num_presets(midend_data *me);
+\c int midend_num_presets(midend *me);
 
 Returns the number of game parameter presets supplied by this game.
 Front ends should use this function and \cw{midend_fetch_preset()}
@@ -2072,7 +2642,7 @@ impossible that they may become more full and formal in future.)
 
 \H{midend-fetch-preset} \cw{midend_fetch_preset()}
 
-\c void midend_fetch_preset(midend_data *me, int n,
+\c void midend_fetch_preset(midend *me, int n,
 \c                          char **name, game_params **params);
 
 Returns one of the preset game parameter structures for the game. On
@@ -2089,7 +2659,7 @@ free them directly, because they will be freed automatically during
 
 \H{midend-wants-statusbar} \cw{midend_wants_statusbar()}
 
-\c int midend_wants_statusbar(midend_data *me);
+\c int midend_wants_statusbar(midend *me);
 
 This function returns \cw{TRUE} if the puzzle has a use for a
 textual status line (to display score, completion status, currently
@@ -2100,7 +2670,7 @@ the back end.
 
 \H{midend-get-config} \cw{midend_get_config()}
 
-\c config_item *midend_get_config(midend_data *me, int which,
+\c config_item *midend_get_config(midend *me, int which,
 \c                                char **wintitle);
 
 Returns a dialog box description for user configuration.
@@ -2147,7 +2717,7 @@ will probably need to pass it to \cw{midend_set_config}.)
 
 \H{midend-set-config} \cw{midend_set_config()}
 
-\c char *midend_set_config(midend_data *me, int which,
+\c char *midend_set_config(midend *me, int which,
 \c                         config_item *cfg);
 
 Passes the mid-end the results of a configuration dialog box.
@@ -2169,7 +2739,7 @@ using \cw{midend_size()} and eventually perform a refresh using
 
 \H{midend-game-id} \cw{midend_game_id()}
 
-\c char *midend_game_id(midend_data *me, char *id);
+\c char *midend_game_id(midend *me, char *id);
 
 Passes the mid-end a string game ID (of any of the valid forms
 \cq{params}, \cq{params:description} or \cq{params#seed}) which the
@@ -2188,9 +2758,17 @@ requested. The front end should therefore call
 using \cw{midend_size()} and eventually case a refresh using
 \cw{midend_redraw()}.
 
+\H{midend-get-game-id} \cw{midend_get_game_id()}
+
+\c char *midend_get_game_id(midend *me)
+
+Returns a descriptive game ID (i.e. one in the form
+\cq{params:description}) describing the game currently active in the
+mid-end. The returned string is dynamically allocated.
+
 \H{midend-text-format} \cw{midend_text_format()}
 
-\c char *midend_text_format(midend_data *me);
+\c char *midend_text_format(midend *me);
 
 Formats the current game's current state as ASCII text suitable for
 copying to the clipboard. The returned string is dynamically
@@ -2207,7 +2785,7 @@ conversion.
 
 \H{midend-solve} \cw{midend_solve()}
 
-\c char *midend_solve(midend_data *me);
+\c char *midend_solve(midend *me);
 
 Requests the mid-end to perform a Solve operation.
 
@@ -2221,7 +2799,7 @@ function.
 
 \H{midend-rewrite-statusbar} \cw{midend_rewrite_statusbar()}
 
-\c char *midend_rewrite_statusbar(midend_data *me, char *text);
+\c char *midend_rewrite_statusbar(midend *me, char *text);
 
 The front end should call this function from within
 \cw{status_bar()} (\k{drawing-status-bar}). It should be passed the
@@ -2247,7 +2825,7 @@ of the other options are an improvement.)
 
 \H{midend-serialise} \cw{midend_serialise()}
 
-\c void midend_serialise(midend_data *me,
+\c void midend_serialise(midend *me,
 \c                       void (*write)(void *ctx, void *buf, int len),
 \c                       void *wctx);
 
@@ -2272,7 +2850,7 @@ output string.
 
 \H{midend-deserialise} \cw{midend_deserialise()}
 
-\c char *midend_deserialise(midend_data *me,
+\c char *midend_deserialise(midend *me,
 \c                          int (*read)(void *ctx, void *buf, int len),
 \c                          void *rctx);
 
@@ -2419,6 +2997,20 @@ variadic function in the style of \cw{printf()}, and is expected to
 show the formatted error message to the user any way it can and then
 terminate the application. It must not return.
 
+\H{frontend-default-colour} \cw{frontend_default_colour()}
+
+\c void frontend_default_colour(frontend *fe, float *output);
+
+This function expects to be passed a pointer to an array of three
+\cw{float}s. It returns the platform's local preferred background
+colour in those three floats, as red, green and blue values (in that
+order) ranging from \cw{0.0} to \cw{1.0}.
+
+This function should only ever be called by the back end function
+\cw{colours()} (\k{backend-colours}). (Thus, it isn't a
+\e{midend}-to-frontend function as such, but there didn't seem to be
+anywhere else particularly good to put it. Sorry.)
+
 \C{utils} Utility APIs
 
 This chapter documents a variety of utility APIs provided for the
index 7e938bb99d0d25973f1da7b4e04314d0ed68bdbe..6d4e462a1a62dd36ce0e229ded1818c734930f87 100644 (file)
@@ -1027,7 +1027,7 @@ static char *validate_desc(game_params *params, char *desc)
     return ret;
 }
 
-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 n = params->n, w = n+2, h = n+1, wh = w*h;
     game_state *state = snew(game_state);
@@ -1433,8 +1433,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = h * TILESIZE + 2*BORDER;
 }
 
-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;
 }
@@ -1469,7 +1469,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1485,7 +1485,7 @@ 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->visible);
     sfree(ds);
@@ -1500,7 +1500,7 @@ enum {
     TYPE_MASK = 0x0F
 };
 
-static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
                       int x, int y, int type)
 {
     int w = state->w /*, h = state->h */;
@@ -1509,7 +1509,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
     char str[80];
     int flags;
 
-    draw_rect(fe, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND);
+    draw_rect(dr, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND);
 
     flags = type &~ TYPE_MASK;
     type &= TYPE_MASK;
@@ -1538,16 +1538,16 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
         }
 
         if (type == TYPE_L || type == TYPE_T)
-            draw_circle(fe, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET,
+            draw_circle(dr, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET,
                         DOMINO_RADIUS, bg, bg);
         if (type == TYPE_R || type == TYPE_T)
-            draw_circle(fe, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET,
+            draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET,
                         DOMINO_RADIUS, bg, bg);
         if (type == TYPE_L || type == TYPE_B)
-            draw_circle(fe, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET,
+            draw_circle(dr, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET,
                         DOMINO_RADIUS, bg, bg);
         if (type == TYPE_R || type == TYPE_B)
-            draw_circle(fe, cx+TILESIZE-1-DOMINO_COFFSET,
+            draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET,
                         cy+TILESIZE-1-DOMINO_COFFSET,
                         DOMINO_RADIUS, bg, bg);
 
@@ -1559,40 +1559,40 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
             x2 = cx + TILESIZE-1 - (i ? DOMINO_GUTTER : DOMINO_COFFSET);
             y2 = cy + TILESIZE-1 - (i ? DOMINO_COFFSET : DOMINO_GUTTER);
             if (type == TYPE_L)
-                x2 = cx + TILESIZE-1;
+                x2 = cx + TILESIZE + TILESIZE/16;
             else if (type == TYPE_R)
-                x1 = cx;
+                x1 = cx - TILESIZE/16;
             else if (type == TYPE_T)
-                y2 = cy + TILESIZE-1;
+                y2 = cy + TILESIZE + TILESIZE/16;
             else if (type == TYPE_B)
-                y1 = cy;
+                y1 = cy - TILESIZE/16;
 
-            draw_rect(fe, x1, y1, x2-x1+1, y2-y1+1, bg);
+            draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg);
         }
     } else {
         if (flags & EDGE_T)
-            draw_rect(fe, cx+DOMINO_GUTTER, cy,
+            draw_rect(dr, cx+DOMINO_GUTTER, cy,
                       TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
         if (flags & EDGE_B)
-            draw_rect(fe, cx+DOMINO_GUTTER, cy+TILESIZE-1,
+            draw_rect(dr, cx+DOMINO_GUTTER, cy+TILESIZE-1,
                       TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
         if (flags & EDGE_L)
-            draw_rect(fe, cx, cy+DOMINO_GUTTER,
+            draw_rect(dr, cx, cy+DOMINO_GUTTER,
                       1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
         if (flags & EDGE_R)
-            draw_rect(fe, cx+TILESIZE-1, cy+DOMINO_GUTTER,
+            draw_rect(dr, cx+TILESIZE-1, cy+DOMINO_GUTTER,
                       1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
         nc = COL_TEXT;
     }
 
     sprintf(str, "%d", state->numbers->numbers[y*w+x]);
-    draw_text(fe, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
+    draw_text(dr, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
               ALIGN_HCENTRE | ALIGN_VCENTRE, nc, str);
 
-    draw_update(fe, cx, cy, TILESIZE, TILESIZE);
+    draw_update(dr, cx, cy, 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)
 {
@@ -1603,8 +1603,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
         int pw, ph;
         game_compute_size(&state->params, TILESIZE, &pw, &ph);
-       draw_rect(fe, 0, 0, pw, ph, COL_BACKGROUND);
-       draw_update(fe, 0, 0, pw, ph);
+       draw_rect(dr, 0, 0, pw, ph, COL_BACKGROUND);
+       draw_update(dr, 0, 0, pw, ph);
        ds->started = TRUE;
     }
 
@@ -1659,7 +1659,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 c |= 0x40;             /* we're flashing */
 
            if (ds->visible[n] != c) {
-               draw_tile(fe, ds, state, x, y, c);
+               draw_tile(dr, ds, state, x, y, c);
                 ds->visible[n] = c;
            }
        }
@@ -1692,6 +1692,54 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    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->w, h = state->h;
+    int c, x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+    c = print_mono_colour(dr, 0); assert(c == COL_TEXT);
+    c = print_mono_colour(dr, 0); assert(c == COL_DOMINO);
+    c = print_mono_colour(dr, 0); assert(c == COL_DOMINOCLASH);
+    c = print_mono_colour(dr, 1); assert(c == COL_DOMINOTEXT);
+    c = print_mono_colour(dr, 0); assert(c == COL_EDGE);
+
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            int n = y*w+x;
+           unsigned long c;
+
+            if (state->grid[n] == n-1)
+                c = TYPE_R;
+            else if (state->grid[n] == n+1)
+                c = TYPE_L;
+            else if (state->grid[n] == n-w)
+                c = TYPE_B;
+            else if (state->grid[n] == n+w)
+                c = TYPE_T;
+            else
+                c = TYPE_BLANK;
+
+           draw_tile(dr, ds, state, x, y, c);
+       }
+}
+
 #ifdef COMBINED
 #define thegame dominosa
 #endif
@@ -1727,6 +1775,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/drawing.c b/drawing.c
new file mode 100644 (file)
index 0000000..63e27a4
--- /dev/null
+++ b/drawing.c
@@ -0,0 +1,222 @@
+/*
+ * drawing.c: Intermediary between the drawing interface as
+ * presented to the back end, and that implemented by the front
+ * end.
+ * 
+ * Mostly just looks up calls in a vtable and passes them through
+ * unchanged. However, on the printing side it tracks print colours
+ * so the front end API doesn't have to.
+ * 
+ * FIXME: could we also sort out rewrite_statusbar in here? Also
+ * I'd _like_ to do automatic draw_updates, but it's a pain for
+ * draw_text in particular - I could invent a front end API which
+ * retrieved the text bounds and then do the alignment myself as
+ * well, except that that doesn't work for PS. As usual.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+struct print_colour {
+    int hatch;
+    float r, g, b;
+};
+
+struct drawing {
+    const drawing_api *api;
+    void *handle;
+    struct print_colour *colours;
+    int ncolours, coloursize;
+    float scale;
+};
+
+drawing *drawing_init(const drawing_api *api, void *handle)
+{
+    drawing *dr = snew(drawing);
+    dr->api = api;
+    dr->handle = handle;
+    dr->colours = NULL;
+    dr->ncolours = dr->coloursize = 0;
+    dr->scale = 1.0F;
+    return dr;
+}
+
+void drawing_free(drawing *dr)
+{
+    sfree(dr->colours);
+    sfree(dr);
+}
+
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text)
+{
+    dr->api->draw_text(dr->handle, x, y, fonttype, fontsize, align,
+                      colour, text);
+}
+
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour)
+{
+    dr->api->draw_rect(dr->handle, x, y, w, h, colour);
+}
+
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour)
+{
+    dr->api->draw_line(dr->handle, x1, y1, x2, y2, colour);
+}
+
+void draw_polygon(drawing *dr, int *coords, int npoints,
+                  int fillcolour, int outlinecolour)
+{
+    dr->api->draw_polygon(dr->handle, coords, npoints, fillcolour,
+                         outlinecolour);
+}
+
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour)
+{
+    dr->api->draw_circle(dr->handle, cx, cy, radius, fillcolour,
+                        outlinecolour);
+}
+
+void draw_update(drawing *dr, int x, int y, int w, int h)
+{
+    if (dr->api->draw_update)
+       dr->api->draw_update(dr->handle, x, y, w, h);
+}
+
+void clip(drawing *dr, int x, int y, int w, int h)
+{
+    dr->api->clip(dr->handle, x, y, w, h);
+}
+
+void unclip(drawing *dr)
+{
+    dr->api->unclip(dr->handle);
+}
+
+void start_draw(drawing *dr)
+{
+    dr->api->start_draw(dr->handle);
+}
+
+void end_draw(drawing *dr)
+{
+    dr->api->end_draw(dr->handle);
+}
+
+void status_bar(drawing *dr, char *text)
+{
+    if (dr->api->status_bar)
+       dr->api->status_bar(dr->handle, text);
+}
+
+blitter *blitter_new(drawing *dr, int w, int h)
+{
+    return dr->api->blitter_new(dr->handle, w, h);
+}
+
+void blitter_free(drawing *dr, blitter *bl)
+{
+    dr->api->blitter_free(dr->handle, bl);
+}
+
+void blitter_save(drawing *dr, blitter *bl, int x, int y)
+{
+    dr->api->blitter_save(dr->handle, bl, x, y);
+}
+
+void blitter_load(drawing *dr, blitter *bl, int x, int y)
+{
+    dr->api->blitter_load(dr->handle, bl, x, y);
+}
+
+void print_begin_doc(drawing *dr, int pages)
+{
+    dr->api->begin_doc(dr->handle, pages);
+}
+
+void print_begin_page(drawing *dr, int number)
+{
+    dr->api->begin_page(dr->handle, number);
+}
+
+void print_begin_puzzle(drawing *dr, float xm, float xc,
+                       float ym, float yc, int pw, int ph, float wmm,
+                       float scale)
+{
+    dr->scale = scale;
+    dr->ncolours = 0;
+    dr->api->begin_puzzle(dr->handle, xm, xc, ym, yc, pw, ph, wmm);
+}
+
+void print_end_puzzle(drawing *dr)
+{
+    dr->api->end_puzzle(dr->handle);
+    dr->scale = 1.0F;
+}
+
+void print_end_page(drawing *dr, int number)
+{
+    dr->api->end_page(dr->handle, number);
+}
+
+void print_end_doc(drawing *dr)
+{
+    dr->api->end_doc(dr->handle);
+}
+
+void print_get_colour(drawing *dr, int colour, int *hatch,
+                     float *r, float *g, float *b)
+{
+    assert(colour >= 0 && colour < dr->ncolours);
+    *hatch = dr->colours[colour].hatch;
+    *r = dr->colours[colour].r;
+    *g = dr->colours[colour].g;
+    *b = dr->colours[colour].b;
+}
+
+int print_rgb_colour(drawing *dr, int hatch, float r, float g, float b)
+{
+    if (dr->ncolours >= dr->coloursize) {
+       dr->coloursize = dr->ncolours + 16;
+       dr->colours = sresize(dr->colours, dr->coloursize,
+                             struct print_colour);
+    }
+    dr->colours[dr->ncolours].hatch = hatch;
+    dr->colours[dr->ncolours].r = r;
+    dr->colours[dr->ncolours].g = g;
+    dr->colours[dr->ncolours].b = b;
+    return dr->ncolours++;
+}
+
+int print_grey_colour(drawing *dr, int hatch, float grey)
+{
+    return print_rgb_colour(dr, hatch, grey, grey, grey);
+}
+
+int print_mono_colour(drawing *dr, int grey)
+{
+    return print_rgb_colour(dr, grey ? HATCH_CLEAR : HATCH_SOLID,
+                           grey, grey, grey);
+}
+
+void print_line_width(drawing *dr, int width)
+{
+    /*
+     * I don't think it's entirely sensible to have line widths be
+     * entirely relative to the puzzle size; there is a point
+     * beyond which lines are just _stupidly_ thick. On the other
+     * hand, absolute line widths aren't particularly nice either
+     * because they start to feel a bit feeble at really large
+     * scales.
+     * 
+     * My experimental answer is to scale line widths as the
+     * _square root_ of the main puzzle scale. Double the puzzle
+     * size, and the line width multiplies by 1.4.
+     */
+    dr->api->line_width(dr->handle, sqrt(dr->scale) * width);
+}
index 20ceb4b39b8bea8b790088daeba2ccec0331f273..72c1c127d03002be3caf0f29aa96aaa8a4985e8c 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -318,7 +318,7 @@ static char *validate_desc(game_params *params, char *desc)
     return err;
 }
 
-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)
 {
     game_state *state = snew(game_state);
     int i;
@@ -574,8 +574,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILE_SIZE * params->h + 2 * BORDER;
 }
 
-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;
 }
@@ -594,7 +594,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -611,17 +611,17 @@ 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->tiles);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
                       int x, int y, int tile, int flash_colour)
 {
     if (tile == 0) {
-        draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
                   flash_colour);
     } else {
         int coords[6];
@@ -633,25 +633,25 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
         coords[3] = y;
         coords[4] = x;
         coords[5] = y + TILE_SIZE - 1;
-        draw_polygon(fe, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
 
         coords[0] = x;
         coords[1] = y;
-        draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
-        draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+        draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
                   TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
                   flash_colour);
 
         sprintf(str, "%d", tile);
-        draw_text(fe, x + TILE_SIZE/2, y + TILE_SIZE/2,
+        draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2,
                   FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
                   COL_TEXT, str);
     }
-    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 }
 
-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)
 {
@@ -666,10 +666,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
         int coords[10];
 
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILE_SIZE * state->w + 2 * BORDER,
                  TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILE_SIZE * state->w + 2 * BORDER,
                    TILE_SIZE * state->h + 2 * BORDER);
 
@@ -686,11 +686,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
         coords[6] = coords[8] + TILE_SIZE;
         coords[7] = coords[9] - TILE_SIZE;
-        draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
         coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
         coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
-        draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
         ds->started = TRUE;
     }
@@ -775,7 +775,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                     y = COORD(Y(state, i));
                 }
 
-                draw_tile(fe, ds, state, x, y, t, bgcolour);
+                draw_tile(dr, ds, state, x, y, t, bgcolour);
             }
             ds->tiles[i] = t0;
         }
@@ -803,7 +803,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                    (state->completed ? "COMPLETED! " : ""),
                    (state->completed ? state->completed : state->movecount));
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 }
 
@@ -837,6 +837,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame fifteen
 #endif
@@ -872,6 +880,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/flip.c b/flip.c
index 6ccd872c317b6835c9ece6d05b1fbb061811c6fe..2d1a27166bb3b7019696cd7b170e4786a076d42a 100644 (file)
--- a/flip.c
+++ b/flip.c
@@ -613,7 +613,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, wh = w * h;
     int mlen = (wh*wh+3)/4;
@@ -999,8 +999,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILE_SIZE * params->h + 2 * BORDER;
 }
 
-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;
 }
@@ -1039,7 +1039,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1055,13 +1055,13 @@ 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->tiles);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_drawstate *ds,
                      game_state *state, int x, int y, int tile, int anim,
                      float animtime)
 {
@@ -1069,9 +1069,9 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
     int bx = x * TILE_SIZE + BORDER, by = y * TILE_SIZE + BORDER;
     int i, j, dcol = (tile & 4) ? COL_CURSOR : COL_DIAG;
 
-    clip(fe, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
+    clip(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
 
-    draw_rect(fe, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1,
+    draw_rect(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1,
               anim ? COL_BACKGROUND : tile & 1 ? COL_WRONG : COL_RIGHT);
     if (anim) {
        /*
@@ -1093,7 +1093,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
        if (animtime < 0.5)
            colour = COL_WRONG + COL_RIGHT - colour;
 
-       draw_polygon(fe, coords, 4, colour, COL_GRID);
+       draw_polygon(dr, coords, 4, colour, COL_GRID);
     }
 
     /*
@@ -1108,12 +1108,12 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
                int cx = (bx + TILE_SIZE/2) + (2 * ox - 1) * td;
                int cy = (by + TILE_SIZE/2) + (2 * oy - 1) * td;
                if (ox == 0 && oy == 0)
-                    draw_rect(fe, cx, cy, 2*td+1, 2*td+1, dcol);
+                    draw_rect(dr, cx, cy, 2*td+1, 2*td+1, dcol);
                 else {
-                    draw_line(fe, cx, cy, cx+2*td, cy, dcol);
-                    draw_line(fe, cx, cy+2*td, cx+2*td, cy+2*td, dcol);
-                    draw_line(fe, cx, cy, cx, cy+2*td, dcol);
-                    draw_line(fe, cx+2*td, cy, cx+2*td, cy+2*td, dcol);
+                    draw_line(dr, cx, cy, cx+2*td, cy, dcol);
+                    draw_line(dr, cx, cy+2*td, cx+2*td, cy+2*td, dcol);
+                    draw_line(dr, cx, cy, cx, cy+2*td, dcol);
+                    draw_line(dr, cx+2*td, cy, cx+2*td, cy+2*td, dcol);
                 }
            }
 
@@ -1125,20 +1125,20 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
        int y1 = by + TILE_SIZE / 20, y2 = by + TILE_SIZE - TILE_SIZE / 20;
        int i = 3;
        while (i--) {
-           draw_line(fe, x1, y1, x2, y1, COL_HINT);
-           draw_line(fe, x1, y2, x2, y2, COL_HINT);
-           draw_line(fe, x1, y1, x1, y2, COL_HINT);
-           draw_line(fe, x2, y1, x2, y2, COL_HINT);
+           draw_line(dr, x1, y1, x2, y1, COL_HINT);
+           draw_line(dr, x1, y2, x2, y2, COL_HINT);
+           draw_line(dr, x1, y1, x1, y2, COL_HINT);
+           draw_line(dr, x2, y1, x2, y2, COL_HINT);
            x1++, y1++, x2--, y2--;
        }
     }
 
-    unclip(fe);
+    unclip(dr);
 
-    draw_update(fe, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
+    draw_update(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
 }
 
-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)
 {
@@ -1146,22 +1146,22 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     int i, flashframe;
 
     if (!ds->started) {
-        draw_rect(fe, 0, 0, TILE_SIZE * w + 2 * BORDER,
+        draw_rect(dr, 0, 0, TILE_SIZE * w + 2 * BORDER,
                   TILE_SIZE * h + 2 * BORDER, COL_BACKGROUND);
 
         /*
          * Draw the grid lines.
          */
         for (i = 0; i <= w; i++)
-            draw_line(fe, i * TILE_SIZE + BORDER, BORDER,
+            draw_line(dr, i * TILE_SIZE + BORDER, BORDER,
                       i * TILE_SIZE + BORDER, h * TILE_SIZE + BORDER,
                       COL_GRID);
         for (i = 0; i <= h; i++)
-            draw_line(fe, BORDER, i * TILE_SIZE + BORDER,
+            draw_line(dr, BORDER, i * TILE_SIZE + BORDER,
                       w * TILE_SIZE + BORDER, i * TILE_SIZE + BORDER,
                       COL_GRID);
 
-        draw_update(fe, 0, 0, TILE_SIZE * w + 2 * BORDER,
+        draw_update(dr, 0, 0, TILE_SIZE * w + 2 * BORDER,
                     TILE_SIZE * h + 2 * BORDER);
 
         ds->started = TRUE;
@@ -1201,7 +1201,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            vv = v;
 
         if (ds->tiles[i] == 255 || vv == 255 || ds->tiles[i] != vv) {
-            draw_tile(fe, ds, state, x, y, v, vv == 255, animtime);
+            draw_tile(dr, ds, state, x, y, v, vv == 255, animtime);
             ds->tiles[i] = vv;
         }
     }
@@ -1215,7 +1215,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 (state->cheated ? "Auto-solver used. " : "")),
                state->moves);
 
-       status_bar(fe, buf);
+       status_bar(dr, buf);
     }
 }
 
@@ -1244,6 +1244,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+    static void game_print_size(game_params *params, float *x, float *y)
+    {
+    }
+    
+    static void game_print(drawing *dr, game_state *state, int tilesize)
+    {
+    }
+    
 #ifdef COMBINED
 #define thegame flip
 #endif
@@ -1279,6 +1287,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/gtk.c b/gtk.c
index 567ff56662dbec29c5154575a54dabe39bc9a2c9..6fef7b864338bca15b12ec5cc498505331fa2e44 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -8,6 +8,7 @@
 #include <time.h>
 #include <stdarg.h>
 #include <string.h>
+#include <errno.h>
 
 #include <sys/time.h>
 
@@ -103,7 +104,7 @@ struct frontend {
     int ncolours;
     GdkColormap *colmap;
     int w, h;
-    midend_data *me;
+    midend *me;
     GdkGC *gc;
     int bbox_l, bbox_r, bbox_u, bbox_d;
     int timer_active, timer_id;
@@ -137,8 +138,9 @@ void frontend_default_colour(frontend *fe, float *output)
     output[2] = col.blue / 65535.0;
 }
 
-void status_bar(frontend *fe, char *text)
+void gtk_status_bar(void *handle, char *text)
 {
+    frontend *fe = (frontend *)handle;
     char *rewritten;
 
     assert(fe->statusbar);
@@ -154,8 +156,9 @@ void status_bar(frontend *fe, char *text)
     }
 }
 
-void start_draw(frontend *fe)
+void gtk_start_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     fe->gc = gdk_gc_new(fe->area->window);
     fe->bbox_l = fe->w;
     fe->bbox_r = 0;
@@ -163,8 +166,9 @@ void start_draw(frontend *fe)
     fe->bbox_d = 0;
 }
 
-void clip(frontend *fe, int x, int y, int w, int h)
+void gtk_clip(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     GdkRectangle rect;
 
     rect.x = x;
@@ -175,8 +179,9 @@ void clip(frontend *fe, int x, int y, int w, int h)
     gdk_gc_set_clip_rectangle(fe->gc, &rect);
 }
 
-void unclip(frontend *fe)
+void gtk_unclip(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     GdkRectangle rect;
 
     rect.x = 0;
@@ -187,9 +192,10 @@ void unclip(frontend *fe)
     gdk_gc_set_clip_rectangle(fe->gc, &rect);
 }
 
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
-               int align, int colour, char *text)
+void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
+                  int align, int colour, char *text)
 {
+    frontend *fe = (frontend *)handle;
     int i;
 
     /*
@@ -335,21 +341,24 @@ void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
 
 }
 
-void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
+void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
 {
+    frontend *fe = (frontend *)handle;
     gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
     gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h);
 }
 
-void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
+void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
 {
+    frontend *fe = (frontend *)handle;
     gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
     gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
 }
 
-void draw_polygon(frontend *fe, int *coords, int npoints,
-                  int fillcolour, int outlinecolour)
+void gtk_draw_poly(void *handle, int *coords, int npoints,
+                  int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     GdkPoint *points = snewn(npoints, GdkPoint);
     int i;
 
@@ -369,9 +378,10 @@ void draw_polygon(frontend *fe, int *coords, int npoints,
     sfree(points);
 }
 
-void draw_circle(frontend *fe, int cx, int cy, int radius,
-                 int fillcolour, int outlinecolour)
+void gtk_draw_circle(void *handle, int cx, int cy, int radius,
+                    int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     if (fillcolour >= 0) {
        gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]);
        gdk_draw_arc(fe->pixmap, fe->gc, TRUE,
@@ -391,7 +401,7 @@ struct blitter {
     int w, h, x, y;
 };
 
-blitter *blitter_new(int w, int h)
+blitter *gtk_blitter_new(void *handle, int w, int h)
 {
     /*
      * We can't create the pixmap right now, because fe->window
@@ -405,15 +415,16 @@ blitter *blitter_new(int w, int h)
     return bl;
 }
 
-void blitter_free(blitter *bl)
+void gtk_blitter_free(void *handle, blitter *bl)
 {
     if (bl->pixmap)
         gdk_pixmap_unref(bl->pixmap);
     sfree(bl);
 }
 
-void blitter_save(frontend *fe, blitter *bl, int x, int y)
+void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     if (!bl->pixmap)
         bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1);
     bl->x = x;
@@ -424,8 +435,9 @@ void blitter_save(frontend *fe, blitter *bl, int x, int y)
                     x, y, 0, 0, bl->w, bl->h);
 }
 
-void blitter_load(frontend *fe, blitter *bl, int x, int y)
+void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     assert(bl->pixmap);
     if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
         x = bl->x;
@@ -437,16 +449,18 @@ void blitter_load(frontend *fe, blitter *bl, int x, int y)
                     0, 0, x, y, bl->w, bl->h);
 }
 
-void draw_update(frontend *fe, int x, int y, int w, int h)
+void gtk_draw_update(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     if (fe->bbox_l > x  ) fe->bbox_l = x  ;
     if (fe->bbox_r < x+w) fe->bbox_r = x+w;
     if (fe->bbox_u > y  ) fe->bbox_u = y  ;
     if (fe->bbox_d < y+h) fe->bbox_d = y+h;
 }
 
-void end_draw(frontend *fe)
+void gtk_end_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     gdk_gc_unref(fe->gc);
     fe->gc = NULL;
 
@@ -460,6 +474,26 @@ void end_draw(frontend *fe)
     }
 }
 
+const struct drawing_api gtk_drawing = {
+    gtk_draw_text,
+    gtk_draw_rect,
+    gtk_draw_line,
+    gtk_draw_poly,
+    gtk_draw_circle,
+    gtk_draw_update,
+    gtk_clip,
+    gtk_unclip,
+    gtk_start_draw,
+    gtk_end_draw,
+    gtk_status_bar,
+    gtk_blitter_new,
+    gtk_blitter_free,
+    gtk_blitter_save,
+    gtk_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL,                             /* line_width */
+};
+
 static void destroy(GtkWidget *widget, gpointer data)
 {
     frontend *fe = (frontend *)data;
@@ -654,6 +688,8 @@ static gint timer_func(gpointer data)
 
 void deactivate_timer(frontend *fe)
 {
+    if (!fe)
+       return;                        /* can happen due to --generate */
     if (fe->timer_active)
         gtk_timeout_remove(fe->timer_id);
     fe->timer_active = FALSE;
@@ -661,6 +697,8 @@ void deactivate_timer(frontend *fe)
 
 void activate_timer(frontend *fe)
 {
+    if (!fe)
+       return;                        /* can happen due to --generate */
     if (!fe->timer_active) {
         fe->timer_id = gtk_timeout_add(20, timer_func, fe);
        gettimeofday(&fe->last_time, NULL);
@@ -1379,16 +1417,20 @@ static frontend *new_window(char *arg, char **error)
     GtkBox *vbox;
     GtkWidget *menubar, *menu, *menuitem;
     int x, y, n;
+    char errbuf[1024];
 
     fe = snew(frontend);
 
     fe->timer_active = FALSE;
     fe->timer_id = -1;
 
-    fe->me = midend_new(fe, &thegame);
+    fe->me = midend_new(fe, &thegame, &gtk_drawing, fe);
 
     if (arg) {
        char *err;
+
+       errbuf[0] = '\0';
+
        /*
         * Try treating the argument as a game ID.
         */
@@ -1401,14 +1443,16 @@ static frontend *new_window(char *arg, char **error)
        } else {
            FILE *fp = fopen(arg, "r");
            if (!fp) {
-               err = "Supplied argument is neither a game ID nor a save file";
+               sprintf(errbuf, "Supplied argument is neither a game ID (%.400s)"
+                       " nor a save file (%.400s)", err, strerror(errno));
            } else {
                err = midend_deserialise(fe->me, savefile_read, fp);
+               sprintf(errbuf, "%.800s", err);
                fclose(fp);
            }
         }
-       if (err) {
-           *error = err;
+       if (*errbuf) {
+           *error = dupstr(errbuf);
            midend_free(fe->me);
            sfree(fe);
            return NULL;
@@ -1647,15 +1691,133 @@ static frontend *new_window(char *arg, char **error)
     return fe;
 }
 
+char *fgetline(FILE *fp)
+{
+    char *ret = snewn(512, char);
+    int size = 512, len = 0;
+    while (fgets(ret + len, size - len, fp)) {
+       len += strlen(ret + len);
+       if (ret[len-1] == '\n')
+           break;                     /* got a newline, we're done */
+       size = len + 512;
+       ret = sresize(ret, size, char);
+    }
+    if (len == 0) {                   /* first fgets returned NULL */
+       sfree(ret);
+       return NULL;
+    }
+    ret[len] = '\0';
+    return ret;
+}
+
 int main(int argc, char **argv)
 {
     char *pname = argv[0];
     char *error;
+    int ngenerate = 0, print = FALSE, px = 1, py = 1;
+    int soln = FALSE, colour = FALSE;
+    float scale = 1.0F;
+    char *arg = NULL;
+    int doing_opts = TRUE;
+    int ac = argc;
+    char **av = argv;
+    char errbuf[500];
 
-    if (argc > 1 && !strcmp(argv[1], "--version")) {
-       printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
-              thegame.name, ver);
-       return 0;
+    /*
+     * Command line parsing in this function is rather fiddly,
+     * because GTK wants to have a go at argc/argv _first_ - and
+     * yet we can't let it, because gtk_init() will bomb out if it
+     * can't open an X display, whereas in fact we want to permit
+     * our --generate and --print modes to run without an X
+     * display.
+     * 
+     * So what we do is:
+     *         - we parse the command line ourselves, without modifying
+     *           argc/argv
+     *         - if we encounter an error which might plausibly be the
+     *           result of a GTK command line (i.e. not detailed errors in
+     *           particular options of ours) we store the error message
+     *           and terminate parsing.
+     *         - if we got enough out of the command line to know it
+     *           specifies a non-X mode of operation, we either display
+     *           the stored error and return failure, or if there is no
+     *           stored error we do the non-X operation and return
+     *           success.
+     *  - otherwise, we go straight to gtk_init().
+     */
+
+    errbuf[0] = '\0';
+    while (--ac > 0) {
+       char *p = *++av;
+       if (doing_opts && !strcmp(p, "--version")) {
+           printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
+                  thegame.name, ver);
+           return 0;
+       } else if (doing_opts && !strcmp(p, "--generate")) {
+           if (--ac > 0) {
+               ngenerate = atoi(*++av);
+               if (!ngenerate) {
+                   fprintf(stderr, "%s: '--generate' expected a number\n",
+                           pname);
+                   return 1;
+               }
+           } else
+               ngenerate = 1;
+       } else if (doing_opts && !strcmp(p, "--print")) {
+           if (!thegame.can_print) {
+               fprintf(stderr, "%s: this game does not support printing\n",
+                       pname);
+               return 1;
+           }
+           print = TRUE;
+           if (--ac > 0) {
+               char *dim = *++av;
+               if (sscanf(dim, "%dx%d", &px, &py) != 2) {
+                   fprintf(stderr, "%s: unable to parse argument '%s' to "
+                           "'--print'\n", pname, dim);
+                   return 1;
+               }
+           } else {
+               px = py = 1;
+           }
+       } else if (doing_opts && !strcmp(p, "--scale")) {
+           if (--ac > 0) {
+               scale = atof(*++av);
+           } else {
+               fprintf(stderr, "%s: no argument supplied to '--scale'\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && (!strcmp(p, "--with-solutions") ||
+                                 !strcmp(p, "--with-solution") ||
+                                 !strcmp(p, "--with-solns") ||
+                                 !strcmp(p, "--with-soln") ||
+                                 !strcmp(p, "--solutions") ||
+                                 !strcmp(p, "--solution") ||
+                                 !strcmp(p, "--solns") ||
+                                 !strcmp(p, "--soln"))) {
+           soln = TRUE;
+       } else if (doing_opts && !strcmp(p, "--colour")) {
+           if (!thegame.can_print_in_colour) {
+               fprintf(stderr, "%s: this game does not support colour"
+                       " printing\n", pname);
+               return 1;
+           }
+           colour = TRUE;
+       } else if (doing_opts && !strcmp(p, "--")) {
+           doing_opts = FALSE;
+       } else if (!doing_opts || p[0] != '-') {
+           if (arg) {
+               fprintf(stderr, "%s: more than one argument supplied\n",
+                       pname);
+               return 1;
+           }
+           arg = p;
+       } else {
+           sprintf(errbuf, "%.100s: unrecognised option '%.100s'\n",
+                   pname, p);
+           break;
+       }
     }
 
     /*
@@ -1663,7 +1825,7 @@ int main(int argc, char **argv)
      * command line. Useful for generating puzzles to be printed
      * out and solved offline (for puzzles where that even makes
      * sense - Solo, for example, is a lot more pencil-and-paper
-     * friendly than Net!)
+     * friendly than Twiddle!)
      * 
      * Usage:
      * 
@@ -1679,50 +1841,96 @@ int main(int argc, char **argv)
      * you may specify it to be 1). Sorry; that was the
      * simplest-to-parse command-line syntax I came up with.
      */
-    if (argc > 1 && !strcmp(argv[1], "--generate")) {
-       int n = 1;
-       char *params = NULL, *seed = NULL;
-       game_params *par;
-       random_state *rs;
-       char *parstr;
-
-       if (argc > 2)
-           n = atoi(argv[2]);
-       if (argc > 3)
-           params = argv[3];
-
-        par = thegame.default_params();
-       if (params) {
-            if ( (seed = strchr(params, '#')) != NULL )
-                *seed++ = '\0';
-           thegame.decode_params(par, params);
-        }
-        if ((error = thegame.validate_params(par, TRUE)) != NULL) {
-           fprintf(stderr, "%s: %s\n", pname, error);
-            return 1;
-        }
-       parstr = thegame.encode_params(par, FALSE);
-
-       {
-           void *seeddata;
-           int seedlen;
-            if (seed) {
-                seeddata = seed;
-                seedlen = strlen(seed);
-            } else {
-                get_random_seed(&seeddata, &seedlen);
-            }
-           rs = random_init(seeddata, seedlen);
+    if (ngenerate > 0 || print) {
+       int i, n = 1;
+       midend *me;
+       char *id;
+       document *doc = NULL;
+
+       if (*errbuf) {
+           fputs(errbuf, stderr);
+           return 1;
        }
 
-       while (n-- > 0) {
-           char *aux = NULL;
-           char *desc = thegame.new_desc(par, rs, &aux, FALSE);
-           printf("%s:%s\n", parstr, desc);
-           sfree(desc);
-            sfree(aux);
+       n = ngenerate;
+
+       me = midend_new(NULL, &thegame, NULL, NULL);
+       i = 0;
+
+       if (print)
+           doc = document_new(px, py, scale);
+
+       /*
+        * In this loop, we either generate a game ID or read one
+        * from stdin depending on whether we're in generate mode;
+        * then we either write it to stdout or print it, depending
+        * on whether we're in print mode. Thus, this loop handles
+        * generate-to-stdout, print-from-stdin and generate-and-
+        * immediately-print modes.
+        * 
+        * (It could also handle a copy-stdin-to-stdout mode,
+        * although there's currently no combination of options
+        * which will cause this loop to be activated in that mode.
+        * It wouldn't be _entirely_ pointless, though, because
+        * stdin could contain bare params strings or random-seed
+        * IDs, and stdout would contain nothing but fully
+        * generated descriptive game IDs.)
+        */
+       while (ngenerate == 0 || i < n) {
+           char *pstr, *err;
+
+           if (ngenerate == 0) {
+               pstr = fgetline(stdin);
+               if (!pstr)
+                   break;
+               pstr[strcspn(pstr, "\r\n")] = '\0';
+           } else {
+               if (arg) {
+                   pstr = snewn(strlen(arg) + 40, char);
+
+                   strcpy(pstr, arg);
+                   if (i > 0 && strchr(arg, '#'))
+                       sprintf(pstr + strlen(pstr), "-%d", i);
+               } else
+                   pstr = NULL;
+           }
+
+           if (pstr) {
+               err = midend_game_id(me, pstr);
+               if (err) {
+                   fprintf(stderr, "%s: error parsing '%s': %s\n",
+                           pname, pstr, err);
+                   return 1;
+               }
+           }
+           sfree(pstr);
+
+           midend_new_game(me);
+
+           if (doc) {
+               err = midend_print_puzzle(me, doc, soln);
+               if (err) {
+                   fprintf(stderr, "%s: error in printing: %s\n", pname, err);
+                   return 1;
+               }
+           } else {
+               id = midend_get_game_id(me);
+               puts(id);
+               sfree(id);
+           }
+
+           i++;
        }
 
+       if (doc) {
+           psdata *ps = ps_init(stdout, colour);
+           document_print(doc, ps_drawing_api(ps));
+           document_free(doc);
+           ps_free(ps);
+       }
+
+       midend_free(me);
+
        return 0;
     } else {
 
diff --git a/guess.c b/guess.c
index 0984d249fadf2e956a34b9f284b78b7a4e1d5794..778a6f5897fde98f70d6f6b08a9081c333328711 100644 (file)
--- a/guess.c
+++ b/guess.c
@@ -309,7 +309,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)
 {
     game_state *state = snew(game_state);
     unsigned char *bmp;
@@ -411,8 +411,8 @@ struct game_ui {
 
 static game_ui *new_ui(game_state *state)
 {
-    game_ui *ui = snew(struct game_ui);
-    memset(ui, 0, sizeof(struct game_ui));
+    game_ui *ui = snew(game_ui);
+    memset(ui, 0, sizeof(game_ui));
     ui->params = state->params;        /* structure copy */
     ui->curr_pegs = new_pegrow(state->params.npegs);
     ui->holds = snewn(state->params.npegs, int);
@@ -833,8 +833,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = (int)ceil((double)tilesize * vmul);
 }
 
-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)
 {
     int colh, guessh;
 
@@ -860,8 +860,8 @@ static void game_set_size(game_drawstate *ds, game_params *params,
     ds->solny = ds->guessy + ((ds->pegsz + ds->gapsz) * params->nguesses) + ds->gapsz;
 
     assert(ds->pegsz > 0);
-    if (ds->blit_peg) blitter_free(ds->blit_peg);
-    ds->blit_peg = blitter_new(ds->pegsz, ds->pegsz);
+    if (ds->blit_peg) blitter_free(dr, ds->blit_peg);
+    ds->blit_peg = blitter_new(dr, ds->pegsz, ds->pegsz);
 }
 
 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
@@ -968,7 +968,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -993,11 +993,11 @@ 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)
 {
     int i;
 
-    if (ds->blit_peg) blitter_free(ds->blit_peg);
+    if (ds->blit_peg) blitter_free(dr, ds->blit_peg);
     free_pegrow(ds->colours);
     free_pegrow(ds->solution);
     for (i = 0; i < ds->nguesses; i++)
@@ -1006,7 +1006,7 @@ static void game_free_drawstate(game_drawstate *ds)
     sfree(ds);
 }
 
-static void draw_peg(frontend *fe, game_drawstate *ds, int cx, int cy,
+static void draw_peg(drawing *dr, game_drawstate *ds, int cx, int cy,
                     int moving, int col)
 {
     /*
@@ -1018,24 +1018,24 @@ static void draw_peg(frontend *fe, game_drawstate *ds, int cx, int cy,
      * behind it.
      */
     if (!moving)
-        draw_rect(fe, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2,
+        draw_rect(dr, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2,
                   COL_BACKGROUND);
     if (PEGRAD > 0) {
-        draw_circle(fe, cx+PEGRAD, cy+PEGRAD, PEGRAD,
+        draw_circle(dr, cx+PEGRAD, cy+PEGRAD, PEGRAD,
                    COL_EMPTY + col, COL_EMPTY + col);
     } else
-        draw_rect(fe, cx, cy, PEGSZ, PEGSZ, COL_EMPTY + col);
-    draw_update(fe, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
+        draw_rect(dr, cx, cy, PEGSZ, PEGSZ, COL_EMPTY + col);
+    draw_update(dr, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
 }
 
-static void draw_cursor(frontend *fe, game_drawstate *ds, int x, int y)
+static void draw_cursor(drawing *dr, game_drawstate *ds, int x, int y)
 {
-    draw_circle(fe, x+PEGRAD, y+PEGRAD, PEGRAD+CGAP, -1, COL_CURSOR);
+    draw_circle(dr, x+PEGRAD, y+PEGRAD, PEGRAD+CGAP, -1, COL_CURSOR);
 
-    draw_update(fe, x-CGAP, y-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
+    draw_update(dr, x-CGAP, y-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
 }
 
-static void guess_redraw(frontend *fe, game_drawstate *ds, int guess,
+static void guess_redraw(drawing *dr, game_drawstate *ds, int guess,
                          pegrow src, int *holds, int cur_col, int force)
 {
     pegrow dest;
@@ -1059,22 +1059,22 @@ static void guess_redraw(frontend *fe, game_drawstate *ds, int guess,
         if (holds && holds[i])
             scol |= 0x2000;
         if ((dest->pegs[i] != scol) || force) {
-           draw_peg(fe, ds, rowx + PEGOFF * i, rowy, FALSE, scol &~ 0x3000);
+           draw_peg(dr, ds, rowx + PEGOFF * i, rowy, FALSE, scol &~ 0x3000);
             /*
              * Hold marker.
              */
-            draw_rect(fe, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
+            draw_rect(dr, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
                       PEGSZ, 2, (scol & 0x2000 ? COL_HOLD : COL_BACKGROUND));
-            draw_update(fe, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
+            draw_update(dr, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
                         PEGSZ, 2);
             if (scol & 0x1000)
-                draw_cursor(fe, ds, rowx + PEGOFF * i, rowy);
+                draw_cursor(dr, ds, rowx + PEGOFF * i, rowy);
         }
         dest->pegs[i] = scol;
     }
 }
 
-static void hint_redraw(frontend *fe, game_drawstate *ds, int guess,
+static void hint_redraw(drawing *dr, game_drawstate *ds, int guess,
                         pegrow src, int force, int cursor, int markable)
 {
     pegrow dest = ds->guesses[guess];
@@ -1112,7 +1112,7 @@ static void hint_redraw(frontend *fe, game_drawstate *ds, int guess,
         hw = HINT_W+GAP*2; hh = hinth+GAP*2;
 
         /* erase a large background rectangle */
-        draw_rect(fe, hx, hy, hw, hh, COL_BACKGROUND);
+        draw_rect(dr, hx, hy, hw, hh, COL_BACKGROUND);
 
         for (i = 0; i < dest->npegs; i++) {
             scol = src ? src->feedback[i] : 0;
@@ -1129,34 +1129,34 @@ static void hint_redraw(frontend *fe, game_drawstate *ds, int guess,
                 rowy += HINTOFF;
             }
             if (HINTRAD > 0) {
-                draw_circle(fe, rowx+HINTRAD, rowy+HINTRAD, HINTRAD, col, col);
+                draw_circle(dr, rowx+HINTRAD, rowy+HINTRAD, HINTRAD, col, col);
             } else {
-                draw_rect(fe, rowx, rowy, HINTSZ, HINTSZ, col);
+                draw_rect(dr, rowx, rowy, HINTSZ, HINTSZ, col);
             }
         }
         if (cursor) {
             int x1,y1,x2,y2;
             x1 = hx + CGAP; y1 = hy + CGAP;
             x2 = hx + hw - CGAP; y2 = hy + hh - CGAP;
-            draw_line(fe, x1, y1, x2, y1, COL_CURSOR);
-            draw_line(fe, x2, y1, x2, y2, COL_CURSOR);
-            draw_line(fe, x2, y2, x1, y2, COL_CURSOR);
-            draw_line(fe, x1, y2, x1, y1, COL_CURSOR);
+            draw_line(dr, x1, y1, x2, y1, COL_CURSOR);
+            draw_line(dr, x2, y1, x2, y2, COL_CURSOR);
+            draw_line(dr, x2, y2, x1, y2, COL_CURSOR);
+            draw_line(dr, x1, y2, x1, y1, COL_CURSOR);
         }
 
-        draw_update(fe, hx, hy, hw, hh);
+        draw_update(dr, hx, hy, hw, hh);
     }
 }
 
-static void currmove_redraw(frontend *fe, game_drawstate *ds, int guess, int col)
+static void currmove_redraw(drawing *dr, game_drawstate *ds, int guess, int col)
 {
     int ox = GUESS_X(guess, 0), oy = GUESS_Y(guess, 0), off = PEGSZ/4;
 
-    draw_rect(fe, ox-off-1, oy, 2, PEGSZ, col);
-    draw_update(fe, ox-off-1, oy, 2, PEGSZ);
+    draw_rect(dr, ox-off-1, oy, 2, PEGSZ, col);
+    draw_update(dr, ox-off-1, oy, 2, PEGSZ);
 }
 
-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)
 {
@@ -1166,15 +1166,15 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     last_go = (state->next_go == state->params.nguesses-1);
 
     if (!ds->started) {
-      draw_rect(fe, 0, 0, ds->w, ds->h, COL_BACKGROUND);
-      draw_rect(fe, SOLN_OX, SOLN_OY - ds->gapsz - 1, SOLN_W, 2, COL_FRAME);
-      draw_update(fe, 0, 0, ds->w, ds->h);
+      draw_rect(dr, 0, 0, ds->w, ds->h, COL_BACKGROUND);
+      draw_rect(dr, SOLN_OX, SOLN_OY - ds->gapsz - 1, SOLN_W, 2, COL_FRAME);
+      draw_update(dr, 0, 0, ds->w, ds->h);
     }
 
     if (ds->drag_col != 0) {
         debug(("Loading from blitter."));
-        blitter_load(fe, ds->blit_peg, ds->blit_ox, ds->blit_oy);
-        draw_update(fe, ds->blit_ox, ds->blit_oy, PEGSZ, PEGSZ);
+        blitter_load(dr, ds->blit_peg, ds->blit_ox, ds->blit_oy);
+        draw_update(dr, ds->blit_ox, ds->blit_oy, PEGSZ, PEGSZ);
     }
 
     /* draw the colours */
@@ -1183,9 +1183,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         if (ui->display_cur && ui->colour_cur == i)
             val |= 0x1000;
         if (ds->colours->pegs[i] != val) {
-           draw_peg(fe, ds, COL_X(i), COL_Y(i), FALSE, i+1);
+           draw_peg(dr, ds, COL_X(i), COL_Y(i), FALSE, i+1);
             if (val & 0x1000)
-                draw_cursor(fe, ds, COL_X(i), COL_Y(i));
+                draw_cursor(dr, ds, COL_X(i), COL_Y(i));
             ds->colours->pegs[i] = val;
         }
     }
@@ -1194,38 +1194,38 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     for (i = 0; i < state->params.nguesses; i++) {
         if (state->next_go > i || state->solved) {
             /* this info is stored in the game_state already */
-            guess_redraw(fe, ds, i, state->guesses[i], NULL, -1, 0);
-            hint_redraw(fe, ds, i, state->guesses[i],
+            guess_redraw(dr, ds, i, state->guesses[i], NULL, -1, 0);
+            hint_redraw(dr, ds, i, state->guesses[i],
                         i == (state->next_go-1) ? 1 : 0, FALSE, FALSE);
         } else if (state->next_go == i) {
             /* this is the one we're on; the (incomplete) guess is
              * stored in the game_ui. */
-            guess_redraw(fe, ds, i, ui->curr_pegs,
+            guess_redraw(dr, ds, i, ui->curr_pegs,
                          ui->holds, ui->display_cur ? ui->peg_cur : -1, 0);
-            hint_redraw(fe, ds, i, NULL, 1,
+            hint_redraw(dr, ds, i, NULL, 1,
                         ui->display_cur && ui->peg_cur == state->params.npegs,
                         ui->markable);
         } else {
             /* we've not got here yet; it's blank. */
-            guess_redraw(fe, ds, i, NULL, NULL, -1, 0);
-            hint_redraw(fe, ds, i, NULL, 0, FALSE, FALSE);
+            guess_redraw(dr, ds, i, NULL, NULL, -1, 0);
+            hint_redraw(dr, ds, i, NULL, 0, FALSE, FALSE);
         }
     }
 
     /* draw the 'current move' and 'able to mark' sign. */
     if (new_move)
-        currmove_redraw(fe, ds, ds->next_go, COL_BACKGROUND);
+        currmove_redraw(dr, ds, ds->next_go, COL_BACKGROUND);
     if (!state->solved)
-        currmove_redraw(fe, ds, state->next_go, COL_HOLD);
+        currmove_redraw(dr, ds, state->next_go, COL_HOLD);
 
     /* draw the solution (or the big rectangle) */
     if ((state->solved != ds->solved) || !ds->started) {
-        draw_rect(fe, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H,
+        draw_rect(dr, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H,
                   state->solved ? COL_BACKGROUND : COL_EMPTY);
-        draw_update(fe, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H);
+        draw_update(dr, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H);
     }
     if (state->solved)
-        guess_redraw(fe, ds, -1, state->solution, NULL, -1, !ds->solved);
+        guess_redraw(dr, ds, -1, state->solution, NULL, -1, !ds->solved);
     ds->solved = state->solved;
 
     ds->next_go = state->next_go;
@@ -1236,8 +1236,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         int ox = ui->drag_x - (PEGSZ/2);
         int oy = ui->drag_y - (PEGSZ/2);
         debug(("Saving to blitter at (%d,%d)", ox, oy));
-        blitter_save(fe, ds->blit_peg, ox, oy);
-        draw_peg(fe, ds, ox, oy, TRUE, ui->drag_col);
+        blitter_save(dr, ds->blit_peg, ox, oy);
+        draw_peg(dr, ds, ox, oy, TRUE, ui->drag_col);
 
         ds->blit_ox = ox; ds->blit_oy = oy;
     }
@@ -1268,6 +1268,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame guess
 #endif
@@ -1303,6 +1311,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index 8549a751940fb32584f0b7d42e74f3a516a0a5df..1a87ece1c33013dc09436c47dc0fd3f90439b19f 100644 (file)
--- a/lightup.c
+++ b/lightup.c
@@ -1181,7 +1181,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)
 {
     game_state *ret = new_state(params);
     int x,y;
@@ -1528,8 +1528,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILE_SIZE * params->h + 2 * BORDER;
 }
 
-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;
     ds->crad = 3*(tilesize-1)/8;
@@ -1562,7 +1562,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1579,7 +1579,7 @@ 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->flags);
     sfree(ds);
@@ -1600,7 +1600,7 @@ static unsigned int tile_flags(game_drawstate *ds, game_state *state, game_ui *u
     unsigned int ret = 0;
 
     if (flashing) ret |= DF_FLASH;
-    if (ui->cur_visible && x == ui->cur_x && y == ui->cur_y)
+    if (ui && ui->cur_visible && x == ui->cur_x && y == ui->cur_y)
         ret |= DF_CURSOR;
 
     if (flags & F_BLACK) {
@@ -1627,7 +1627,7 @@ static unsigned int tile_flags(game_drawstate *ds, game_state *state, game_ui *u
     return ret;
 }
 
-static void tile_redraw(frontend *fe, game_drawstate *ds, game_state *state,
+static void tile_redraw(drawing *dr, game_drawstate *ds, game_state *state,
                         int x, int y)
 {
     unsigned int ds_flags = GRID(ds, flags, x, y);
@@ -1635,7 +1635,7 @@ static void tile_redraw(frontend *fe, game_drawstate *ds, game_state *state,
     int lit = (ds_flags & DF_FLASH) ? COL_GRID : COL_LIT;
 
     if (ds_flags & DF_BLACK) {
-        draw_rect(fe, dx, dy, TILE_SIZE, TILE_SIZE, COL_BLACK);
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BLACK);
         if (ds_flags & DF_NUMBERED) {
             int ccol = (ds_flags & DF_NUMBERWRONG) ? COL_ERROR : COL_LIGHT;
             char str[10];
@@ -1644,35 +1644,35 @@ static void tile_redraw(frontend *fe, game_drawstate *ds, game_state *state,
              * so it's OK to ignore this when calculating whether or not
              * to redraw the tile. */
             sprintf(str, "%d", GRID(state, lights, x, y));
-            draw_text(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+            draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
                       FONT_VARIABLE, TILE_SIZE*3/5,
                      ALIGN_VCENTRE | ALIGN_HCENTRE, ccol, str);
         }
     } else {
-        draw_rect(fe, dx, dy, TILE_SIZE, TILE_SIZE,
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE,
                   (ds_flags & DF_LIT) ? lit : COL_BACKGROUND);
-        draw_rect_outline(fe, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
         if (ds_flags & DF_LIGHT) {
             int lcol = (ds_flags & DF_OVERLAP) ? COL_ERROR : COL_LIGHT;
-            draw_circle(fe, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS,
+            draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS,
                         lcol, COL_BLACK);
         } else if (ds_flags & DF_IMPOSSIBLE) {
             int rlen = TILE_SIZE / 4;
-            draw_rect(fe, dx + TILE_SIZE/2 - rlen/2, dy + TILE_SIZE/2 - rlen/2,
+            draw_rect(dr, dx + TILE_SIZE/2 - rlen/2, dy + TILE_SIZE/2 - rlen/2,
                       rlen, rlen, COL_BLACK);
         }
     }
 
     if (ds_flags & DF_CURSOR) {
         int coff = TILE_SIZE/8;
-        draw_rect_outline(fe, dx + coff, dy + coff,
+        draw_rect_outline(dr, dx + coff, dy + coff,
                           TILE_SIZE - coff*2, TILE_SIZE - coff*2, COL_CURSOR);
     }
 
-    draw_update(fe, dx, dy, TILE_SIZE, TILE_SIZE);
+    draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
 }
 
-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)
 {
@@ -1682,16 +1682,16 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (flashtime) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1;
 
     if (!ds->started) {
-        draw_rect(fe, 0, 0,
+        draw_rect(dr, 0, 0,
                   TILE_SIZE * ds->w + 2 * BORDER,
                   TILE_SIZE * ds->h + 2 * BORDER, COL_BACKGROUND);
 
-        draw_rect_outline(fe, COORD(0)-1, COORD(0)-1,
+        draw_rect_outline(dr, COORD(0)-1, COORD(0)-1,
                           TILE_SIZE * ds->w + 2,
                           TILE_SIZE * ds->h + 2,
                           COL_GRID);
 
-        draw_update(fe, 0, 0,
+        draw_update(dr, 0, 0,
                     TILE_SIZE * ds->w + 2 * BORDER,
                     TILE_SIZE * ds->h + 2 * BORDER);
         ds->started = 1;
@@ -1702,7 +1702,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
             unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing);
             if (ds_flags != GRID(ds, flags, x, y)) {
                 GRID(ds, flags, x, y) = ds_flags;
-                tile_redraw(fe, ds, state, x, y);
+                tile_redraw(dr, ds, state, x, y);
             }
         }
     }
@@ -1733,6 +1733,69 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    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->w, h = state->h;
+    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;
+    ads.tilesize = tilesize;
+    ds->crad = 3*(tilesize-1)/8;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 16);
+    draw_rect_outline(dr, COORD(0), COORD(0),
+                     TILE_SIZE * w, TILE_SIZE * h, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILE_SIZE / 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);
+
+    /*
+     * Grid contents.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+            unsigned int ds_flags = tile_flags(ds, state, NULL, x, y, FALSE);
+           int dx = COORD(x), dy = COORD(y);
+           if (ds_flags & DF_BLACK) {
+               draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, ink);
+               if (ds_flags & DF_NUMBERED) {
+                   char str[10];
+                   sprintf(str, "%d", GRID(state, lights, x, y));
+                   draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                             FONT_VARIABLE, TILE_SIZE*3/5,
+                             ALIGN_VCENTRE | ALIGN_HCENTRE, paper, str);
+               }
+           } else if (ds_flags & DF_LIGHT) {
+               draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                           TILE_RADIUS, -1, ink);
+           }
+       }
+}
+
 #ifdef COMBINED
 #define thegame lightup
 #endif
@@ -1768,6 +1831,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/map.c b/map.c
index 9b6698a3c2bba1be1a2dd05ccc9e7420cb0f1046..c65d3dae089a895bc76ab3a3cf28ff9b60b9b384 100644 (file)
--- a/map.c
+++ b/map.c
@@ -1389,7 +1389,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, wh = w*h, n = params->n;
     int i, pos;
@@ -1785,16 +1785,26 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = params->h * TILESIZE + 2 * BORDER + 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;
 
     if (ds->bl)
-        blitter_free(ds->bl);
-    ds->bl = blitter_new(TILESIZE+3, TILESIZE+3);
+        blitter_free(dr, ds->bl);
+    ds->bl = blitter_new(dr, TILESIZE+3, TILESIZE+3);
 }
 
+const float map_colours[FOUR][3] = {
+    {0.7F, 0.5F, 0.4F},
+    {0.8F, 0.7F, 0.4F},
+    {0.5F, 0.6F, 0.4F},
+    {0.55F, 0.45F, 0.35F},
+};
+const int map_hatching[FOUR] = {
+    HATCH_VERT, HATCH_SLASH, HATCH_HORIZ, HATCH_BACKSLASH
+};
+
 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
 {
     float *ret = snewn(3 * NCOLOURS, float);
@@ -1805,27 +1815,16 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     ret[COL_GRID * 3 + 1] = 0.0F;
     ret[COL_GRID * 3 + 2] = 0.0F;
 
-    ret[COL_0 * 3 + 0] = 0.7F;
-    ret[COL_0 * 3 + 1] = 0.5F;
-    ret[COL_0 * 3 + 2] = 0.4F;
-
-    ret[COL_1 * 3 + 0] = 0.8F;
-    ret[COL_1 * 3 + 1] = 0.7F;
-    ret[COL_1 * 3 + 2] = 0.4F;
-
-    ret[COL_2 * 3 + 0] = 0.5F;
-    ret[COL_2 * 3 + 1] = 0.6F;
-    ret[COL_2 * 3 + 2] = 0.4F;
-
-    ret[COL_3 * 3 + 0] = 0.55F;
-    ret[COL_3 * 3 + 1] = 0.45F;
-    ret[COL_3 * 3 + 2] = 0.35F;
+    memcpy(ret + COL_0 * 3, map_colours[0], 3 * sizeof(float));
+    memcpy(ret + COL_1 * 3, map_colours[1], 3 * sizeof(float));
+    memcpy(ret + COL_2 * 3, map_colours[2], 3 * sizeof(float));
+    memcpy(ret + COL_3 * 3, map_colours[3], 3 * sizeof(float));
 
     *ncolours = NCOLOURS;
     return ret;
 }
 
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -1840,27 +1839,27 @@ 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->drawn);
     if (ds->bl)
-        blitter_free(ds->bl);
+        blitter_free(dr, ds->bl);
     sfree(ds);
 }
 
-static void draw_square(frontend *fe, game_drawstate *ds,
+static void draw_square(drawing *dr, game_drawstate *ds,
                        game_params *params, struct map *map,
                        int x, int y, int v)
 {
     int w = params->w, h = params->h, wh = w*h;
     int tv = v / FIVE, bv = v % FIVE;
 
-    clip(fe, COORD(x), COORD(y), TILESIZE, TILESIZE);
+    clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
 
     /*
      * Draw the region colour.
      */
-    draw_rect(fe, COORD(x), COORD(y), TILESIZE, TILESIZE,
+    draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE,
              (tv == FOUR ? COL_BACKGROUND : COL_0 + tv));
     /*
      * Draw the second region colour, if this is a diagonally
@@ -1877,7 +1876,7 @@ static void draw_square(frontend *fe, game_drawstate *ds,
         coords[3] = COORD(y)-1;
         coords[4] = COORD(x+1)+1;
         coords[5] = COORD(y+1)+1;
-        draw_polygon(fe, coords, 3,
+        draw_polygon(dr, coords, 3,
                      (bv == FOUR ? COL_BACKGROUND : COL_0 + bv), COL_GRID);
     }
 
@@ -1885,19 +1884,19 @@ static void draw_square(frontend *fe, game_drawstate *ds,
      * Draw the grid lines, if required.
      */
     if (x <= 0 || map->map[RE*wh+y*w+(x-1)] != map->map[LE*wh+y*w+x])
-       draw_rect(fe, COORD(x), COORD(y), 1, TILESIZE, COL_GRID);
+       draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE, COL_GRID);
     if (y <= 0 || map->map[BE*wh+(y-1)*w+x] != map->map[TE*wh+y*w+x])
-       draw_rect(fe, COORD(x), COORD(y), TILESIZE, 1, COL_GRID);
+       draw_rect(dr, COORD(x), COORD(y), TILESIZE, 1, COL_GRID);
     if (x <= 0 || y <= 0 ||
         map->map[RE*wh+(y-1)*w+(x-1)] != map->map[TE*wh+y*w+x] ||
         map->map[BE*wh+(y-1)*w+(x-1)] != map->map[LE*wh+y*w+x])
-       draw_rect(fe, COORD(x), COORD(y), 1, 1, COL_GRID);
+       draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID);
 
-    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)
 {
@@ -1906,8 +1905,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     int flash;
 
     if (ds->drag_visible) {
-        blitter_load(fe, ds->bl, ds->dragx, ds->dragy);
-        draw_update(fe, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
+        blitter_load(dr, ds->bl, ds->dragx, ds->dragy);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
         ds->drag_visible = FALSE;
     }
 
@@ -1921,11 +1920,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        int ww, wh;
 
        game_compute_size(&state->p, TILESIZE, &ww, &wh);
-       draw_rect(fe, 0, 0, ww, wh, COL_BACKGROUND);
-       draw_rect(fe, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1,
+       draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND);
+       draw_rect(dr, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1,
                  COL_GRID);
 
-       draw_update(fe, 0, 0, ww, wh);
+       draw_update(dr, 0, 0, ww, wh);
        ds->started = TRUE;
     }
 
@@ -1968,7 +1967,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
             v = tv * FIVE + bv;
 
            if (ds->drawn[y*w+x] != v) {
-               draw_square(fe, ds, &state->p, state->map, x, y, v);
+               draw_square(dr, ds, &state->p, state->map, x, y, v);
                ds->drawn[y*w+x] = v;
            }
        }
@@ -1979,11 +1978,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (ui->drag_colour > -2) {
         ds->dragx = ui->dragx - TILESIZE/2 - 2;
         ds->dragy = ui->dragy - TILESIZE/2 - 2;
-        blitter_save(fe, ds->bl, ds->dragx, ds->dragy);
-        draw_circle(fe, ui->dragx, ui->dragy, TILESIZE/2,
+        blitter_save(dr, ds->bl, ds->dragx, ds->dragy);
+        draw_circle(dr, ui->dragx, ui->dragy, TILESIZE/2,
                     (ui->drag_colour < 0 ? COL_BACKGROUND :
                      COL_0 + ui->drag_colour), COL_GRID);
-        draw_update(fe, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
         ds->drag_visible = TRUE;
     }
 }
@@ -2022,6 +2021,157 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 4mm squares by default, I think. Simplest way to
+     * compute this size is to compute the pixel puzzle size at a
+     * given tile size and then scale.
+     */
+    game_compute_size(params, 400, &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, wh = w*h, n = state->p.n;
+    int ink, c[FOUR], i;
+    int x, y, r;
+    int *coords, ncoords, coordsize;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    ink = print_mono_colour(dr, 0);
+    for (i = 0; i < FOUR; i++)
+       c[i] = print_rgb_colour(dr, map_hatching[i], map_colours[i][0],
+                               map_colours[i][1], map_colours[i][2]);
+
+    coordsize = 0;
+    coords = NULL;
+
+    print_line_width(dr, TILESIZE / 16);
+
+    /*
+     * Draw a single filled polygon around each region.
+     */
+    for (r = 0; r < n; r++) {
+       int octants[8], lastdir, d1, d2, ox, oy;
+
+       /*
+        * Start by finding a point on the region boundary. Any
+        * point will do. To do this, we'll search for a square
+        * containing the region and then decide which corner of it
+        * to use.
+        */
+       x = w;
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               if (state->map->map[wh*0+y*w+x] == r ||
+                   state->map->map[wh*1+y*w+x] == r ||
+                   state->map->map[wh*2+y*w+x] == r ||
+                   state->map->map[wh*3+y*w+x] == r)
+                   break;
+           }
+           if (x < w)
+               break;
+       }
+       assert(y < h && x < w);        /* we must have found one somewhere */
+       /*
+        * This is the first square in lexicographic order which
+        * contains part of this region. Therefore, one of the top
+        * two corners of the square must be what we're after. The
+        * only case in which it isn't the top left one is if the
+        * square is diagonally divided and the region is in the
+        * bottom right half.
+        */
+       if (state->map->map[wh*TE+y*w+x] != r &&
+           state->map->map[wh*LE+y*w+x] != r)
+           x++;                       /* could just as well have done y++ */
+
+       /*
+        * Now we have a point on the region boundary. Trace around
+        * the region until we come back to this point,
+        * accumulating coordinates for a polygon draw operation as
+        * we go.
+        */
+       lastdir = -1;
+       ox = x;
+       oy = y;
+       ncoords = 0;
+
+       do {
+           /*
+            * There are eight possible directions we could head in
+            * from here. We identify them by octant numbers, and
+            * we also use octant numbers to identify the spaces
+            * between them:
+            * 
+            *   6   7   0
+            *    \ 7|0 /
+            *     \ | /
+            *    6 \|/ 1
+            * 5-----+-----1
+            *    5 /|\ 2
+            *     / | \
+            *    / 4|3 \
+            *   4   3   2
+            */
+           octants[0] = x<w && y>0 ? state->map->map[wh*LE+(y-1)*w+x] : -1;
+           octants[1] = x<w && y>0 ? state->map->map[wh*BE+(y-1)*w+x] : -1;
+           octants[2] = x<w && y<h ? state->map->map[wh*TE+y*w+x] : -1;
+           octants[3] = x<w && y<h ? state->map->map[wh*LE+y*w+x] : -1;
+           octants[4] = x>0 && y<h ? state->map->map[wh*RE+y*w+(x-1)] : -1;
+           octants[5] = x>0 && y<h ? state->map->map[wh*TE+y*w+(x-1)] : -1;
+           octants[6] = x>0 && y>0 ? state->map->map[wh*BE+(y-1)*w+(x-1)] :-1;
+           octants[7] = x>0 && y>0 ? state->map->map[wh*RE+(y-1)*w+(x-1)] :-1;
+
+           d1 = d2 = -1;
+           for (i = 0; i < 8; i++)
+               if ((octants[i] == r) ^ (octants[(i+1)%8] == r)) {
+                   assert(d2 == -1);
+                   if (d1 == -1)
+                       d1 = i;
+                   else
+                       d2 = i;
+               }
+/* printf("%% %d,%d r=%d: d1=%d d2=%d lastdir=%d\n", x, y, r, d1, d2, lastdir); */
+           assert(d1 != -1 && d2 != -1);
+           if (d1 == lastdir)
+               d1 = d2;
+
+           /*
+            * Now we're heading in direction d1. Save the current
+            * coordinates.
+            */
+           if (ncoords + 2 > coordsize) {
+               coordsize += 128;
+               coords = sresize(coords, coordsize, int);
+           }
+           coords[ncoords++] = COORD(x);
+           coords[ncoords++] = COORD(y);
+
+           /*
+            * Compute the new coordinates.
+            */
+           x += (d1 % 4 == 3 ? 0 : d1 < 4 ? +1 : -1);
+           y += (d1 % 4 == 1 ? 0 : d1 > 1 && d1 < 5 ? +1 : -1);
+           assert(x >= 0 && x <= w && y >= 0 && y <= h);
+
+           lastdir = d1 ^ 4;
+       } while (x != ox || y != oy);
+
+       draw_polygon(dr, coords, ncoords/2,
+                    state->colouring[r] >= 0 ?
+                    c[state->colouring[r]] : -1, ink);
+    }
+    sfree(coords);
+}
+
 #ifdef COMBINED
 #define thegame map
 #endif
@@ -2057,6 +2207,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, TRUE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index ceef686bf32af32f3cb7796cfd72fcbd0aabf863..75d51bb42f886da347c9649058a34722bbecbf4d 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -25,7 +25,7 @@ struct midend_state_entry {
     int movetype;
 };
 
-struct midend_data {
+struct midend {
     frontend *frontend;
     random_state *random;
     const game *ourgame;
@@ -76,6 +76,8 @@ struct midend_data {
     float elapsed;
     char *laststatus;
 
+    drawing *drawing;
+
     int pressed_mouse_button;
 
     int tilesize, winwidth, winheight;
@@ -89,9 +91,10 @@ struct midend_data {
     } \
 } while (0)
 
-midend_data *midend_new(frontend *fe, const game *ourgame)
+midend *midend_new(frontend *fe, const game *ourgame,
+                  const drawing_api *drapi, void *drhandle)
 {
-    midend_data *me = snew(midend_data);
+    midend *me = snew(midend);
     void *randseed;
     int randseedsize;
 
@@ -122,13 +125,17 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
     me->timing = FALSE;
     me->elapsed = 0.0F;
     me->tilesize = me->winwidth = me->winheight = 0;
+    if (drapi)
+       me->drawing = drawing_init(drapi, drhandle);
+    else
+       me->drawing = NULL;
 
     sfree(randseed);
 
     return me;
 }
 
-static void midend_free_game(midend_data *me)
+static void midend_free_game(midend *me)
 {
     while (me->nstates > 0) {
         me->nstates--;
@@ -137,15 +144,17 @@ static void midend_free_game(midend_data *me)
     }
 
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
 }
 
-void midend_free(midend_data *me)
+void midend_free(midend *me)
 {
     int i;
 
     midend_free_game(me);
 
+    if (me->drawing)
+       drawing_free(me->drawing);
     random_free(me->random);
     sfree(me->states);
     sfree(me->desc);
@@ -169,7 +178,7 @@ void midend_free(midend_data *me)
     sfree(me);
 }
 
-static void midend_size_new_drawstate(midend_data *me)
+static void midend_size_new_drawstate(midend *me)
 {
     /*
      * Don't even bother, if we haven't worked out our tile size
@@ -178,11 +187,12 @@ static void midend_size_new_drawstate(midend_data *me)
     if (me->tilesize > 0) {
        me->ourgame->compute_size(me->params, me->tilesize,
                                  &me->winwidth, &me->winheight);
-       me->ourgame->set_size(me->drawstate, me->params, me->tilesize);
+       me->ourgame->set_size(me->drawing, me->drawstate,
+                             me->params, me->tilesize);
     }
 }
 
-void midend_size(midend_data *me, int *x, int *y, int expand)
+void midend_size(midend *me, int *x, int *y, int expand)
 {
     int min, max;
     int rx, ry;
@@ -228,13 +238,13 @@ void midend_size(midend_data *me, int *x, int *y, int expand)
     *y = me->winheight;
 }
 
-void midend_set_params(midend_data *me, game_params *params)
+void midend_set_params(midend *me, game_params *params)
 {
     me->ourgame->free_params(me->params);
     me->params = me->ourgame->dup_params(params);
 }
 
-static void midend_set_timer(midend_data *me)
+static void midend_set_timer(midend *me)
 {
     me->timing = (me->ourgame->is_timed &&
                  me->ourgame->timing_state(me->states[me->statepos-1].state,
@@ -245,16 +255,17 @@ static void midend_set_timer(midend_data *me)
        deactivate_timer(me->frontend);
 }
 
-void midend_force_redraw(midend_data *me)
+void midend_force_redraw(midend *me)
 {
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
-    me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
     midend_size_new_drawstate(me);
     midend_redraw(me);
 }
 
-void midend_new_game(midend_data *me)
+void midend_new_game(midend *me)
 {
     midend_free_game(me);
 
@@ -296,8 +307,14 @@ void midend_new_game(midend_data *me)
        me->aux_info = NULL;
 
         rs = random_init(me->seedstr, strlen(me->seedstr));
+       /*
+        * If this midend has been instantiated without providing a
+        * drawing API, it is non-interactive. This means that it's
+        * being used for bulk game generation, and hence we should
+        * pass the non-interactive flag to new_desc.
+        */
         me->desc = me->ourgame->new_desc(me->curparams, rs,
-                                        &me->aux_info, TRUE);
+                                        &me->aux_info, (me->drawing != NULL));
        me->privdesc = NULL;
         random_free(rs);
     }
@@ -309,7 +326,8 @@ void midend_new_game(midend_data *me)
     me->states[me->nstates].movetype = NEWGAME;
     me->nstates++;
     me->statepos = 1;
-    me->drawstate = me->ourgame->new_drawstate(me->states[0].state);
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
     midend_size_new_drawstate(me);
     me->elapsed = 0.0F;
     if (me->ui)
@@ -319,7 +337,7 @@ void midend_new_game(midend_data *me)
     me->pressed_mouse_button = 0;
 }
 
-static int midend_undo(midend_data *me)
+static int midend_undo(midend *me)
 {
     if (me->statepos > 1) {
         if (me->ui)
@@ -333,7 +351,7 @@ static int midend_undo(midend_data *me)
         return 0;
 }
 
-static int midend_redo(midend_data *me)
+static int midend_redo(midend *me)
 {
     if (me->statepos < me->nstates) {
         if (me->ui)
@@ -347,7 +365,7 @@ static int midend_redo(midend_data *me)
         return 0;
 }
 
-static void midend_finish_move(midend_data *me)
+static void midend_finish_move(midend *me)
 {
     float flashtime;
 
@@ -380,7 +398,7 @@ static void midend_finish_move(midend_data *me)
     midend_set_timer(me);
 }
 
-void midend_stop_anim(midend_data *me)
+void midend_stop_anim(midend *me)
 {
     if (me->oldstate || me->anim_time != 0) {
        midend_finish_move(me);
@@ -388,7 +406,7 @@ void midend_stop_anim(midend_data *me)
     }
 }
 
-void midend_restart_game(midend_data *me)
+void midend_restart_game(midend *me)
 {
     game_state *s;
 
@@ -428,7 +446,7 @@ void midend_restart_game(midend_data *me)
     midend_set_timer(me);
 }
 
-static int midend_really_process_key(midend_data *me, int x, int y, int button)
+static int midend_really_process_key(midend *me, int x, int y, int button)
 {
     game_state *oldstate =
         me->ourgame->dup_game(me->states[me->statepos - 1].state);
@@ -534,7 +552,7 @@ static int midend_really_process_key(midend_data *me, int x, int y, int button)
     return ret;
 }
 
-int midend_process_key(midend_data *me, int x, int y, int button)
+int midend_process_key(midend *me, int x, int y, int button)
 {
     int ret = 1;
 
@@ -648,26 +666,28 @@ int midend_process_key(midend_data *me, int x, int y, int button)
     return ret;
 }
 
-void midend_redraw(midend_data *me)
+void midend_redraw(midend *me)
 {
+    assert(me->drawing);
+
     if (me->statepos > 0 && me->drawstate) {
-        start_draw(me->frontend);
+        start_draw(me->drawing);
         if (me->oldstate && me->anim_time > 0 &&
             me->anim_pos < me->anim_time) {
             assert(me->dir != 0);
-            me->ourgame->redraw(me->frontend, me->drawstate, me->oldstate,
+            me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
                                me->states[me->statepos-1].state, me->dir,
                                me->ui, me->anim_pos, me->flash_pos);
         } else {
-            me->ourgame->redraw(me->frontend, me->drawstate, NULL,
+            me->ourgame->redraw(me->drawing, me->drawstate, NULL,
                                me->states[me->statepos-1].state, +1 /*shrug*/,
                                me->ui, 0.0, me->flash_pos);
         }
-        end_draw(me->frontend);
+        end_draw(me->drawing);
     }
 }
 
-void midend_timer(midend_data *me, float tplus)
+void midend_timer(midend *me, float tplus)
 {
     me->anim_pos += tplus;
     if (me->anim_pos >= me->anim_time ||
@@ -687,13 +707,13 @@ void midend_timer(midend_data *me, float tplus)
        float oldelapsed = me->elapsed;
        me->elapsed += tplus;
        if ((int)oldelapsed != (int)me->elapsed)
-           status_bar(me->frontend, me->laststatus ? me->laststatus : "");
+           status_bar(me->drawing, me->laststatus ? me->laststatus : "");
     }
 
     midend_set_timer(me);
 }
 
-float *midend_colours(midend_data *me, int *ncolours)
+float *midend_colours(midend *me, int *ncolours)
 {
     game_state *state = NULL;
     float *ret;
@@ -744,7 +764,7 @@ float *midend_colours(midend_data *me, int *ncolours)
     return ret;
 }
 
-int midend_num_presets(midend_data *me)
+int midend_num_presets(midend *me)
 {
     if (!me->npresets) {
         char *name;
@@ -823,7 +843,7 @@ int midend_num_presets(midend_data *me)
     return me->npresets;
 }
 
-void midend_fetch_preset(midend_data *me, int n,
+void midend_fetch_preset(midend *me, int n,
                          char **name, game_params **params)
 {
     assert(n >= 0 && n < me->npresets);
@@ -831,12 +851,12 @@ void midend_fetch_preset(midend_data *me, int n,
     *params = me->presets[n];
 }
 
-int midend_wants_statusbar(midend_data *me)
+int midend_wants_statusbar(midend *me)
 {
     return me->ourgame->wants_statusbar();
 }
 
-void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc)
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc)
 {
     sfree(me->desc);
     sfree(me->privdesc);
@@ -844,7 +864,7 @@ void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc)
     me->privdesc = privdesc ? dupstr(privdesc) : NULL;
 }
 
-config_item *midend_get_config(midend_data *me, int which, char **wintitle)
+config_item *midend_get_config(midend *me, int which, char **wintitle)
 {
     char *titlebuf, *parstr, *rest;
     config_item *ret;
@@ -909,7 +929,7 @@ config_item *midend_get_config(midend_data *me, int which, char **wintitle)
     return NULL;
 }
 
-static char *midend_game_id_int(midend_data *me, char *id, int defmode)
+static char *midend_game_id_int(midend *me, char *id, int defmode)
 {
     char *error, *par, *desc, *seed;
     game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
@@ -1043,12 +1063,25 @@ static char *midend_game_id_int(midend_data *me, char *id, int defmode)
     return NULL;
 }
 
-char *midend_game_id(midend_data *me, char *id)
+char *midend_game_id(midend *me, char *id)
 {
     return midend_game_id_int(me, id, DEF_PARAMS);
 }
 
-char *midend_set_config(midend_data *me, int which, config_item *cfg)
+char *midend_get_game_id(midend *me)
+{
+    char *parstr, *ret;
+
+    parstr = me->ourgame->encode_params(me->curparams, FALSE);
+    assert(parstr);
+    assert(me->desc);
+    ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
+    sprintf(ret, "%s:%s", parstr, me->desc);
+    sfree(parstr);
+    return ret;
+}
+
+char *midend_set_config(midend *me, int which, config_item *cfg)
 {
     char *error;
     game_params *params;
@@ -1079,7 +1112,7 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg)
     return NULL;
 }
 
-char *midend_text_format(midend_data *me)
+char *midend_text_format(midend *me)
 {
     if (me->ourgame->can_format_as_text && me->statepos > 0)
        return me->ourgame->text_format(me->states[me->statepos-1].state);
@@ -1087,7 +1120,7 @@ char *midend_text_format(midend_data *me)
        return NULL;
 }
 
-char *midend_solve(midend_data *me)
+char *midend_solve(midend *me)
 {
     game_state *s;
     char *msg, *movestr;
@@ -1139,7 +1172,7 @@ char *midend_solve(midend_data *me)
     return NULL;
 }
 
-char *midend_rewrite_statusbar(midend_data *me, char *text)
+char *midend_rewrite_statusbar(midend *me, char *text)
 {
     /*
      * An important special case is that we are occasionally called
@@ -1172,7 +1205,7 @@ char *midend_rewrite_statusbar(midend_data *me, char *text)
 #define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
 #define SERIALISE_VERSION "1"
 
-void midend_serialise(midend_data *me,
+void midend_serialise(midend *me,
                       void (*write)(void *ctx, void *buf, int len),
                       void *wctx)
 {
@@ -1321,7 +1354,7 @@ void midend_serialise(midend_data *me,
 /*
  * This function returns NULL on success, or an error message.
  */
-char *midend_deserialise(midend_data *me,
+char *midend_deserialise(midend *me,
                          int (*read)(void *ctx, void *buf, int len),
                          void *rctx)
 {
@@ -1337,7 +1370,7 @@ char *midend_deserialise(midend_data *me,
      * We construct all the new state in local variables while we
      * check its sanity. Only once we have finished reading the
      * serialised data and detected no errors at all do we start
-     * modifying stuff in the midend_data passed in.
+     * modifying stuff in the midend passed in.
      */
     char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL;
     char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL;
@@ -1632,9 +1665,10 @@ char *midend_deserialise(midend_data *me,
     midend_set_timer(me);
 
     if (me->drawstate)
-        me->ourgame->free_drawstate(me->drawstate);
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
     me->drawstate =
-        me->ourgame->new_drawstate(me->states[me->statepos-1].state);
+        me->ourgame->new_drawstate(me->drawing,
+                                  me->states[me->statepos-1].state);
     midend_size_new_drawstate(me);
 
     ret = NULL;                        /* success! */
@@ -1667,3 +1701,43 @@ char *midend_deserialise(midend_data *me,
 
     return ret;
 }
+
+char *midend_print_puzzle(midend *me, document *doc, int with_soln)
+{
+    game_state *soln = NULL;
+
+    if (me->statepos < 1)
+       return "No game set up to print";/* _shouldn't_ happen! */
+
+    if (with_soln) {
+       char *msg, *movestr;
+
+       if (!me->ourgame->can_solve)
+           return "This game does not support the Solve operation";
+
+       msg = "Solve operation failed";/* game _should_ overwrite on error */
+       movestr = me->ourgame->solve(me->states[0].state,
+                                    me->states[me->statepos-1].state,
+                                    me->aux_info, &msg);
+       if (!movestr)
+           return msg;
+       soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
+                                        movestr);
+       assert(soln);
+
+       sfree(movestr);
+    } else
+       soln = NULL;
+
+    /*
+     * This call passes over ownership of the two game_states and
+     * the game_params. Hence we duplicate the ones we want to
+     * keep, and we don't have to bother freeing soln if it was
+     * non-NULL.
+     */
+    document_add_puzzle(doc, me->ourgame,
+                       me->ourgame->dup_params(me->curparams),
+                       me->ourgame->dup_game(me->states[0].state), soln);
+
+    return NULL;
+}
diff --git a/mines.c b/mines.c
index 2781f15c08ddc347e988bdd2d705a83d55ec60ff..746106b64464e7c2c0b289a7f27666df4566bc39 100644 (file)
--- a/mines.c
+++ b/mines.c
@@ -53,7 +53,7 @@ struct mine_layout {
      */
     int n, unique;
     random_state *rs;
-    midend_data *me;                  /* to give back the new game desc */
+    midend *me;                       /* to give back the new game desc */
 };
 
 struct game_state {
@@ -2157,7 +2157,7 @@ static int open_square(game_state *state, int x, int y)
     return 0;
 }
 
-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)
 {
     game_state *state = snew(game_state);
     int i, wh, x, y, ret, masked;
@@ -2630,8 +2630,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = BORDER * 2 + TILE_SIZE * params->h;
 }
 
-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;
 }
@@ -2714,7 +2714,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -2730,13 +2730,13 @@ 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->grid);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_drawstate *ds,
                       int x, int y, int v, int bg)
 {
     if (v < 0) {
@@ -2749,10 +2749,10 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
            /*
             * Omit the highlights in this case.
             */
-           draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+           draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
                       bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg);
-           draw_line(fe, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
-           draw_line(fe, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
+           draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+           draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
        } else {
            /*
             * Draw highlights to indicate the square is covered.
@@ -2763,14 +2763,14 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
            coords[3] = y;
            coords[4] = x;
            coords[5] = y + TILE_SIZE - 1;
-           draw_polygon(fe, coords, 3, COL_LOWLIGHT ^ hl, COL_LOWLIGHT ^ hl);
+           draw_polygon(dr, coords, 3, COL_LOWLIGHT ^ hl, COL_LOWLIGHT ^ hl);
 
            coords[0] = x;
            coords[1] = y;
-           draw_polygon(fe, coords, 3, COL_HIGHLIGHT ^ hl,
+           draw_polygon(dr, coords, 3, COL_HIGHLIGHT ^ hl,
                         COL_HIGHLIGHT ^ hl);
 
-           draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+           draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
                      TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
                      bg);
        }
@@ -2789,19 +2789,19 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
            SETCOORD(3, 0.25, 0.8);
            SETCOORD(4, 0.55, 0.7);
            SETCOORD(5, 0.55, 0.35);
-           draw_polygon(fe, coords, 6, COL_FLAGBASE, COL_FLAGBASE);
+           draw_polygon(dr, coords, 6, COL_FLAGBASE, COL_FLAGBASE);
 
            SETCOORD(0, 0.6, 0.2);
            SETCOORD(1, 0.6, 0.5);
            SETCOORD(2, 0.2, 0.35);
-           draw_polygon(fe, coords, 3, COL_FLAG, COL_FLAG);
+           draw_polygon(dr, coords, 3, COL_FLAG, COL_FLAG);
 #undef SETCOORD
 
        } else if (v == -3) {
            /*
             * Draw a question mark.
             */
-           draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+           draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
                      FONT_VARIABLE, TILE_SIZE * 6 / 8,
                      ALIGN_VCENTRE | ALIGN_HCENTRE,
                      COL_QUERY, "?");
@@ -2814,11 +2814,11 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
         * Exception is that for value 65 (mine we've just trodden
         * on), we clear the square to COL_BANG.
         */
-        draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
                  (v == 65 ? COL_BANG :
                    bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg));
-       draw_line(fe, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
-       draw_line(fe, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
+       draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+       draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
 
        if (v > 0 && v <= 8) {
            /*
@@ -2827,7 +2827,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
            char str[2];
            str[0] = v + '0';
            str[1] = '\0';
-           draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+           draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
                      FONT_VARIABLE, TILE_SIZE * 7 / 8,
                      ALIGN_VCENTRE | ALIGN_HCENTRE,
                      (COL_1 - 1) + v, str);
@@ -2839,7 +2839,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
             * FIXME: this could be done better!
             */
 #if 0
-           draw_text(fe, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+           draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
                      FONT_VARIABLE, TILE_SIZE * 7 / 8,
                      ALIGN_VCENTRE | ALIGN_HCENTRE,
                      COL_MINE, "*");
@@ -2872,9 +2872,9 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
                    xdy = -tdy;
                }
 
-               draw_polygon(fe, coords, 5*4, COL_MINE, COL_MINE);
+               draw_polygon(dr, coords, 5*4, COL_MINE, COL_MINE);
 
-               draw_rect(fe, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
+               draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
            }
 #endif
 
@@ -2884,10 +2884,10 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
                 */
                int dx;
                for (dx = -1; dx <= +1; dx++) {
-                   draw_line(fe, x + 3 + dx, y + 2,
+                   draw_line(dr, x + 3 + dx, y + 2,
                              x + TILE_SIZE - 3 + dx,
                              y + TILE_SIZE - 2, COL_CROSS);
-                   draw_line(fe, x + TILE_SIZE - 3 + dx, y + 2,
+                   draw_line(dr, x + TILE_SIZE - 3 + dx, y + 2,
                              x + 3 + dx, y + TILE_SIZE - 2,
                              COL_CROSS);
                }
@@ -2895,10 +2895,10 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
        }
     }
 
-    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 }
 
-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)
 {
@@ -2917,10 +2917,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
         int coords[10];
 
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILE_SIZE * state->w + 2 * BORDER,
                  TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILE_SIZE * state->w + 2 * BORDER,
                    TILE_SIZE * state->h + 2 * BORDER);
 
@@ -2937,11 +2937,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         coords[9] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
         coords[6] = coords[8] + TILE_SIZE;
         coords[7] = coords[9] - TILE_SIZE;
-        draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
         coords[1] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
         coords[0] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
-        draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
         ds->started = TRUE;
     }
@@ -2965,7 +2965,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                v -= 20;
 
            if (ds->grid[y*ds->w+x] != v || bg != ds->bg) {
-               draw_tile(fe, ds, COORD(x), COORD(y), v, bg);
+               draw_tile(dr, ds, COORD(x), COORD(y), v, bg);
                ds->grid[y*ds->w+x] = v;
            }
        }
@@ -2992,7 +2992,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         if (ui->deaths)
             sprintf(statusbar + strlen(statusbar),
                     "  Deaths: %d", ui->deaths);
-       status_bar(fe, statusbar);
+       status_bar(dr, statusbar);
     }
 }
 
@@ -3033,6 +3033,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame mines
 #endif
@@ -3068,6 +3076,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     TRUE, game_timing_state,
     BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON),
@@ -3093,19 +3102,19 @@ const struct game thegame = {
 #include <stdarg.h>
 
 void frontend_default_colour(frontend *fe, float *output) {}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+void draw_text(drawing *dr, 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,
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_polygon(drawing *dr, int *coords, int npoints,
                   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) {}
-void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc) {}
-void status_bar(frontend *fe, char *text) {}
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) {}
+void status_bar(drawing *dr, char *text) {}
 
 void fatal(char *fmt, ...)
 {
diff --git a/misc.c b/misc.c
index 91778d8da9392bcf2b8c05874bd20460474a2146..8f5314ce79bc266602da57aab2b73e7daf01112e 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -222,14 +222,21 @@ void shuffle(void *array, int nelts, int eltsize, random_state *rs)
     }
 }
 
-void draw_rect_outline(frontend *fe, int x, int y, int w, int h, int colour)
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour)
 {
     int x0 = x, x1 = x+w-1, y0 = y, y1 = y+h-1;
-
-    draw_line(fe, x0, y0, x0, y1, colour);
-    draw_line(fe, x0, y1, x1, y1, colour);
-    draw_line(fe, x1, y1, x1, y0, colour);
-    draw_line(fe, x1, y0, x0, y0, colour);
+    int coords[8];
+
+    coords[0] = x0;
+    coords[1] = y0;
+    coords[2] = x0;
+    coords[3] = y1;
+    coords[4] = x1;
+    coords[5] = y1;
+    coords[6] = x1;
+    coords[7] = y0;
+
+    draw_polygon(dr, coords, 4, -1, colour);
 }
 
 /* vim: set shiftwidth=4 tabstop=8: */
diff --git a/net.c b/net.c
index 71c2b9d56363127ad3e4e61ff9706e6daa4f13a2..e205341a11dc6d5e6b7c1e428dedd59191197540 100644 (file)
--- a/net.c
+++ b/net.c
@@ -1517,7 +1517,7 @@ static char *validate_desc(game_params *params, char *desc)
  * Construct an initial game state, given a description and parameters.
  */
 
-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)
 {
     game_state *state;
     int w, h, x, y;
@@ -1660,6 +1660,8 @@ static char *solve_game(game_state *state, game_state *currstate,
                 tiles[i] = c - 'a' + 10;
             else if (c >= 'A' && c <= 'F')
                 tiles[i] = c - 'A' + 10;
+
+           tiles[i] |= LOCKED;
         }
     }
 
@@ -2109,7 +2111,7 @@ static game_state *execute_move(game_state *from, char *move)
  * Routines for drawing the game position on the screen.
  */
 
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 {
     game_drawstate *ds = snew(game_drawstate);
 
@@ -2124,7 +2126,7 @@ 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->visible);
     sfree(ds);
@@ -2137,8 +2139,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER;
 }
 
-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;
 }
@@ -2201,17 +2203,17 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     return ret;
 }
 
-static void draw_thick_line(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
                             int colour)
 {
-    draw_line(fe, x1-1, y1, x2-1, y2, COL_WIRE);
-    draw_line(fe, x1+1, y1, x2+1, y2, COL_WIRE);
-    draw_line(fe, x1, y1-1, x2, y2-1, COL_WIRE);
-    draw_line(fe, x1, y1+1, x2, y2+1, COL_WIRE);
-    draw_line(fe, x1, y1, x2, y2, colour);
+    draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
+    draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
+    draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
+    draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
+    draw_line(dr, x1, y1, x2, y2, colour);
 }
 
-static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
                              int colour)
 {
     int mx = (x1 < x2 ? x1 : x2);
@@ -2219,13 +2221,13 @@ static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
     int dx = (x2 + x1 - 2*mx + 1);
     int dy = (y2 + y1 - 2*my + 1);
 
-    draw_rect(fe, mx, my, dx, dy, colour);
+    draw_rect(dr, mx, my, dx, dy, colour);
 }
 
 /*
  * draw_barrier_corner() and draw_barrier() are passed physical coords
  */
-static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
+static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
                                 int x, int y, int dx, int dy, int phase)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2236,20 +2238,20 @@ static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
     y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
 
     if (phase == 0) {
-        draw_rect_coords(fe, bx+x1+dx, by+y1,
+        draw_rect_coords(dr, bx+x1+dx, by+y1,
                          bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_WIRE);
-        draw_rect_coords(fe, bx+x1, by+y1+dy,
+        draw_rect_coords(dr, bx+x1, by+y1+dy,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
                          COL_WIRE);
     } else {
-        draw_rect_coords(fe, bx+x1, by+y1,
+        draw_rect_coords(dr, bx+x1, by+y1,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_BARRIER);
     }
 }
 
-static void draw_barrier(frontend *fe, game_drawstate *ds,
+static void draw_barrier(drawing *dr, game_drawstate *ds,
                          int x, int y, int dir, int phase)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2262,16 +2264,16 @@ static void draw_barrier(frontend *fe, game_drawstate *ds,
     h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
 
     if (phase == 0) {
-        draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+        draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
     } else {
-        draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER);
+        draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
     }
 }
 
 /*
  * draw_tile() is passed physical coordinates
  */
-static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds,
                       int x, int y, int tile, int src, float angle, int cursor)
 {
     int bx = WINDOW_OFFSET + TILE_SIZE * x;
@@ -2287,16 +2289,16 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
      * we must draw those connections on the borders themselves.
      */
 
-    clip(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 
     /*
      * So. First blank the tile out completely: draw a big
      * rectangle in border colour, and a smaller rectangle in
      * background colour to fill it in.
      */
-    draw_rect(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+    draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
               COL_BORDER);
-    draw_rect(fe, bx+TILE_BORDER, by+TILE_BORDER,
+    draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
               TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
               tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
 
@@ -2306,16 +2308,16 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
      * in.
      */
     if (cursor) {
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
-       draw_line(fe, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
                  tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
     }
@@ -2338,7 +2340,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
-            draw_thick_line(fe, bx+(int)cx, by+(int)cy,
+            draw_thick_line(dr, bx+(int)cx, by+(int)cy,
                            bx+(int)(cx+tx), by+(int)(cy+ty),
                             COL_WIRE);
         }
@@ -2348,7 +2350,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
-            draw_line(fe, bx+(int)cx, by+(int)cy,
+            draw_line(dr, bx+(int)cx, by+(int)cy,
                      bx+(int)(cx+tx), by+(int)(cy+ty), col);
         }
     }
@@ -2381,7 +2383,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
             points[i+1] = by+(int)(cy+ty);
         }
 
-        draw_polygon(fe, points, 4, col, COL_WIRE);
+        draw_polygon(dr, points, 4, col, COL_WIRE);
     }
 
     /*
@@ -2418,8 +2420,8 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
              * in: if we are fully connected to the other tile then
              * the two ACTIVE states will be the same.)
              */
-            draw_rect_coords(fe, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
-            draw_rect_coords(fe, px, py, px+lx, py+ly,
+            draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+            draw_rect_coords(dr, px, py, px+lx, py+ly,
                              (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
         } else {
             /*
@@ -2427,7 +2429,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
              * actually connected to us. Just draw a single black
              * dot.
              */
-            draw_rect_coords(fe, px, py, px, py, COL_WIRE);
+            draw_rect_coords(dr, px, py, px, py, COL_WIRE);
         }
     }
 
@@ -2471,7 +2473,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
                  * At least one barrier terminates here. Draw a
                  * corner.
                  */
-                draw_barrier_corner(fe, ds, x, y,
+                draw_barrier_corner(dr, ds, x, y,
                                     X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
                                     phase);
             }
@@ -2479,15 +2481,15 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds,
 
         for (dir = 1; dir < 0x10; dir <<= 1)
             if (barrier(state, GX(x), GY(y)) & dir)
-                draw_barrier(fe, ds, x, y, dir, phase);
+                draw_barrier(dr, ds, x, y, dir, phase);
     }
 
-    unclip(fe);
+    unclip(dr);
 
-    draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 }
 
-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 t, float ft)
 {
     int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
@@ -2503,7 +2505,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
         ds->started = TRUE;
 
-        draw_rect(fe, 0, 0, 
+        draw_rect(dr, 0, 0, 
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
                   WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
                   COL_BACKGROUND);
@@ -2512,7 +2514,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         ds->org_y = ui->org_y;
         moved_origin = TRUE;
 
-        draw_update(fe, 0, 0, 
+        draw_update(dr, 0, 0, 
                     WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
                     WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
 
@@ -2521,38 +2523,38 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
             for (x = 0; x < ds->width; x++) {
                 if (x+1 < ds->width) {
                     if (barrier(state, GX(x), GY(0)) & R)
-                        draw_barrier_corner(fe, ds, x, -1, +1, +1, phase);
+                        draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
                     if (barrier(state, GX(x), GY(ds->height-1)) & R)
-                        draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase);
+                        draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
                 }
                 if (barrier(state, GX(x), GY(0)) & U) {
-                    draw_barrier_corner(fe, ds, x, -1, -1, +1, phase);
-                    draw_barrier_corner(fe, ds, x, -1, +1, +1, phase);
-                    draw_barrier(fe, ds, x, -1, D, phase);
+                    draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
+                    draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
+                    draw_barrier(dr, ds, x, -1, D, phase);
                 }
                 if (barrier(state, GX(x), GY(ds->height-1)) & D) {
-                    draw_barrier_corner(fe, ds, x, ds->height, -1, -1, phase);
-                    draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase);
-                    draw_barrier(fe, ds, x, ds->height, U, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
+                    draw_barrier(dr, ds, x, ds->height, U, phase);
                 }
             }
 
             for (y = 0; y < ds->height; y++) {
                 if (y+1 < ds->height) {
                     if (barrier(state, GX(0), GY(y)) & D)
-                        draw_barrier_corner(fe, ds, -1, y, +1, +1, phase);
+                        draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
                     if (barrier(state, GX(ds->width-1), GY(y)) & D)
-                        draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase);
+                        draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
                 }
                 if (barrier(state, GX(0), GY(y)) & L) {
-                    draw_barrier_corner(fe, ds, -1, y, +1, -1, phase);
-                    draw_barrier_corner(fe, ds, -1, y, +1, +1, phase);
-                    draw_barrier(fe, ds, -1, y, R, phase);
+                    draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
+                    draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
+                    draw_barrier(dr, ds, -1, y, R, phase);
                 }
                 if (barrier(state, GX(ds->width-1), GY(y)) & R) {
-                    draw_barrier_corner(fe, ds, ds->width, y, -1, -1, phase);
-                    draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase);
-                    draw_barrier(fe, ds, ds->width, y, L, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
+                    draw_barrier(dr, ds, ds->width, y, L, phase);
                 }
             }
         }
@@ -2618,7 +2620,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 index(state, ds->visible, x, y) != c ||
                 index(state, ds->visible, x, y) == 0xFF ||
                 is_src || is_anim || is_cursor) {
-                draw_tile(fe, state, ds, x, y, c,
+                draw_tile(dr, state, ds, x, y, c,
                           is_src, (is_anim ? angle : 0.0F), is_cursor);
                 if (is_src || is_anim || is_cursor)
                     index(state, ds->visible, x, y) = 0xFF;
@@ -2646,7 +2648,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                (state->used_solve ? "Auto-solved. " :
                 state->completed ? "COMPLETED! " : ""), a, n2);
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 
     sfree(active);
@@ -2705,6 +2707,143 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 8mm squares by default.
+     */
+    game_compute_size(params, 800, &pw, &ph);
+    *x = pw / 100.0;
+    *y = ph / 100.0;
+}
+
+static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y,
+                        int topleft, int v, int drawlines, int ink)
+{
+    int tx, ty, cx, cy, r, br, k, thick;
+
+    tx = WINDOW_OFFSET + TILE_SIZE * x;
+    ty = WINDOW_OFFSET + TILE_SIZE * y;
+
+    /*
+     * Find our centre point.
+     */
+    if (topleft) {
+       cx = tx + (v & L ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       cy = ty + (v & U ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       r = TILE_SIZE / 8;
+       br = TILE_SIZE / 32;
+    } else {
+       cx = tx + TILE_SIZE / 2;
+       cy = ty + TILE_SIZE / 2;
+       r = TILE_SIZE / 2;
+       br = TILE_SIZE / 8;
+    }
+    thick = r / 20;
+
+    /*
+     * Draw the square block if we have an endpoint.
+     */
+    if (v == 1 || v == 2 || v == 4 || v == 8)
+       draw_rect(dr, cx - br, cy - br, br*2, br*2, ink);
+
+    /*
+     * Draw each radial line.
+     */
+    if (drawlines) {
+       print_line_width(dr, thick * 2);
+       for (k = 1; k < 16; k *= 2)
+           if (v & k) {
+               int x1 = min(cx, cx + (r-thick) * X(k));
+               int x2 = max(cx, cx + (r-thick) * X(k));
+               int y1 = min(cy, cy + (r-thick) * Y(k));
+               int y2 = max(cy, cy + (r-thick) * Y(k));
+               draw_rect(dr, x1 - thick, y1 - thick,
+                         (x2 - x1) + 2*thick, (y2 - y1) + 2*thick, ink);
+           }
+    }
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+    int w = state->width, h = state->height;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / (state->wrapping ? 128 : 12));
+    draw_rect_outline(dr, WINDOW_OFFSET, WINDOW_OFFSET,
+                     TILE_SIZE * w, TILE_SIZE * h, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILE_SIZE / 128);
+    for (x = 1; x < w; x++)
+       draw_line(dr, WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET,
+                 WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET + TILE_SIZE * h,
+                 ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, WINDOW_OFFSET, WINDOW_OFFSET + TILE_SIZE * y,
+                 WINDOW_OFFSET + TILE_SIZE * w, WINDOW_OFFSET + TILE_SIZE * y,
+                 ink);
+
+    /*
+     * Barriers.
+     */
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           int b = barrier(state, x % w, y % h);
+           fprintf(stderr, "%d,%d: %d\n", x, y, b);
+           if (x < w && (b & U))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE + TILE_SIZE/24 * 2, TILE_SIZE/24 * 2, ink);
+           if (y < h && (b & L))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE/24 * 2, TILE_SIZE + TILE_SIZE/24 * 2, ink);
+       }
+
+    /*
+     * Grid contents.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int vx, v = tile(state, x, y);
+           int locked = v & LOCKED;
+
+           v &= 0xF;
+
+           /*
+            * Rotate into a standard orientation for the top left
+            * corner diagram.
+            */
+           vx = v;
+           while (vx != 0 && vx != 15 && vx != 1 && vx != 9 && vx != 13 &&
+                  vx != 5)
+               vx = A(vx);
+
+           /*
+            * Draw the top left corner diagram.
+            */
+           draw_diagram(dr, ds, x, y, TRUE, vx, TRUE, ink);
+
+           /*
+            * Draw the real solution diagram, if we're doing so.
+            */
+           draw_diagram(dr, ds, x, y, FALSE, v, locked, ink);
+       }
+}
+
 #ifdef COMBINED
 #define thegame net
 #endif
@@ -2740,6 +2879,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index 206535b7a7f176f21e0005a7b2029b4a94a35015..4be890fbf43a05fcef525686b8a230e3da14b955 100644 (file)
@@ -726,7 +726,7 @@ static char *validate_desc(game_params *params, char *desc)
  * Construct an initial game state, given a description and parameters.
  */
 
-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)
 {
     game_state *state;
     int w, h, x, y;
@@ -1174,7 +1174,7 @@ static game_state *execute_move(game_state *from, char *move)
  * Routines for drawing the game position on the screen.
  */
 
-static game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 {
     game_drawstate *ds = snew(game_drawstate);
 
@@ -1188,7 +1188,7 @@ 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->visible);
     sfree(ds);
@@ -1205,8 +1205,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER;
 }
 
-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;
 }
@@ -1276,17 +1276,17 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     return ret;
 }
 
-static void draw_thick_line(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
                             int colour)
 {
-    draw_line(fe, x1-1, y1, x2-1, y2, COL_WIRE);
-    draw_line(fe, x1+1, y1, x2+1, y2, COL_WIRE);
-    draw_line(fe, x1, y1-1, x2, y2-1, COL_WIRE);
-    draw_line(fe, x1, y1+1, x2, y2+1, COL_WIRE);
-    draw_line(fe, x1, y1, x2, y2, colour);
+    draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
+    draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
+    draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
+    draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
+    draw_line(dr, x1, y1, x2, y2, colour);
 }
 
-static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
+static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
                              int colour)
 {
     int mx = (x1 < x2 ? x1 : x2);
@@ -1294,10 +1294,10 @@ static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2,
     int dx = (x2 + x1 - 2*mx + 1);
     int dy = (y2 + y1 - 2*my + 1);
 
-    draw_rect(fe, mx, my, dx, dy, colour);
+    draw_rect(dr, mx, my, dx, dy, colour);
 }
 
-static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
+static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
                                 int x, int y, int dir, int phase)
 {
     int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
@@ -1313,20 +1313,20 @@ static void draw_barrier_corner(frontend *fe, game_drawstate *ds,
     y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
 
     if (phase == 0) {
-        draw_rect_coords(fe, bx+x1, by+y1,
+        draw_rect_coords(dr, bx+x1, by+y1,
                          bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_WIRE);
-        draw_rect_coords(fe, bx+x1, by+y1,
+        draw_rect_coords(dr, bx+x1, by+y1,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
                          COL_WIRE);
     } else {
-        draw_rect_coords(fe, bx+x1, by+y1,
+        draw_rect_coords(dr, bx+x1, by+y1,
                          bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
                          COL_BARRIER);
     }
 }
 
-static void draw_barrier(frontend *fe, game_drawstate *ds,
+static void draw_barrier(drawing *dr, game_drawstate *ds,
                          int x, int y, int dir, int phase)
 {
     int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
@@ -1339,13 +1339,13 @@ static void draw_barrier(frontend *fe, game_drawstate *ds,
     h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
 
     if (phase == 0) {
-        draw_rect(fe, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+        draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
     } else {
-        draw_rect(fe, bx+x1, by+y1, w, h, COL_BARRIER);
+        draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
     }
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
                       int x, int y, int tile, float xshift, float yshift)
 {
     int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x + (xshift * TILE_SIZE);
@@ -1374,9 +1374,9 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
      * rectangle in border colour, and a smaller rectangle in
      * background colour to fill it in.
      */
-    draw_rect(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+    draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
               COL_BORDER);
-    draw_rect(fe, bx+TILE_BORDER, by+TILE_BORDER,
+    draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
               TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
               tile & FLASHING ? COL_FLASHING : COL_BACKGROUND);
 
@@ -1389,7 +1389,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
         if (tile & dir) {
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
-            draw_thick_line(fe, bx+(int)cx, by+(int)cy,
+            draw_thick_line(dr, bx+(int)cx, by+(int)cy,
                            bx+(int)(cx+ex), by+(int)(cy+ey),
                             COL_WIRE);
         }
@@ -1398,7 +1398,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
         if (tile & dir) {
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
-            draw_line(fe, bx+(int)cx, by+(int)cy,
+            draw_line(dr, bx+(int)cx, by+(int)cy,
                      bx+(int)(cx+ex), by+(int)(cy+ey), col);
         }
     }
@@ -1430,7 +1430,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
             points[i+1] = by+(int)(cy+ey);
         }
 
-        draw_polygon(fe, points, 4, col, COL_WIRE);
+        draw_polygon(dr, points, 4, col, COL_WIRE);
     }
 
     /*
@@ -1467,8 +1467,8 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
              * in: if we are fully connected to the other tile then
              * the two ACTIVE states will be the same.)
              */
-            draw_rect_coords(fe, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
-            draw_rect_coords(fe, px, py, px+lx, py+ly,
+            draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+            draw_rect_coords(dr, px, py, px+lx, py+ly,
                              (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
         } else {
             /*
@@ -1476,14 +1476,14 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
              * actually connected to us. Just draw a single black
              * dot.
              */
-            draw_rect_coords(fe, px, py, px, py, COL_WIRE);
+            draw_rect_coords(dr, px, py, px, py, COL_WIRE);
         }
     }
 
-    draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 }
 
-static void draw_tile_barriers(frontend *fe, game_drawstate *ds,
+static void draw_tile_barriers(drawing *dr, game_drawstate *ds,
                                game_state *state, int x, int y)
 {
     int phase;
@@ -1496,16 +1496,16 @@ static void draw_tile_barriers(frontend *fe, game_drawstate *ds,
     for (phase = 0; phase < 2; phase++) {
         for (dir = 1; dir < 0x10; dir <<= 1)
             if (barrier(state, x, y) & (dir << 4))
-                draw_barrier_corner(fe, ds, x, y, dir << 4, phase);
+                draw_barrier_corner(dr, ds, x, y, dir << 4, phase);
         for (dir = 1; dir < 0x10; dir <<= 1)
             if (barrier(state, x, y) & dir)
-                draw_barrier(fe, ds, x, y, dir, phase);
+                draw_barrier(dr, ds, x, y, dir, phase);
     }
 
-    draw_update(fe, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
 }
 
-static void draw_arrow(frontend *fe, game_drawstate *ds,
+static void draw_arrow(drawing *dr, game_drawstate *ds,
                        int x, int y, int xdx, int xdy)
 {
     int coords[14];
@@ -1526,10 +1526,10 @@ static void draw_arrow(frontend *fe, game_drawstate *ds,
     POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2);   /* left concave */
     POINT(6,     TILE_SIZE / 4, TILE_SIZE / 2);   /* left corner */
 
-    draw_polygon(fe, coords, 7, COL_LOWLIGHT, COL_TEXT);
+    draw_polygon(dr, coords, 7, COL_LOWLIGHT, COL_TEXT);
 }
 
-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 t, float ft)
 {
     int x, y, tx, ty, frame;
@@ -1546,11 +1546,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
         ds->started = TRUE;
 
-        draw_rect(fe, 0, 0, 
+        draw_rect(dr, 0, 0, 
                   BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
                   BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
                   COL_BACKGROUND);
-        draw_update(fe, 0, 0, 
+        draw_update(dr, 0, 0, 
                     BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
                     BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
 
@@ -1558,32 +1558,32 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
             for (x = 0; x < ds->width; x++) {
                 if (barrier(state, x, 0) & UL)
-                    draw_barrier_corner(fe, ds, x, -1, LD, phase);
+                    draw_barrier_corner(dr, ds, x, -1, LD, phase);
                 if (barrier(state, x, 0) & RU)
-                    draw_barrier_corner(fe, ds, x, -1, DR, phase);
+                    draw_barrier_corner(dr, ds, x, -1, DR, phase);
                 if (barrier(state, x, 0) & U)
-                    draw_barrier(fe, ds, x, -1, D, phase);
+                    draw_barrier(dr, ds, x, -1, D, phase);
                 if (barrier(state, x, ds->height-1) & DR)
-                    draw_barrier_corner(fe, ds, x, ds->height, RU, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, RU, phase);
                 if (barrier(state, x, ds->height-1) & LD)
-                    draw_barrier_corner(fe, ds, x, ds->height, UL, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, UL, phase);
                 if (barrier(state, x, ds->height-1) & D)
-                    draw_barrier(fe, ds, x, ds->height, U, phase);
+                    draw_barrier(dr, ds, x, ds->height, U, phase);
             }
 
             for (y = 0; y < ds->height; y++) {
                 if (barrier(state, 0, y) & UL)
-                    draw_barrier_corner(fe, ds, -1, y, RU, phase);
+                    draw_barrier_corner(dr, ds, -1, y, RU, phase);
                 if (barrier(state, 0, y) & LD)
-                    draw_barrier_corner(fe, ds, -1, y, DR, phase);
+                    draw_barrier_corner(dr, ds, -1, y, DR, phase);
                 if (barrier(state, 0, y) & L)
-                    draw_barrier(fe, ds, -1, y, R, phase);
+                    draw_barrier(dr, ds, -1, y, R, phase);
                 if (barrier(state, ds->width-1, y) & RU)
-                    draw_barrier_corner(fe, ds, ds->width, y, UL, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, UL, phase);
                 if (barrier(state, ds->width-1, y) & DR)
-                    draw_barrier_corner(fe, ds, ds->width, y, LD, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, LD, phase);
                 if (barrier(state, ds->width-1, y) & R)
-                    draw_barrier(fe, ds, ds->width, y, L, phase);
+                    draw_barrier(dr, ds, ds->width, y, L, phase);
             }
         }
 
@@ -1592,13 +1592,13 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
          */
         for (x = 0; x < ds->width; x++) {
             if (x == state->cx) continue;
-            draw_arrow(fe, ds, x, 0, +1, 0);
-            draw_arrow(fe, ds, x+1, ds->height, -1, 0);
+            draw_arrow(dr, ds, x, 0, +1, 0);
+            draw_arrow(dr, ds, x+1, ds->height, -1, 0);
         }
         for (y = 0; y < ds->height; y++) {
             if (y == state->cy) continue;
-            draw_arrow(fe, ds, ds->width, y, 0, +1);
-            draw_arrow(fe, ds, 0, y+1, 0, -1);
+            draw_arrow(dr, ds, ds->width, y, 0, +1);
+            draw_arrow(dr, ds, 0, y+1, 0, -1);
         }
     }
 
@@ -1644,7 +1644,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         active = compute_active(state, -1, -1);
     }
 
-    clip(fe,
+    clip(dr,
          BORDER + WINDOW_OFFSET, BORDER + WINDOW_OFFSET,
          TILE_SIZE * state->width + TILE_BORDER,
          TILE_SIZE * state->height + TILE_BORDER);
@@ -1678,15 +1678,15 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 float xs = (y == state->last_move_row ? xshift : 0.0);
                 float ys = (x == state->last_move_col ? yshift : 0.0);
 
-                draw_tile(fe, ds, state, x, y, c, xs, ys);
+                draw_tile(dr, ds, state, x, y, c, xs, ys);
                 if (xs < 0 && x == 0)
-                    draw_tile(fe, ds, state, state->width, y, c, xs, ys);
+                    draw_tile(dr, ds, state, state->width, y, c, xs, ys);
                 else if (xs > 0 && x == state->width - 1)
-                    draw_tile(fe, ds, state, -1, y, c, xs, ys);
+                    draw_tile(dr, ds, state, -1, y, c, xs, ys);
                 else if (ys < 0 && y == 0)
-                    draw_tile(fe, ds, state, x, state->height, c, xs, ys);
+                    draw_tile(dr, ds, state, x, state->height, c, xs, ys);
                 else if (ys > 0 && y == state->height - 1)
-                    draw_tile(fe, ds, state, x, -1, c, xs, ys);
+                    draw_tile(dr, ds, state, x, -1, c, xs, ys);
 
                 if (x == state->last_move_col || y == state->last_move_row)
                     index(state, ds->visible, x, y) = 0xFF;
@@ -1697,9 +1697,9 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
     for (x = 0; x < ds->width; x++)
         for (y = 0; y < ds->height; y++)
-            draw_tile_barriers(fe, ds, state, x, y);
+            draw_tile_barriers(dr, ds, state, x, y);
 
-    unclip(fe);
+    unclip(dr);
 
     /*
      * Update the status bar.
@@ -1727,7 +1727,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
        sprintf(statusbuf + strlen(statusbuf), " Active: %d/%d", a, n);
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 
     sfree(active);
@@ -1781,6 +1781,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return FALSE;
 }
 
+                     static void game_print_size(game_params *params, float *x, float *y)
+                 {
+                 }
+                     
+                     static void game_print(drawing *dr, game_state *state, int tilesize)
+                 {
+                 }
+                     
 #ifdef COMBINED
 #define thegame netslide
 #endif
@@ -1816,6 +1824,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index 212d5c274b3d42ec70624a4cb1aebabe78328b79..6463bf7ac44df5971e81a2c94f3895f05b25214b 100644 (file)
@@ -94,7 +94,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)
 {
     game_state *state = snew(game_state);
 
@@ -177,8 +177,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *x = *y = 10 * tilesize;          /* FIXME */
 }
 
-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;
 }
@@ -193,7 +193,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -203,12 +203,12 @@ 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);
 }
 
-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)
 {
@@ -218,7 +218,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      * should start by drawing a big background-colour rectangle
      * covering the whole window.
      */
-    draw_rect(fe, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
+    draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
 }
 
 static float game_anim_length(game_state *oldstate, game_state *newstate,
@@ -243,6 +243,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame nullgame
 #endif
@@ -278,6 +286,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/osx.m b/osx.m
index c3f6d1bf1e0c57fbeac48d58911d19cb3f3866bb..e7da743c8be70e45cae40838f563f749f49cf059 100644 (file)
--- a/osx.m
+++ b/osx.m
  */
 NSMenu *typemenu;
 
+/*
+ * Forward reference.
+ */
+extern const struct drawing_api osx_drawing;
+
 /* ----------------------------------------------------------------------
  * Miscellaneous support routines that aren't part of any object or
  * clearly defined subsystem.
@@ -153,6 +158,15 @@ static int savefile_read(void *wctx, void *buf, int len)
     return (ret == len);
 }
 
+/*
+ * Since this front end does not support printing (yet), we need
+ * this stub to satisfy the reference in midend_print_puzzle().
+ */
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2)
+{
+}
+
 /* ----------------------------------------------------------------------
  * Tiny extension to NSMenuItem which carries a payload of a `void
  * *', allowing several menu items to invoke the same message but
@@ -381,7 +395,7 @@ struct frontend {
 @interface GameWindow : NSWindow
 {
     const game *ourgame;
-    midend_data *me;
+    midend *me;
     struct frontend fe;
     struct timeval last_time;
     NSTimer *timer;
@@ -510,7 +524,7 @@ struct frontend {
 
     fe.window = self;
 
-    me = midend_new(&fe, ourgame);
+    me = midend_new(&fe, ourgame, osx_drawing, &fe);
     /*
      * If we ever need to open a fresh window using a provided game
      * ID, I think the right thing is to move most of this method
@@ -1215,9 +1229,10 @@ struct frontend {
 /*
  * Drawing routines called by the midend.
  */
-void draw_polygon(frontend *fe, int *coords, int npoints,
-                  int fillcolour, int outlinecolour)
+static void osx_draw_polygon(void *handle, int *coords, int npoints,
+                            int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     NSBezierPath *path = [NSBezierPath bezierPath];
     int i;
 
@@ -1243,9 +1258,10 @@ void draw_polygon(frontend *fe, int *coords, int npoints,
     [fe->colours[outlinecolour] set];
     [path stroke];
 }
-void draw_circle(frontend *fe, int cx, int cy, int radius,
-                 int fillcolour, int outlinecolour)
+static void osx_draw_circle(void *handle, int cx, int cy, int radius,
+                           int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     NSBezierPath *path = [NSBezierPath bezierPath];
 
     [[NSGraphicsContext currentContext] setShouldAntialias:YES];
@@ -1265,8 +1281,9 @@ void draw_circle(frontend *fe, int cx, int cy, int radius,
     [fe->colours[outlinecolour] set];
     [path stroke];
 }
-void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
+static void osx_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
 {
+    frontend *fe = (frontend *)handle;
     NSBezierPath *path = [NSBezierPath bezierPath];
     NSPoint p1 = { x1 + 0.5, y1 + 0.5 }, p2 = { x2 + 0.5, y2 + 0.5 };
 
@@ -1279,10 +1296,11 @@ void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
     [path lineToPoint:p2];
     [path stroke];
 }
-void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
+static void osx_draw_rect(void *handle, int x, int y, int w, int h, int colour)
 {
+    frontend *fe = (frontend *)handle;
     NSRect r = { {x,y}, {w,h} };
-
+    
     [[NSGraphicsContext currentContext] setShouldAntialias:NO];
 
     assert(colour >= 0 && colour < fe->ncolours);
@@ -1290,9 +1308,10 @@ void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
 
     NSRectFill(r);
 }
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
-               int align, int colour, char *text)
+static void osx_draw_text(void *handle, int x, int y, int fonttype,
+                         int fontsize, int align, int colour, char *text)
 {
+    frontend *fe = (frontend *)handle;
     NSString *string = [NSString stringWithCString:text];
     NSDictionary *attr;
     NSFont *font;
@@ -1330,7 +1349,7 @@ struct blitter {
     int x, y;
     NSImage *img;
 };
-blitter *blitter_new(int w, int h)
+static blitter *osx_blitter_new(void *handle, int w, int h)
 {
     blitter *bl = snew(blitter);
     bl->x = bl->y = -1;
@@ -1340,13 +1359,14 @@ blitter *blitter_new(int w, int h)
     [bl->img setFlipped:YES];
     return bl;
 }
-void blitter_free(blitter *bl)
+static void osx_blitter_free(void *handle, blitter *bl)
 {
     [bl->img release];
     sfree(bl);
 }
-void blitter_save(frontend *fe, blitter *bl, int x, int y)
+static void osx_blitter_save(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     [fe->image unlockFocus];
     [bl->img lockFocus];
     [fe->image drawInRect:NSMakeRect(0, 0, bl->w, bl->h)
@@ -1357,8 +1377,9 @@ void blitter_save(frontend *fe, blitter *bl, int x, int y)
     bl->x = x;
     bl->y = y;
 }
-void blitter_load(frontend *fe, blitter *bl, int x, int y)
+static void osx_blitter_load(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
         x = bl->x;
         y = bl->y;
@@ -1367,34 +1388,64 @@ void blitter_load(frontend *fe, blitter *bl, int x, int y)
        fromRect:NSMakeRect(0, 0, bl->w, bl->h)
        operation:NSCompositeCopy fraction:1.0];
 }
-void draw_update(frontend *fe, int x, int y, int w, int h)
+static void osx_draw_update(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     [fe->view setNeedsDisplayInRect:NSMakeRect(x,y,w,h)];
 }
-void clip(frontend *fe, int x, int y, int w, int h)
+static void osx_clip(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     NSRect r = { {x,y}, {w,h} };
-
+    
     if (!fe->clipped)
        [[NSGraphicsContext currentContext] saveGraphicsState];
     [NSBezierPath clipRect:r];
     fe->clipped = TRUE;
 }
-void unclip(frontend *fe)
+static void osx_unclip(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     if (fe->clipped)
        [[NSGraphicsContext currentContext] restoreGraphicsState];
     fe->clipped = FALSE;
 }
-void start_draw(frontend *fe)
+static void osx_start_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     [fe->image lockFocus];
     fe->clipped = FALSE;
 }
-void end_draw(frontend *fe)
+static void osx_end_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     [fe->image unlockFocus];
 }
+static void osx_status_bar(void *handle, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    [fe->window setStatusLine:text];
+}
+
+const struct drawing_api osx_drawing = {
+    osx_draw_text,
+    osx_draw_rect,
+    osx_draw_line,
+    osx_draw_poly,
+    osx_draw_circle,
+    osx_draw_update,
+    osx_clip,
+    osx_unclip,
+    osx_start_draw,
+    osx_end_draw,
+    osx_status_bar,
+    osx_blitter_new,
+    osx_blitter_free,
+    osx_blitter_save,
+    osx_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL,                             /* line_width */
+};
 
 void deactivate_timer(frontend *fe)
 {
@@ -1405,11 +1456,6 @@ void activate_timer(frontend *fe)
     [fe->window activateTimer];
 }
 
-void status_bar(frontend *fe, char *text)
-{
-    [fe->window setStatusLine:text];
-}
-
 /* ----------------------------------------------------------------------
  * AppController: the object which receives the messages from all
  * menu selections that aren't standard OS X functions.
index 3d5c11f7cc11616d2df8d867e3aeb439cbbc445b..4856928ceb7ee4fdca95ee37a76052284b3853c6 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -15,6 +15,7 @@ enum {
     COL_BACKGROUND,
     COL_EMPTY,
     COL_FULL,
+    COL_TEXT,
     COL_UNKNOWN,
     COL_GRID,
     NCOLOURS
@@ -595,7 +596,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 i;
     char *p;
@@ -956,8 +957,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = SIZE(params->h);
 }
 
-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;
 }
@@ -976,6 +977,10 @@ static float *game_colours(frontend *fe, game_state *state, int *ncolours)
     ret[COL_UNKNOWN * 3 + 1] = 0.5F;
     ret[COL_UNKNOWN * 3 + 2] = 0.5F;
 
+    ret[COL_TEXT * 3 + 0] = 0.0F;
+    ret[COL_TEXT * 3 + 1] = 0.0F;
+    ret[COL_TEXT * 3 + 2] = 0.0F;
+
     ret[COL_FULL * 3 + 0] = 0.0F;
     ret[COL_FULL * 3 + 1] = 0.0F;
     ret[COL_FULL * 3 + 2] = 0.0F;
@@ -988,7 +993,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
 
@@ -1002,18 +1007,18 @@ 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->visible);
     sfree(ds);
 }
 
-static void grid_square(frontend *fe, game_drawstate *ds,
+static void grid_square(drawing *dr, game_drawstate *ds,
                         int y, int x, int state)
 {
     int xl, xr, yt, yb;
 
-    draw_rect(fe, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
+    draw_rect(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
               TILE_SIZE, TILE_SIZE, COL_GRID);
 
     xl = (x % 5 == 0 ? 1 : 0);
@@ -1021,16 +1026,59 @@ static void grid_square(frontend *fe, game_drawstate *ds,
     xr = (x % 5 == 4 || x == ds->w-1 ? 1 : 0);
     yb = (y % 5 == 4 || y == ds->h-1 ? 1 : 0);
 
-    draw_rect(fe, TOCOORD(ds->w, x) + 1 + xl, TOCOORD(ds->h, y) + 1 + yt,
+    draw_rect(dr, TOCOORD(ds->w, x) + 1 + xl, TOCOORD(ds->h, y) + 1 + yt,
               TILE_SIZE - xl - xr - 1, TILE_SIZE - yt - yb - 1,
               (state == GRID_FULL ? COL_FULL :
                state == GRID_EMPTY ? COL_EMPTY : COL_UNKNOWN));
 
-    draw_update(fe, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
+    draw_update(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
                 TILE_SIZE, TILE_SIZE);
 }
 
-static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+static void draw_numbers(drawing *dr, game_drawstate *ds, game_state *state,
+                        int colour)
+{
+    int i, j;
+
+    /*
+     * Draw the numbers.
+     */
+    for (i = 0; i < state->w + state->h; i++) {
+       int rowlen = state->rowlen[i];
+       int *rowdata = state->rowdata + state->rowsize * i;
+       int nfit;
+
+       /*
+        * Normally I space the numbers out by the same
+        * distance as the tile size. However, if there are
+        * more numbers than available spaces, I have to squash
+        * them up a bit.
+        */
+       nfit = max(rowlen, TLBORDER(state->h))-1;
+       assert(nfit > 0);
+
+       for (j = 0; j < rowlen; j++) {
+           int x, y;
+           char str[80];
+
+           if (i < state->w) {
+               x = TOCOORD(state->w, i);
+               y = BORDER + TILE_SIZE * (TLBORDER(state->h)-1);
+               y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->h)-1) / nfit;
+           } else {
+               y = TOCOORD(state->h, i - state->w);
+               x = BORDER + TILE_SIZE * (TLBORDER(state->w)-1);
+               x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->h)-1) / nfit;
+           }
+
+           sprintf(str, "%d", rowdata[j]);
+           draw_text(dr, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE,
+                     TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, colour, str);
+       }
+    }
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
                         game_state *state, int dir, game_ui *ui,
                         float animtime, float flashtime)
 {
@@ -1044,56 +1092,23 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
          * all games should start by drawing a big background-
          * colour rectangle covering the whole window.
          */
-        draw_rect(fe, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND);
-
-        /*
-         * Draw the numbers.
-         */
-        for (i = 0; i < ds->w + ds->h; i++) {
-            int rowlen = state->rowlen[i];
-            int *rowdata = state->rowdata + state->rowsize * i;
-           int nfit;
-
-           /*
-            * Normally I space the numbers out by the same
-            * distance as the tile size. However, if there are
-            * more numbers than available spaces, I have to squash
-            * them up a bit.
-            */
-           nfit = max(rowlen, TLBORDER(ds->h))-1;
-           assert(nfit > 0);
-
-            for (j = 0; j < rowlen; j++) {
-                int x, y;
-                char str[80];
-
-                if (i < ds->w) {
-                    x = TOCOORD(ds->w, i);
-                    y = BORDER + TILE_SIZE * (TLBORDER(ds->h)-1);
-                   y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(ds->h)-1) / nfit;
-                } else {
-                    y = TOCOORD(ds->h, i - ds->w);
-                    x = BORDER + TILE_SIZE * (TLBORDER(ds->w)-1);
-                   x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(ds->h)-1) / nfit;
-                }
+        draw_rect(dr, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND);
 
-                sprintf(str, "%d", rowdata[j]);
-                draw_text(fe, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE,
-                          TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE,
-                          COL_FULL, str);   /* FIXME: COL_TEXT */
-            }
-        }
+       /*
+        * Draw the numbers.
+        */
+       draw_numbers(dr, ds, state, COL_TEXT);
 
         /*
          * Draw the grid outline.
          */
-        draw_rect(fe, TOCOORD(ds->w, 0) - 1, TOCOORD(ds->h, 0) - 1,
+        draw_rect(dr, TOCOORD(ds->w, 0) - 1, TOCOORD(ds->h, 0) - 1,
                   ds->w * TILE_SIZE + 3, ds->h * TILE_SIZE + 3,
                   COL_GRID);
 
         ds->started = TRUE;
 
-       draw_update(fe, 0, 0, SIZE(ds->w), SIZE(ds->h));
+       draw_update(dr, 0, 0, SIZE(ds->w), SIZE(ds->h));
     }
 
     if (ui->dragging) {
@@ -1132,7 +1147,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                 val = (GRID_FULL ^ GRID_EMPTY) ^ val;
 
             if (ds->visible[i * ds->w + j] != val) {
-                grid_square(fe, ds, i, j, val);
+                grid_square(dr, ds, i, j, val);
                 ds->visible[i * ds->w + j] = val;
             }
         }
@@ -1164,6 +1179,70 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 5mm squares by default.
+     */
+    game_compute_size(params, 500, &pw, &ph);
+    *x = pw / 100.0;
+    *y = ph / 100.0;
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 16);
+    draw_rect_outline(dr, TOCOORD(w, 0), TOCOORD(h, 0),
+                     w*TILE_SIZE, h*TILE_SIZE, ink);
+
+    /*
+     * Grid.
+     */
+    for (x = 1; x < w; x++) {
+       print_line_width(dr, TILE_SIZE / (x % 5 ? 128 : 24));
+       draw_line(dr, TOCOORD(w, x), TOCOORD(h, 0),
+                 TOCOORD(w, x), TOCOORD(h, h), ink);
+    }
+    for (y = 1; y < h; y++) {
+       print_line_width(dr, TILE_SIZE / (y % 5 ? 128 : 24));
+       draw_line(dr, TOCOORD(w, 0), TOCOORD(h, y),
+                 TOCOORD(w, w), TOCOORD(h, y), ink);
+    }
+
+    /*
+     * Clues.
+     */
+    draw_numbers(dr, ds, state, ink);
+
+    /*
+     * Solution.
+     */
+    print_line_width(dr, TILE_SIZE / 128);
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           if (state->grid[y*w+x] == GRID_FULL)
+               draw_rect(dr, TOCOORD(w, x), TOCOORD(h, y),
+                         TILE_SIZE, TILE_SIZE, ink);
+           else if (state->grid[y*w+x] == GRID_EMPTY)
+               draw_circle(dr, TOCOORD(w, x) + TILE_SIZE/2,
+                           TOCOORD(h, y) + TILE_SIZE/2,
+                           TILE_SIZE/12, ink, ink);
+       }
+}
+
 #ifdef COMBINED
 #define thegame pattern
 #endif
@@ -1199,6 +1278,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
@@ -1213,17 +1293,22 @@ const struct game thegame = {
 #include <stdarg.h>
 
 void frontend_default_colour(frontend *fe, float *output) {}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+void draw_text(drawing *dr, 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 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) {}
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour) {}
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
 unsigned long random_upto(random_state *state, unsigned long limit)
 { assert(!"Shouldn't get randomness"); return 0; }
+int print_mono_colour(drawing *dr, int grey) { return 0; }
+void print_line_width(drawing *dr, int width) {}
 
 void fatal(char *fmt, ...)
 {
diff --git a/pegs.c b/pegs.c
index 4abe116b310d487bad820589cbdbd18fd775ec0e..427633a678a040d42cc15c6a6d241f6bb1b4f6dc 100644 (file)
--- a/pegs.c
+++ b/pegs.c
@@ -668,7 +668,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;
     game_state *state = snew(game_state);
@@ -929,16 +929,16 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILESIZE * params->h + 2 * BORDER;
 }
 
-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;
 
     assert(TILESIZE > 0);
 
     if (ds->drag_background)
-       blitter_free(ds->drag_background);
-    ds->drag_background = blitter_new(TILESIZE, TILESIZE);
+       blitter_free(dr, ds->drag_background);
+    ds->drag_background = blitter_new(dr, TILESIZE, TILESIZE);
 }
 
 static float *game_colours(frontend *fe, game_state *state, int *ncolours)
@@ -955,7 +955,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->w, h = state->h;
     struct game_drawstate *ds = snew(struct game_drawstate);
@@ -978,33 +978,33 @@ 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)
 {
     if (ds->drag_background)
-       blitter_free(ds->drag_background);
+       blitter_free(dr, ds->drag_background);
     sfree(ds->grid);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_drawstate *ds,
                      int x, int y, int v, int bgcolour)
 {
     if (bgcolour >= 0) {
-       draw_rect(fe, x, y, TILESIZE, TILESIZE, bgcolour);
+       draw_rect(dr, x, y, TILESIZE, TILESIZE, bgcolour);
     }
 
     if (v == GRID_HOLE) {
-       draw_circle(fe, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4,
+       draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4,
                    COL_LOWLIGHT, COL_LOWLIGHT);
     } else if (v == GRID_PEG) {
-       draw_circle(fe, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3,
+       draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3,
                    COL_PEG, COL_PEG);
     }
 
-    draw_update(fe, x, y, TILESIZE, TILESIZE);
+    draw_update(dr, x, 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)
 {
@@ -1023,13 +1023,13 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      */
     if (ds->dragging) {
        assert(ds->drag_background);
-        blitter_load(fe, ds->drag_background, ds->dragx, ds->dragy);
-        draw_update(fe, ds->dragx, ds->dragy, TILESIZE, TILESIZE);
+        blitter_load(dr, ds->drag_background, ds->dragx, ds->dragy);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE, TILESIZE);
        ds->dragging = FALSE;
     }
 
     if (!ds->started) {
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILESIZE * state->w + 2 * BORDER,
                  TILESIZE * state->h + 2 * BORDER, COL_BACKGROUND);
 
@@ -1050,10 +1050,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                    coords[3] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
                    coords[4] = COORD(x) - HIGHLIGHT_WIDTH;
                    coords[5] = COORD(y) - HIGHLIGHT_WIDTH;
-                   draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+                   draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
                    coords[4] = COORD(x+1) + HIGHLIGHT_WIDTH - 1;
                    coords[5] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
-                   draw_polygon(fe, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+                   draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
                }
        for (y = 0; y < h; y++)
            for (x = 0; x < w; x++)
@@ -1062,11 +1062,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                     * Second pass: draw everything but the two
                     * diagonal corners.
                     */
-                   draw_rect(fe, COORD(x) - HIGHLIGHT_WIDTH,
+                   draw_rect(dr, COORD(x) - HIGHLIGHT_WIDTH,
                              COORD(y) - HIGHLIGHT_WIDTH,
                              TILESIZE + HIGHLIGHT_WIDTH,
                              TILESIZE + HIGHLIGHT_WIDTH, COL_HIGHLIGHT);
-                   draw_rect(fe, COORD(x),
+                   draw_rect(dr, COORD(x),
                              COORD(y),
                              TILESIZE + HIGHLIGHT_WIDTH,
                              TILESIZE + HIGHLIGHT_WIDTH, COL_LOWLIGHT);
@@ -1094,7 +1094,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                            coords[5] = coords[3] - HIGHLIGHT_WIDTH * (dx-sn*dy);
                            coords[6] = coords[0] + HIGHLIGHT_WIDTH * (dy+sn*dx);
                            coords[7] = coords[1] + HIGHLIGHT_WIDTH * (dx+sn*dy);
-                           draw_polygon(fe, coords, 4, c, c);
+                           draw_polygon(dr, coords, 4, c, c);
                        }
                    }
                }
@@ -1105,7 +1105,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                     * Second pass: draw everything but the two
                     * diagonal corners.
                     */
-                   draw_rect(fe, COORD(x),
+                   draw_rect(dr, COORD(x),
                              COORD(y),
                              TILESIZE,
                              TILESIZE, COL_BACKGROUND);
@@ -1113,7 +1113,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
        ds->started = TRUE;
 
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILESIZE * state->w + 2 * BORDER,
                    TILESIZE * state->h + 2 * BORDER);
     }
@@ -1136,7 +1136,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            if (v != GRID_OBST &&
                 (bgcolour != ds->bgcolour || /* always redraw when flashing */
                  v != ds->grid[y*w+x])) {
-               draw_tile(fe, ds, COORD(x), COORD(y), v, bgcolour);
+               draw_tile(dr, ds, COORD(x), COORD(y), v, bgcolour);
            }
        }
 
@@ -1147,8 +1147,8 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        ds->dragging = TRUE;
        ds->dragx = ui->dx - TILESIZE/2;
        ds->dragy = ui->dy - TILESIZE/2;
-       blitter_save(fe, ds->drag_background, ds->dragx, ds->dragy);
-       draw_tile(fe, ds, ds->dragx, ds->dragy, GRID_PEG, -1);
+       blitter_save(dr, ds->drag_background, ds->dragx, ds->dragy);
+       draw_tile(dr, ds, ds->dragx, ds->dragy, GRID_PEG, -1);
     }
 
     ds->bgcolour = bgcolour;
@@ -1179,6 +1179,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame pegs
 #endif
@@ -1214,6 +1222,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/print.py b/print.py
deleted file mode 100755 (executable)
index 13b4263..0000000
--- a/print.py
+++ /dev/null
@@ -1,601 +0,0 @@
-#!/usr/bin/env python
-
-# This program accepts a series of newline-separated game IDs on
-# stdin and formats them into PostScript to be printed out. You
-# specify using command-line options which game the IDs are for,
-# and how many you want per page.
-
-# Supported games are those which are sensibly solvable using
-# pencil and paper: Rectangles, Pattern, Solo, Net.
-
-# Command-line syntax is
-#
-#     print.py <game-name> <format>
-#
-# <game-name> is one of `rect', `rectangles', `pattern', `solo',
-# `net', `dominosa'. <format> is two numbers separated by an x:
-# `2x3', for example, means two columns by three rows.
-#
-# The program will then read game IDs from stdin until it sees EOF,
-# and generate as many PostScript pages on stdout as it needs.
-#
-# The resulting PostScript will automatically adapt itself to the
-# size of the clip rectangle, so that the puzzles are sensibly
-# distributed across whatever paper size you decide to use.
-
-import sys
-import string
-import re
-
-class Holder:
-    pass
-
-def psvprint(h, a):
-    for i in xrange(len(a)):
-       h.s = h.s + str(a[i])
-       if i < len(a)-1:
-           h.s = h.s + " "
-       else:
-           h.s = h.s + "\n"
-
-def psprint(h, *a):
-    psvprint(h, a)
-
-def rect_format(s):
-    # Parse the game ID.
-    ret = Holder()
-    ret.s = ""
-    params, seed = string.split(s, ":")
-    w, h = map(string.atoi, string.split(params, "x"))
-    grid = []
-    while len(seed) > 0:
-       if seed[0] in '_'+string.lowercase:
-           if seed[0] in string.lowercase:
-               grid.extend([-1] * (ord(seed[0]) - ord('a') + 1))
-           seed = seed[1:]
-       elif seed[0] in string.digits:
-           ns = ""
-           while len(seed) > 0 and seed[0] in string.digits:
-               ns = ns + seed[0]
-               seed = seed[1:]
-           grid.append(string.atoi(ns))
-    assert w * h == len(grid)
-    # I'm going to arbitrarily choose to use 7pt text for the
-    # numbers, and a 14pt grid pitch.
-    textht = 7
-    gridpitch = 14
-    # Set up coordinate system.
-    pw = gridpitch * w
-    ph = gridpitch * h
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    # Draw the internal grid lines, _very_ thin (the player will
-    # need to draw over them visibly).
-    psprint(ret, "newpath 0.01 setlinewidth")
-    for x in xrange(1,w):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch))
-    for y in xrange(1,h):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    # Draw round the grid exterior, much thicker.
-    psprint(ret, "newpath 1.5 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (h * gridpitch, w * gridpitch, -h * gridpitch))
-    psprint(ret, "closepath stroke")
-    # And draw the numbers.
-    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
-    for y in xrange(h):
-       for x in xrange(w):
-           n = grid[y*w+x]
-           if n > 0:
-               psprint(ret, "%g %g (%d) ctshow" % \
-               ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, n))
-    return ret.coords, ret.s
-
-def net_format(s):
-    # Parse the game ID.
-    ret = Holder()
-    ret.s = ""
-    params, seed = string.split(s, ":")
-    wrapping = 0
-    if params[-1:] == "w":
-       wrapping = 1
-       params = params[:-1]
-    w, h = map(string.atoi, string.split(params, "x"))
-    grid = []
-    hbarriers = []
-    vbarriers = []
-    while len(seed) > 0:
-       n = string.atoi(seed[0], 16)
-       seed = seed[1:]
-       while len(seed) > 0 and seed[0] in 'hv':
-           x = len(grid) % w
-           y = len(grid) / w
-           if seed[0] == 'h':
-               hbarriers.append((x, y+1))
-           else:
-               vbarriers.append((x+1, y))
-           seed = seed[1:]
-       grid.append(n)
-    assert w * h == len(grid)
-    # I'm going to arbitrarily choose a 24pt grid pitch.
-    gridpitch = 24
-    scale = 0.25
-    bigoffset = 0.25
-    smalloffset = 0.17
-    squaresize = 0.25
-    # Set up coordinate system.
-    pw = gridpitch * w
-    ph = gridpitch * h
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    # Draw the base grid lines.
-    psprint(ret, "newpath 0.02 setlinewidth")
-    for x in xrange(1,w):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch))
-    for y in xrange(1,h):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    # Draw round the grid exterior.
-    psprint(ret, "newpath")
-    if not wrapping:
-       psprint(ret, "2 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (h * gridpitch, w * gridpitch, -h * gridpitch))
-    psprint(ret, "closepath stroke")
-    # Draw any barriers.
-    psprint(ret, "newpath 2 setlinewidth 1 setlinecap")
-    for x, y in hbarriers:
-       psprint(ret, "%g %g moveto %g 0 rlineto" % \
-       (x * gridpitch, (h - y) * gridpitch, gridpitch))
-    for x, y in vbarriers:
-       psprint(ret, "%g %g moveto 0 -%g rlineto" % \
-       (x * gridpitch, (h - y) * gridpitch, gridpitch))
-    psprint(ret, "stroke")
-    # And draw the symbol in each box.
-    for i in xrange(len(grid)):
-       x = i % w
-       y = i / w
-       v = grid[i]
-       # Rotate to canonical form.
-       if v in (1,2,4,8):
-           v = 1
-       elif v in (5,10):
-           v = 5
-       elif v in (3,6,9,12):
-           v = 9
-       elif v in (7,11,13,14):
-           v = 13
-       # Centre on an area in the corner of the tile.
-       psprint(ret, "gsave")
-       if v & 4:
-           hoffset = bigoffset
-       else:
-           hoffset = smalloffset
-       if v & 2:
-           voffset = bigoffset
-       else:
-           voffset = smalloffset
-       psprint(ret, "%g %g translate" % \
-       ((x + hoffset) * gridpitch, (h - y - voffset) * gridpitch))
-       psprint(ret, "%g dup scale" % (float(gridpitch) * scale / 2))
-       psprint(ret, "newpath 0.07 setlinewidth")
-       # Draw the radial lines.
-       for dx, dy, z in ((1,0,1), (0,1,2), (-1,0,4), (0,-1,8)):
-           if v & z:
-               psprint(ret, "0 0 moveto %d %d lineto" % (dx, dy))
-       psprint(ret, "stroke")
-       # Draw additional figures if desired.
-       if v == 1:
-           # Endpoints have a little empty square at the centre.
-           psprint(ret, "newpath %g %g moveto 0 -%g rlineto" % \
-           (squaresize, squaresize, squaresize * 2))
-           psprint(ret, "-%g 0 rlineto 0 %g rlineto closepath fill" % \
-           (squaresize * 2, squaresize * 2))
-       # Get back out of the centre section.
-       psprint(ret, "grestore")
-       # Draw the endpoint square in large in the middle.
-       if v == 1:
-           psprint(ret, "gsave")
-           psprint(ret, "%g %g translate" % \
-           ((x + 0.5) * gridpitch, (h - y - 0.5) * gridpitch))
-           psprint(ret, "%g dup scale" % (float(gridpitch) / 2))
-           psprint(ret, "newpath %g %g moveto 0 -%g rlineto" % \
-           (squaresize, squaresize, squaresize * 2))
-           psprint(ret, "-%g 0 rlineto 0 %g rlineto closepath fill" % \
-           (squaresize * 2, squaresize * 2))
-           psprint(ret, "grestore")
-    return ret.coords, ret.s
-
-def pattern_format(s):
-    ret = Holder()
-    ret.s = ""
-    # Parse the game ID.
-    params, seed = string.split(s, ":")
-    w, h = map(string.atoi, string.split(params, "x"))
-    rowdata = map(lambda s: string.split(s, "."), string.split(seed, "/"))
-    assert len(rowdata) == w+h
-    # I'm going to arbitrarily choose to use 7pt text for the
-    # numbers, and a 14pt grid pitch.
-    textht = 7
-    gridpitch = 14
-    gutter = 8 # between the numbers and the grid
-    # Find the maximum number of numbers in each dimension, to
-    # determine the border size required.
-    xborder = reduce(max, map(len, rowdata[w:]))
-    yborder = reduce(max, map(len, rowdata[:w]))
-    # Set up coordinate system. I'm going to put the origin at the
-    # _top left_ of the grid, so that both sets of numbers get
-    # drawn the same way.
-    pw = (w + xborder) * gridpitch + gutter
-    ph = (h + yborder) * gridpitch + gutter
-    ret.coords = (xborder * gridpitch + gutter, w * gridpitch, \
-    yborder * gridpitch + gutter, h * gridpitch)
-    # Draw the internal grid lines. Every fifth one is thicker, as
-    # a visual aid.
-    psprint(ret, "newpath 0.1 setlinewidth")
-    for x in xrange(1,w):
-       if x % 5 != 0:
-           psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, -h * gridpitch))
-    for y in xrange(1,h):
-       if y % 5 != 0:
-           psprint(ret, "0 %g moveto %g 0 rlineto" % (-y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    psprint(ret, "newpath 0.75 setlinewidth")
-    for x in xrange(5,w,5):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, -h * gridpitch))
-    for y in xrange(5,h,5):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (-y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    # Draw round the grid exterior.
-    psprint(ret, "newpath 1.5 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (-h * gridpitch, w * gridpitch, h * gridpitch))
-    psprint(ret, "closepath stroke")
-    # And draw the numbers.
-    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
-    for i in range(w+h):
-       ns = rowdata[i]
-       if i < w:
-           xo = (i + 0.5) * gridpitch
-           yo = (gutter + 0.5 * gridpitch)
-       else:
-           xo = -(gutter + 0.5 * gridpitch)
-           yo = ((i-w) + 0.5) * -gridpitch
-       for j in range(len(ns)-1, -1, -1):
-           psprint(ret, "%g %g (%s) ctshow" % (xo, yo, ns[j]))
-           if i < w:
-               yo = yo + gridpitch
-           else:
-               xo = xo - gridpitch
-    return ret.coords, ret.s
-
-def solo_format(s):
-    ret = Holder()
-    ret.s = ""
-    # Parse the game ID.
-    params, seed = string.split(s, ":")
-    c, r = map(string.atoi, string.split(params, "x"))
-    cr = c*r
-    grid = []
-    while len(seed) > 0:
-       if seed[0] in '_'+string.lowercase:
-           if seed[0] in string.lowercase:
-               grid.extend([-1] * (ord(seed[0]) - ord('a') + 1))
-           seed = seed[1:]
-       elif seed[0] in string.digits:
-           ns = ""
-           while len(seed) > 0 and seed[0] in string.digits:
-               ns = ns + seed[0]
-               seed = seed[1:]
-           grid.append(string.atoi(ns))
-    assert cr * cr == len(grid)
-    # I'm going to arbitrarily choose to use 9pt text for the
-    # numbers, and a 16pt grid pitch.
-    textht = 9
-    gridpitch = 16
-    # Set up coordinate system.
-    pw = ph = gridpitch * cr
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    # Draw the thin internal grid lines.
-    psprint(ret, "newpath 0.1 setlinewidth")
-    for x in xrange(1,cr):
-       if x % r != 0:
-           psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, cr * gridpitch))
-    for y in xrange(1,cr):
-       if y % c != 0:
-           psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, cr * gridpitch))
-    psprint(ret, "stroke")
-    # Draw the thicker internal grid lines.
-    psprint(ret, "newpath 1 setlinewidth")
-    for x in xrange(r,cr,r):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, cr * gridpitch))
-    for y in xrange(c,cr,c):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, cr * gridpitch))
-    psprint(ret, "stroke")
-    # Draw round the grid exterior, thicker still.
-    psprint(ret, "newpath 1.5 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (cr * gridpitch, cr * gridpitch, -cr * gridpitch))
-    psprint(ret, "closepath stroke")
-    # And draw the numbers.
-    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
-    for y in xrange(cr):
-       for x in xrange(cr):
-           n = grid[y*cr+x]
-           if n > 0:
-               if n > 9:
-                   s = chr(ord('a') + n - 10)
-               else:
-                   s = chr(ord('0') + n)
-               psprint(ret, "%g %g (%s) ctshow" % \
-               ((x+0.5)*gridpitch, (cr-y-0.5)*gridpitch, s))
-    return ret.coords, ret.s
-
-def dominosa_format(s):
-    ret = Holder()
-    ret.s = ""
-    params, seed = string.split(s, ":")
-    n = string.atoi(params)
-    w = n+2
-    h = n+1
-    grid = []
-    while len(seed) > 0:
-        if seed[0] == '[': # XXX
-            d, seed = string.split(seed[1:], "]")
-            grid.append(string.atoi(d))
-        else:
-            assert seed[0] in string.digits
-            grid.append(string.atoi(seed[0:1]))
-            seed = seed[1:]
-    assert w*h == len(grid)
-    # I'm going to arbitrarily choose to use 9pt text for the
-    # numbers, and a 16pt grid pitch.
-    textht = 9
-    gridpitch = 16
-    pw = gridpitch * w
-    ph = gridpitch * h
-    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    for y in xrange(h):
-        for x in xrange(w):
-            psprint(ret, "%g %g (%d) ctshow" % \
-                ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, grid[y*w+x]))
-    return ret.coords, ret.s
-
-def slant_format(s):
-    # Parse the game ID.
-    ret = Holder()
-    ret.s = ""
-    params, seed = string.split(s, ":")
-    w, h = map(string.atoi, string.split(params, "x"))
-    W = w+1
-    H = h+1
-    grid = []
-    while len(seed) > 0:
-       if seed[0] in string.lowercase:
-           grid.extend([-1] * (ord(seed[0]) - ord('a') + 1))
-           seed = seed[1:]
-       elif seed[0] in "01234":
-           grid.append(string.atoi(seed[0]))
-           seed = seed[1:]
-    assert W * H == len(grid)
-    # I'm going to arbitrarily choose to use 7pt text for the
-    # numbers, and a 14pt grid pitch.
-    textht = 7
-    gridpitch = 14
-    radius = textht * 2.0 / 3.0
-    # Set up coordinate system.
-    pw = gridpitch * w
-    ph = gridpitch * h
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    # Draw round the grid exterior, thickly.
-    psprint(ret, "newpath 1 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (h * gridpitch, w * gridpitch, -h * gridpitch))
-    psprint(ret, "closepath stroke")
-    # Draw the internal grid lines, _very_ thin (the player will
-    # need to draw over them visibly).
-    psprint(ret, "newpath 0.01 setlinewidth")
-    for x in xrange(1,w):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch))
-    for y in xrange(1,h):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    # And draw the numbers.
-    psprint(ret, "/Helvetica findfont %g scalefont setfont" % textht)
-    for y in xrange(H):
-       for x in xrange(W):
-           n = grid[y*W+x]
-           if n >= 0:
-               psprint(ret, "newpath %g %g %g 0 360 arc" % \
-               ((x)*gridpitch, (h-y)*gridpitch, radius),
-               "gsave 1 setgray fill grestore stroke")
-               psprint(ret, "%g %g (%d) ctshow" % \
-               ((x)*gridpitch, (h-y)*gridpitch, n))
-    return ret.coords, ret.s
-
-def lightup_format(s):
-    # Parse the game ID.
-    ret = Holder()
-    ret.s = ""
-    params, seed = string.split(s, ":")
-    w, h = map(string.atoi, string.split(params, "x"))
-    grid = []
-    while len(seed) > 0:
-       if seed[0] in string.lowercase:
-           grid.extend([-2] * (ord(seed[0]) - ord('a') + 1))
-           seed = seed[1:]
-       elif seed[0] == "B":
-           grid.append(-1)
-           seed = seed[1:]
-       elif seed[0] in "01234":
-           grid.append(string.atoi(seed[0]))
-           seed = seed[1:]
-    assert w * h == len(grid)
-    # I'm going to arbitrarily choose to use 9pt text for the
-    # numbers, and a 14pt grid pitch.
-    textht = 10
-    gridpitch = 14
-    # Set up coordinate system.
-    pw = gridpitch * w
-    ph = gridpitch * h
-    ret.coords = (pw/2, pw/2, ph/2, ph/2)
-    psprint(ret, "%g %g translate" % (-ret.coords[0], -ret.coords[2]))
-    # Draw round the grid exterior, thickly.
-    psprint(ret, "newpath 1 setlinewidth")
-    psprint(ret, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
-    (h * gridpitch, w * gridpitch, -h * gridpitch))
-    psprint(ret, "closepath stroke")
-    # Draw the internal grid lines.
-    psprint(ret, "newpath 0.02 setlinewidth")
-    for x in xrange(1,w):
-       psprint(ret, "%g 0 moveto 0 %g rlineto" % (x * gridpitch, h * gridpitch))
-    for y in xrange(1,h):
-       psprint(ret, "0 %g moveto %g 0 rlineto" % (y * gridpitch, w * gridpitch))
-    psprint(ret, "stroke")
-    # And draw the black squares and numbers.
-    psprint(ret, "/Helvetica-Bold findfont %g scalefont setfont" % textht)
-    for y in xrange(h):
-       for x in xrange(w):
-           n = grid[y*w+x]
-           if n >= -1:
-               psprint(ret, ("newpath %g %g moveto 0 %g rlineto " +
-               "%g 0 rlineto 0 %g rlineto closepath fill") % \
-               ((x)*gridpitch, (h-1-y)*gridpitch, gridpitch, gridpitch, \
-               -gridpitch))
-               if n >= 0:
-                   psprint(ret, "gsave 1 setgray %g %g (%d) ctshow grestore" % \
-                   ((x+0.5)*gridpitch, (h-y-0.5)*gridpitch, n))
-    return ret.coords, ret.s
-
-formatters = {
-"net": net_format,
-"rect": rect_format,
-"rectangles": rect_format,
-"pattern": pattern_format,
-"solo": solo_format,
-"dominosa": dominosa_format,
-"slant": slant_format,
-"lightup": lightup_format
-}
-
-if len(sys.argv) < 3:
-    sys.stderr.write("print.py: expected two arguments (game and format)\n")
-    sys.exit(1)
-
-formatter = formatters.get(sys.argv[1], None)
-if formatter == None:
-    sys.stderr.write("print.py: unrecognised game name `%s'\n" % sys.argv[1])
-    sys.exit(1)
-
-try:
-    format = map(string.atoi, string.split(sys.argv[2], "x"))
-except ValueError, e:
-    format = []
-if len(format) != 2:
-    sys.stderr.write("print.py: expected format such as `2x3' as second" \
-    + " argument\n")
-    sys.exit(1)
-
-xx, yy = format
-ppp = xx * yy # puzzles per page
-
-ids = []
-while 1:
-    s = sys.stdin.readline()
-    if s == "": break
-    if s[-1:] == "\n": s = s[:-1]
-    ids.append(s)
-
-pages = int((len(ids) + ppp - 1) / ppp)
-
-# Output initial DSC stuff.
-print "%!PS-Adobe-3.0"
-print "%%Creator: print.py from Simon Tatham's Puzzle Collection"
-print "%%DocumentData: Clean7Bit"
-print "%%LanguageLevel: 1"
-print "%%Pages:", pages
-print "%%DocumentNeededResources:"
-print "%%+ font Helvetica"
-print "%%DocumentSuppliedResources: procset Puzzles 0 0"
-print "%%EndComments"
-print "%%BeginProlog"
-print "%%BeginResource: procset Puzzles 0 0"
-print "/ctshow {"
-print "  3 1 roll"
-print "  newpath 0 0 moveto (X) true charpath flattenpath pathbbox"
-print "  3 -1 roll add 2 div 3 1 roll pop pop sub moveto"
-print "  dup stringwidth pop 0.5 mul neg 0 rmoveto show"
-print "} bind def"
-print "%%EndResource"
-print "%%EndProlog"
-print "%%BeginSetup"
-print "%%IncludeResource: font Helvetica"
-print "%%EndSetup"
-
-# Now do each page.
-puzzle_index = 0;
-
-for i in xrange(1, pages+1):
-    print "%%Page:", i, i
-    print "save"
-
-    # Do the drawing for each puzzle, giving a set of PS fragments
-    # and bounding boxes.
-    fragments = [['' for i in xrange(xx)] for i in xrange(yy)]
-    lrbound = [(0,0) for i in xrange(xx)]
-    tbbound = [(0,0) for i in xrange(yy)]
-
-    for y in xrange(yy):
-       for x in xrange(xx):
-           if puzzle_index >= len(ids):
-               break
-           coords, frag = formatter(ids[puzzle_index])
-           fragments[y][x] = frag
-           lb, rb = lrbound[x]
-           lrbound[x] = (max(lb, coords[0]), max(rb, coords[1]))
-           tb, bb = tbbound[y]
-           tbbound[y] = (max(tb, coords[2]), max(bb, coords[3]))
-           puzzle_index = puzzle_index + 1
-
-    # Now we know the sizes of everything, do the drawing in such a
-    # way that we provide equal gutter space at the page edges and
-    # between puzzle rows/columns.
-    for y in xrange(yy):
-       for x in xrange(xx):
-           if len(fragments[y][x]) > 0:
-               print "gsave"
-               print "clippath flattenpath pathbbox pop pop translate"
-               print "clippath flattenpath pathbbox 4 2 roll pop pop"
-               # Compute the total height of all puzzles, which
-               # we'll use it to work out the amount of gutter
-               # space below this puzzle.
-               htotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound), 0)
-               # Now compute the total height of all puzzles
-               # _below_ this one, plus the height-below-origin of
-               # this one.
-               hbelow = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, tbbound[y+1:]), 0)
-               hbelow = hbelow + tbbound[y][1]
-               print "%g sub %d mul %d div %g add exch" % (htotal, yy-y, yy+1, hbelow)
-               # Now do all the same computations for width,
-               # except we need the total width of everything
-               # _before_ this one since the coordinates work the
-               # other way round.
-               wtotal = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound), 0)
-               # Now compute the total height of all puzzles
-               # _below_ this one, plus the height-below-origin of
-               # this one.
-               wleft = reduce(lambda a,b:a+b, map(lambda (a,b):a+b, lrbound[:x]), 0)
-               wleft = wleft + lrbound[x][0]
-               print "%g sub %d mul %d div %g add exch" % (wtotal, x+1, xx+1, wleft)
-               print "translate"
-               sys.stdout.write(fragments[y][x])
-               print "grestore"
-
-    print "restore showpage"
-
-print "%%EOF"
diff --git a/printing.c b/printing.c
new file mode 100644 (file)
index 0000000..e921a4d
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * printing.c: Cross-platform printing manager. Handles document
+ * setup and layout.
+ */
+
+#include "puzzles.h"
+
+struct puzzle {
+    const game *game;
+    game_params *par;
+    game_state *st;
+    game_state *st2;
+};
+
+struct document {
+    int pw, ph;
+    int npuzzles;
+    struct puzzle *puzzles;
+    int puzzlesize;
+    int got_solns;
+    float *colwid, *rowht;
+    float userscale;
+};
+
+/*
+ * Create a new print document. pw and ph are the layout
+ * parameters: they state how many puzzles will be printed across
+ * the page, and down the page.
+ */
+document *document_new(int pw, int ph, float userscale)
+{
+    document *doc = snew(document);
+
+    doc->pw = pw;
+    doc->ph = ph;
+    doc->puzzles = NULL;
+    doc->puzzlesize = doc->npuzzles = 0;
+    doc->got_solns = FALSE;
+
+    doc->colwid = snewn(pw, float);
+    doc->rowht = snewn(ph, float);
+
+    doc->userscale = userscale;
+
+    return doc;
+}
+
+/*
+ * Free a document structure, whether it's been printed or not.
+ */
+void document_free(document *doc)
+{
+    int i;
+
+    for (i = 0; i < doc->npuzzles; i++) {
+       doc->puzzles[i].game->free_params(doc->puzzles[i].par);
+       doc->puzzles[i].game->free_game(doc->puzzles[i].st);
+       if (doc->puzzles[i].st2)
+           doc->puzzles[i].game->free_game(doc->puzzles[i].st2);
+    }
+
+    sfree(doc->colwid);
+    sfree(doc->rowht);
+
+    sfree(doc->puzzles);
+    sfree(doc);
+}
+
+/*
+ * Called from midend.c to add a puzzle to be printed. Provides a
+ * game_params (for initial layout computation), a game_state, and
+ * optionally a second game_state to be printed in parallel on
+ * another sheet (typically the solution to the first game_state).
+ */
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2)
+{
+    if (doc->npuzzles >= doc->puzzlesize) {
+       doc->puzzlesize += 32;
+       doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle);
+    }
+    doc->puzzles[doc->npuzzles].game = game;
+    doc->puzzles[doc->npuzzles].par = par;
+    doc->puzzles[doc->npuzzles].st = st;
+    doc->puzzles[doc->npuzzles].st2 = st2;
+    doc->npuzzles++;
+    if (st2)
+       doc->got_solns = TRUE;
+}
+
+static void get_puzzle_size(document *doc, struct puzzle *pz,
+                           float *w, float *h, float *scale)
+{
+    float ww, hh, ourscale;
+
+    /* Get the preferred size of the game, in mm. */
+    pz->game->print_size(pz->par, &ww, &hh);
+
+    /* Adjust for user-supplied scale factor. */
+    ourscale = doc->userscale;
+
+    /*
+     * FIXME: scale it down here if it's too big for the page size.
+     * Rather than do complicated things involving scaling all
+     * columns down in proportion, the simplest approach seems to
+     * me to be to scale down until the game fits within one evenly
+     * divided cell of the page (i.e. width/pw by height/ph).
+     * 
+     * In order to do this step we need the page size available.
+     */
+
+    *scale = ourscale;
+    *w = ww * ourscale;
+    *h = hh * ourscale;
+}
+
+/*
+ * Having accumulated a load of puzzles, actually do the printing.
+ */
+void document_print(document *doc, drawing *dr)
+{
+    int ppp;                          /* puzzles per page */
+    int pages, passes;
+    int page, pass;
+    int pageno;
+
+    ppp = doc->pw * doc->ph;
+    pages = (doc->npuzzles + ppp - 1) / ppp;
+    passes = (doc->got_solns ? 2 : 1);
+
+    print_begin_doc(dr, pages * passes);
+
+    pageno = 1;
+    for (pass = 0; pass < passes; pass++) {
+       for (page = 0; page < pages; page++) {
+           int i, n, offset;
+           float colsum, rowsum;
+
+           print_begin_page(dr, pageno);
+
+           offset = page * ppp;
+           n = min(ppp, doc->npuzzles - offset);
+
+           for (i = 0; i < doc->pw; i++)
+               doc->colwid[i] = 0;
+           for (i = 0; i < doc->ph; i++)
+               doc->rowht[i] = 0;
+
+           /*
+            * Lay the page out by computing all the puzzle sizes.
+            */
+           for (i = 0; i < n; i++) {
+               struct puzzle *pz = doc->puzzles + offset + i;
+               int x = i % doc->pw, y = i / doc->pw;
+               float w, h, scale;
+
+               get_puzzle_size(doc, pz, &w, &h, &scale);
+
+               /* Update the maximum width/height of this column. */
+               doc->colwid[x] = max(doc->colwid[x], w);
+               doc->rowht[y] = max(doc->rowht[y], h);
+           }
+
+           /*
+            * Add up the maximum column/row widths to get the
+            * total amount of space used up by puzzles on the
+            * page. We will use this to compute gutter widths.
+            */
+           colsum = 0.0;
+           for (i = 0; i < doc->pw; i++)
+               colsum += doc->colwid[i];
+           rowsum = 0.0;
+           for (i = 0; i < doc->ph; i++)
+               rowsum += doc->rowht[i];
+
+           /*
+            * Now do the printing.
+            */
+           for (i = 0; i < n; i++) {
+               struct puzzle *pz = doc->puzzles + offset + i;
+               int x = i % doc->pw, y = i / doc->pw, j;
+               float w, h, scale, xm, xc, ym, yc;
+               int pixw, pixh, tilesize;
+
+               if (pass == 1 && !pz->st2)
+                   continue;          /* nothing to do */
+
+               /*
+                * The total amount of gutter space is the page
+                * width minus colsum. This is divided into pw+1
+                * gutters, so the amount of horizontal gutter
+                * space appearing to the left of this puzzle
+                * column is
+                * 
+                *   (width-colsum) * (x+1)/(pw+1)
+                * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
+                */
+               xm = (float)(x+1) / (doc->pw + 1);
+               xc = -xm * colsum;
+               /* And similarly for y. */
+               ym = (float)(y+1) / (doc->ph + 1);
+               yc = -ym * rowsum;
+
+               /*
+                * However, the amount of space to the left of this
+                * puzzle isn't just gutter space: we must also
+                * count the widths of all the previous columns.
+                */
+               for (j = 0; j < x; j++)
+                   xc += doc->colwid[j];
+               /* And similarly for rows. */
+               for (j = 0; j < y; j++)
+                   yc += doc->rowht[j];
+
+               /*
+                * Now we adjust for this _specific_ puzzle, which
+                * means centring it within the cell we've just
+                * computed.
+                */
+               get_puzzle_size(doc, pz, &w, &h, &scale);
+               xc += (doc->colwid[x] - w) / 2;
+               yc += (doc->rowht[y] - h) / 2;
+
+               /*
+                * And now we know where and how big we want to
+                * print the puzzle, just go ahead and do so. For
+                * the moment I'll pick a standard pixel tile size
+                * of 512.
+                * 
+                * (FIXME: would it be better to pick this value
+                * with reference to the printer resolution? Or
+                * permit each game to choose its own?)
+                */
+               tilesize = 512;
+               pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
+               print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
+               pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
+               print_end_puzzle(dr);
+           }
+
+           print_end_page(dr, pageno);
+           pageno++;
+       }
+    }
+
+    print_end_doc(dr);
+}
diff --git a/ps.c b/ps.c
new file mode 100644 (file)
index 0000000..a2fd619
--- /dev/null
+++ b/ps.c
@@ -0,0 +1,351 @@
+/*
+ * ps.c: PostScript printing functions.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+#include "puzzles.h"
+
+#define ROOT2 1.414213562
+
+struct psdata {
+    FILE *fp;
+    int colour;
+    int ytop;
+    int clipped;
+    float hatchthick, hatchspace;
+    int gamewidth, gameheight;
+    drawing *drawing;
+};
+
+static void ps_printf(psdata *ps, char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(ps->fp, fmt, ap);
+    va_end(ap);
+}
+
+static void ps_fill(psdata *ps, int colour)
+{
+    int hatch;
+    float r, g, b;
+
+    print_get_colour(ps->drawing, colour, &hatch, &r, &g, &b);
+
+    if (ps->colour) {
+       ps_printf(ps, "%g %g %g setrgbcolor fill\n", r, g, b);
+    } else if (hatch == HATCH_SOLID || hatch == HATCH_CLEAR) {
+       ps_printf(ps, "%d setgray fill\n", hatch == HATCH_CLEAR);
+    } else {
+       /* Clip to the region. */
+       ps_printf(ps, "gsave clip\n");
+       /* Hatch the entire game printing area. */
+       ps_printf(ps, "newpath\n");
+       if (hatch == HATCH_VERT || hatch == HATCH_PLUS)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 moveto 0 %d rlineto\n"
+                     "} for\n", ps->hatchspace, ps->gamewidth,
+                     ps->gameheight);
+       if (hatch == HATCH_HORIZ || hatch == HATCH_PLUS)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 exch moveto %d 0 rlineto\n"
+                     "} for\n", ps->hatchspace, ps->gameheight,
+                     ps->gamewidth);
+       if (hatch == HATCH_SLASH || hatch == HATCH_X)
+           ps_printf(ps, "%d %g %d {\n"
+                     "  0 moveto %d dup rlineto\n"
+                     "} for\n", -ps->gameheight, ps->hatchspace * ROOT2,
+                     ps->gamewidth, max(ps->gamewidth, ps->gameheight));
+       if (hatch == HATCH_BACKSLASH || hatch == HATCH_X)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 moveto %d neg dup neg rlineto\n"
+                     "} for\n", ps->hatchspace * ROOT2,
+                     ps->gamewidth+ps->gameheight,
+                     max(ps->gamewidth, ps->gameheight));
+       ps_printf(ps, "0 setgray %g setlinewidth stroke grestore\n",
+                 ps->hatchthick);
+    }
+}
+
+static void ps_setcolour_internal(psdata *ps, int colour, char *suffix)
+{
+    int hatch;
+    float r, g, b;
+
+    print_get_colour(ps->drawing, colour, &hatch, &r, &g, &b);
+
+    if (ps->colour) {
+       if (r != g || r != b)
+           ps_printf(ps, "%g %g %g setrgbcolor%s\n", r, g, b, suffix);
+       else
+           ps_printf(ps, "%g setgray%s\n", r, suffix);
+    } else {
+       /*
+        * Stroking in hatched colours is not permitted.
+        */
+       assert(hatch == HATCH_SOLID || hatch == HATCH_CLEAR);
+       ps_printf(ps, "%d setgray%s\n", hatch == HATCH_CLEAR, suffix);
+    }
+}
+
+static void ps_setcolour(psdata *ps, int colour)
+{
+    ps_setcolour_internal(ps, colour, "");
+}
+
+static void ps_stroke(psdata *ps, int colour)
+{
+    ps_setcolour_internal(ps, colour, " stroke");
+}
+
+static void ps_draw_text(void *handle, int x, int y, int fonttype,
+                        int fontsize, int align, int colour, char *text)
+{
+    psdata *ps = (psdata *)handle;
+
+    y = ps->ytop - y;
+    ps_setcolour(ps, colour);
+    ps_printf(ps, "/%s findfont %d scalefont setfont\n",
+             fonttype == FONT_FIXED ? "Courier" : "Helvetica",
+             fontsize, x, y);
+    if (align & ALIGN_VCENTRE) {
+       ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
+                 " pathbbox\n"
+                 "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n",
+                 y, x);
+    } else {
+       ps_printf(ps, "%d %d moveto\n", x, y);
+    }
+    ps_printf(ps, "(");
+    while (*text) {
+       if (*text == '\\' || *text == '(' || *text == ')')
+           ps_printf(ps, "\\");
+       ps_printf(ps, "%c", *text);
+       text++;
+    }
+    ps_printf(ps, ") ");
+    if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT))
+       ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n",
+                 (align & ALIGN_HCENTRE) ? "2 div " : "");
+    else
+       ps_printf(ps, "show\n");
+}
+
+static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    psdata *ps = (psdata *)handle;
+
+    y = ps->ytop - y;
+    /*
+     * Offset by half a pixel for the exactness requirement.
+     */
+    ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
+             " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
+    ps_fill(ps, colour);
+}
+
+static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2,
+                        int colour)
+{
+    psdata *ps = (psdata *)handle;
+
+    y1 = ps->ytop - y1;
+    y2 = ps->ytop - y2;
+    ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2);
+    ps_stroke(ps, colour);
+}
+
+static void ps_draw_polygon(void *handle, int *coords, int npoints,
+                           int fillcolour, int outlinecolour)
+{
+    psdata *ps = (psdata *)handle;
+
+    int i;
+
+    ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]);
+
+    for (i = 1; i < npoints; i++)
+       ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]);
+
+    ps_printf(ps, "closepath\n");
+
+    if (fillcolour >= 0) {
+       ps_printf(ps, "gsave\n");
+       ps_fill(ps, fillcolour);
+       ps_printf(ps, "grestore\n");
+    }
+    ps_stroke(ps, outlinecolour);
+}
+
+static void ps_draw_circle(void *handle, int cx, int cy, int radius,
+                          int fillcolour, int outlinecolour)
+{
+    psdata *ps = (psdata *)handle;
+
+    cy = ps->ytop - cy;
+
+    ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius);
+
+    if (fillcolour >= 0) {
+       ps_printf(ps, "gsave\n");
+       ps_fill(ps, fillcolour);
+       ps_printf(ps, "grestore\n");
+    }
+    ps_stroke(ps, outlinecolour);
+}
+
+static void ps_unclip(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    assert(ps->clipped);
+    ps_printf(ps, "grestore\n");
+    ps->clipped = FALSE;
+}
+static void ps_clip(void *handle, int x, int y, int w, int h)
+{
+    psdata *ps = (psdata *)handle;
+
+    if (ps->clipped)
+       ps_unclip(ps);
+
+    y = ps->ytop - y;
+    /*
+     * Offset by half a pixel for the exactness requirement.
+     */
+    ps_printf(ps, "gsave\n");
+    ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
+             " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
+    ps_printf(ps, "clip\n");
+    ps->clipped = TRUE;
+}
+
+static void ps_line_width(void *handle, float width)
+{
+    psdata *ps = (psdata *)handle;
+
+    ps_printf(ps, "%g setlinewidth\n", width);
+}
+
+static void ps_begin_doc(void *handle, int pages)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("%!PS-Adobe-3.0\n", ps->fp);
+    fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp);
+    fputs("%%DocumentData: Clean7Bit\n", ps->fp);
+    fputs("%%LanguageLevel: 1\n", ps->fp);
+    fprintf(ps->fp, "%%%%Pages: %d\n", pages);
+    fputs("%%DocumentNeededResources:\n", ps->fp);
+    fputs("%%+ font Helvetica\n", ps->fp);
+    fputs("%%+ font Courier\n", ps->fp);
+    fputs("%%EndComments\n", ps->fp);
+    fputs("%%BeginSetup\n", ps->fp);
+    fputs("%%IncludeResource: font Helvetica\n", ps->fp);
+    fputs("%%IncludeResource: font Courier\n", ps->fp);
+    fputs("%%EndSetup\n", ps->fp);
+}
+
+static void ps_begin_page(void *handle, int number)
+{
+    psdata *ps = (psdata *)handle;
+
+    fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n",
+           number, number, 72.0 / 25.4);
+}
+
+static void ps_begin_puzzle(void *handle, float xm, float xc,
+                           float ym, float yc, int pw, int ph, float wmm)
+{
+    psdata *ps = (psdata *)handle;
+
+    fprintf(ps->fp, "gsave\n"
+           "clippath flattenpath pathbbox pop pop translate\n"
+           "clippath flattenpath pathbbox 4 2 roll pop pop\n"
+           "exch %g mul %g add exch dup %g mul %g add sub translate\n"
+           "%g dup scale\n"
+           "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph);
+    ps->ytop = ph;
+    ps->clipped = FALSE;
+    ps->gamewidth = pw;
+    ps->gameheight = ph;
+    ps->hatchthick = 0.2 * pw / wmm;
+    ps->hatchspace = 1.0 * pw / wmm;
+}
+
+static void ps_end_puzzle(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("grestore\n", ps->fp);
+}
+
+static void ps_end_page(void *handle, int number)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("restore grestore showpage\n", ps->fp);
+}
+
+static void ps_end_doc(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("%%EOF\n", ps->fp);
+}
+
+static const struct drawing_api ps_drawing = {
+    ps_draw_text,
+    ps_draw_rect,
+    ps_draw_line,
+    ps_draw_polygon,
+    ps_draw_circle,
+    NULL /* draw_update */,
+    ps_clip,
+    ps_unclip,
+    NULL /* start_draw */,
+    NULL /* end_draw */,
+    NULL /* status_bar */,
+    NULL /* blitter_new */,
+    NULL /* blitter_free */,
+    NULL /* blitter_save */,
+    NULL /* blitter_load */,
+    ps_begin_doc,
+    ps_begin_page,
+    ps_begin_puzzle,
+    ps_end_puzzle,
+    ps_end_page,
+    ps_end_doc,
+    ps_line_width,
+};
+
+psdata *ps_init(FILE *outfile, int colour)
+{
+    psdata *ps = snew(psdata);
+
+    ps->fp = outfile;
+    ps->colour = colour;
+    ps->ytop = 0;
+    ps->clipped = FALSE;
+    ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0;
+    ps->drawing = drawing_init(&ps_drawing, ps);
+
+    return ps;
+}
+
+void ps_free(psdata *ps)
+{
+    drawing_free(ps->drawing);
+    sfree(ps);
+}
+
+drawing *ps_drawing_api(psdata *ps)
+{
+    return ps->drawing;
+}
index 4762c7cbf42580dd43c247cdf24788a281fceec8..007dfee49a65d2e121e8fcf4bcebb69a65de7b1f 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -5,6 +5,7 @@
 #ifndef PUZZLES_PUZZLES_H
 #define PUZZLES_PUZZLES_H
 
+#include <stdio.h>  /* for FILE */
 #include <stdlib.h> /* for size_t */
 
 #ifndef TRUE
@@ -70,7 +71,7 @@ enum {
 
 typedef struct frontend frontend;
 typedef struct config_item config_item;
-typedef struct midend_data midend_data;
+typedef struct midend midend;
 typedef struct random_state random_state;
 typedef struct game_params game_params;
 typedef struct game_state game_state;
@@ -78,6 +79,10 @@ typedef struct game_ui game_ui;
 typedef struct game_drawstate game_drawstate;
 typedef struct game game;
 typedef struct blitter blitter;
+typedef struct document document;
+typedef struct drawing_api drawing_api;
+typedef struct drawing drawing;
+typedef struct psdata psdata;
 
 #define ALIGN_VNORMAL 0x000
 #define ALIGN_VCENTRE 0x100
@@ -89,6 +94,16 @@ typedef struct blitter blitter;
 #define FONT_FIXED    0
 #define FONT_VARIABLE 1
 
+/* For printing colours */
+#define HATCH_SOLID 0
+#define HATCH_CLEAR 1
+#define HATCH_SLASH 2
+#define HATCH_BACKSLASH 3
+#define HATCH_HORIZ 4
+#define HATCH_VERT 5
+#define HATCH_PLUS 6
+#define HATCH_X 7
+
 /*
  * Structure used to pass configuration data between frontend and
  * game
@@ -135,67 +150,90 @@ void debug_printf(char *fmt, ...);
 
 void fatal(char *fmt, ...);
 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);
 void deactivate_timer(frontend *fe);
 void activate_timer(frontend *fe);
-void status_bar(frontend *fe, char *text);
 void get_random_seed(void **randseed, int *randseedsize);
 
-blitter *blitter_new(int w, int h);
-void blitter_free(blitter *bl);
+/*
+ * drawing.c
+ */
+drawing *drawing_init(const drawing_api *api, void *handle);
+void drawing_free(drawing *dr);
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text);
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour);
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour);
+void draw_polygon(drawing *dr, int *coords, int npoints,
+                  int fillcolour, int outlinecolour);
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour);
+void clip(drawing *dr, int x, int y, int w, int h);
+void unclip(drawing *dr);
+void start_draw(drawing *dr);
+void draw_update(drawing *dr, int x, int y, int w, int h);
+void end_draw(drawing *dr);
+void status_bar(drawing *dr, char *text);
+blitter *blitter_new(drawing *dr, int w, int h);
+void blitter_free(drawing *dr, blitter *bl);
 /* save puts the portion of the current display with top-left corner
  * (x,y) to the blitter. load puts it back again to the specified
  * coords, or else wherever it was saved from
  * (if x = y = BLITTER_FROMSAVED). */
-void blitter_save(frontend *fe, blitter *bl, int x, int y);
+void blitter_save(drawing *dr, blitter *bl, int x, int y);
 #define BLITTER_FROMSAVED (-1)
-void blitter_load(frontend *fe, blitter *bl, int x, int y);
+void blitter_load(drawing *dr, blitter *bl, int x, int y);
+void print_begin_doc(drawing *dr, int pages);
+void print_begin_page(drawing *dr, int number);
+void print_begin_puzzle(drawing *dr, float xm, float xc,
+                       float ym, float yc, int pw, int ph, float wmm,
+                       float scale);
+void print_end_puzzle(drawing *dr);
+void print_end_page(drawing *dr, int number);
+void print_end_doc(drawing *dr);
+void print_get_colour(drawing *dr, int colour, int *hatch,
+                     float *r, float *g, float *b);
+int print_mono_colour(drawing *dr, int grey); /* 0==black, 1==white */
+int print_grey_colour(drawing *dr, int hatch, float grey);
+int print_rgb_colour(drawing *dr, int hatch, float r, float g, float b);
+void print_line_width(drawing *dr, int width);
 
 /*
  * midend.c
  */
-midend_data *midend_new(frontend *fe, const game *ourgame);
-void midend_free(midend_data *me);
-void midend_set_params(midend_data *me, game_params *params);
-void midend_size(midend_data *me, int *x, int *y, int expand);
-void midend_new_game(midend_data *me);
-void midend_restart_game(midend_data *me);
-void midend_stop_anim(midend_data *me);
-int midend_process_key(midend_data *me, int x, int y, int button);
-void midend_force_redraw(midend_data *me);
-void midend_redraw(midend_data *me);
-float *midend_colours(midend_data *me, int *ncolours);
-void midend_timer(midend_data *me, float tplus);
-int midend_num_presets(midend_data *me);
-void midend_fetch_preset(midend_data *me, int n,
+midend *midend_new(frontend *fe, const game *ourgame,
+                  const drawing_api *drapi, void *drhandle);
+void midend_free(midend *me);
+void midend_set_params(midend *me, game_params *params);
+void midend_size(midend *me, int *x, int *y, int expand);
+void midend_new_game(midend *me);
+void midend_restart_game(midend *me);
+void midend_stop_anim(midend *me);
+int midend_process_key(midend *me, int x, int y, int button);
+void midend_force_redraw(midend *me);
+void midend_redraw(midend *me);
+float *midend_colours(midend *me, int *ncolours);
+void midend_timer(midend *me, float tplus);
+int midend_num_presets(midend *me);
+void midend_fetch_preset(midend *me, int n,
                          char **name, game_params **params);
-int midend_wants_statusbar(midend_data *me);
+int midend_wants_statusbar(midend *me);
 enum { CFG_SETTINGS, CFG_SEED, CFG_DESC };
-config_item *midend_get_config(midend_data *me, int which, char **wintitle);
-char *midend_set_config(midend_data *me, int which, config_item *cfg);
-char *midend_game_id(midend_data *me, char *id);
-char *midend_text_format(midend_data *me);
-char *midend_solve(midend_data *me);
-void midend_supersede_game_desc(midend_data *me, char *desc, char *privdesc);
-char *midend_rewrite_statusbar(midend_data *me, char *text);
-void midend_serialise(midend_data *me,
+config_item *midend_get_config(midend *me, int which, char **wintitle);
+char *midend_set_config(midend *me, int which, config_item *cfg);
+char *midend_game_id(midend *me, char *id);
+char *midend_get_game_id(midend *me);
+char *midend_text_format(midend *me);
+char *midend_solve(midend *me);
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc);
+char *midend_rewrite_statusbar(midend *me, char *text);
+void midend_serialise(midend *me,
                       void (*write)(void *ctx, void *buf, int len),
                       void *wctx);
-char *midend_deserialise(midend_data *me,
+char *midend_deserialise(midend *me,
                          int (*read)(void *ctx, void *buf, int len),
                          void *rctx);
+/* Printing functions supplied by the mid-end */
+char *midend_print_puzzle(midend *me, document *doc, int with_soln);
 
 /*
  * malloc.c
@@ -230,8 +268,8 @@ void game_mkhighlight(frontend *fe, float *ret,
 /* Randomly shuffles an array of items. */
 void shuffle(void *array, int nelts, int eltsize, random_state *rs);
 
-/* Draw a rectangle outline, using the frontend's draw_line. */
-void draw_rect_outline(frontend *fe, int x, int y, int w, int h,
+/* Draw a rectangle outline, using the drawing API's draw_line. */
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
                        int colour);
 
 /*
@@ -268,6 +306,22 @@ void SHA_Bytes(SHA_State *s, void *p, int len);
 void SHA_Final(SHA_State *s, unsigned char *output);
 void SHA_Simple(void *p, int len, unsigned char *output);
 
+/*
+ * printing.c
+ */
+document *document_new(int pw, int ph, float userscale);
+void document_free(document *doc);
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2);
+void document_print(document *doc, drawing *dr);
+
+/*
+ * ps.c
+ */
+psdata *ps_init(FILE *outfile, int colour);
+void ps_free(psdata *ps);
+drawing *ps_drawing_api(psdata *ps);
+
 /*
  * Data structure containing the function calls and data specific
  * to a particular game. This is enclosed in a data structure so
@@ -291,7 +345,7 @@ struct game {
     char *(*new_desc)(game_params *params, random_state *rs,
                      char **aux, int interactive);
     char *(*validate_desc)(game_params *params, char *desc);
-    game_state *(*new_game)(midend_data *me, game_params *params, char *desc);
+    game_state *(*new_game)(midend *me, game_params *params, char *desc);
     game_state *(*dup_game)(game_state *state);
     void (*free_game)(game_state *state);
     int can_solve;
@@ -310,23 +364,62 @@ struct game {
     game_state *(*execute_move)(game_state *state, char *move);
     int preferred_tilesize;
     void (*compute_size)(game_params *params, int tilesize, int *x, int *y);
-    void (*set_size)(game_drawstate *ds, game_params *params, int tilesize);
+    void (*set_size)(drawing *dr, game_drawstate *ds,
+                    game_params *params, int tilesize);
     float *(*colours)(frontend *fe, game_state *state, int *ncolours);
-    game_drawstate *(*new_drawstate)(game_state *state);
-    void (*free_drawstate)(game_drawstate *ds);
-    void (*redraw)(frontend *fe, game_drawstate *ds, game_state *oldstate,
+    game_drawstate *(*new_drawstate)(drawing *dr, game_state *state);
+    void (*free_drawstate)(drawing *dr, game_drawstate *ds);
+    void (*redraw)(drawing *dr, game_drawstate *ds, game_state *oldstate,
                   game_state *newstate, int dir, game_ui *ui, float anim_time,
                   float flash_time);
     float (*anim_length)(game_state *oldstate, game_state *newstate, int dir,
                         game_ui *ui);
     float (*flash_length)(game_state *oldstate, game_state *newstate, int dir,
                          game_ui *ui);
+    int can_print, can_print_in_colour;
+    void (*print_size)(game_params *params, float *x, float *y);
+    void (*print)(drawing *dr, game_state *state, int tilesize);
     int (*wants_statusbar)(void);
     int is_timed;
     int (*timing_state)(game_state *state, game_ui *ui);
     int mouse_priorities;
 };
 
+/*
+ * Data structure containing the drawing API implemented by the
+ * front end and also by cross-platform printing modules such as
+ * PostScript.
+ */
+struct drawing_api {
+    void (*draw_text)(void *handle, int x, int y, int fonttype, int fontsize,
+                     int align, int colour, char *text);
+    void (*draw_rect)(void *handle, int x, int y, int w, int h, int colour);
+    void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
+                     int colour);
+    void (*draw_polygon)(void *handle, int *coords, int npoints,
+                        int fillcolour, int outlinecolour);
+    void (*draw_circle)(void *handle, int cx, int cy, int radius,
+                       int fillcolour, int outlinecolour);
+    void (*draw_update)(void *handle, int x, int y, int w, int h);
+    void (*clip)(void *handle, int x, int y, int w, int h);
+    void (*unclip)(void *handle);
+    void (*start_draw)(void *handle);
+    void (*end_draw)(void *handle);
+    void (*status_bar)(void *handle, char *text);
+    blitter *(*blitter_new)(void *handle, int w, int h);
+    void (*blitter_free)(void *handle, blitter *bl);
+    void (*blitter_save)(void *handle, blitter *bl, int x, int y);
+    void (*blitter_load)(void *handle, blitter *bl, int x, int y);
+    void (*begin_doc)(void *handle, int pages);
+    void (*begin_page)(void *handle, int number);
+    void (*begin_puzzle)(void *handle, float xm, float xc,
+                        float ym, float yc, int pw, int ph, float wmm);
+    void (*end_puzzle)(void *handle);
+    void (*end_page)(void *handle, int number);
+    void (*end_doc)(void *handle);
+    void (*line_width)(void *handle, float width);
+};
+
 /*
  * For one-game-at-a-time platforms, there's a single structure
  * like the above, under a fixed name. For all-at-once platforms,
diff --git a/rect.c b/rect.c
index 439ae1f3aec4ea967112bc2d9cf15acaab0abd71..b8b1fe6dce910ecb37f1ecc140aa757a3e3e24ab 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -1869,7 +1869,7 @@ static unsigned char *get_correct(game_state *state)
     return ret;
 }
 
-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)
 {
     game_state *state = snew(game_state);
     int x, y, i, area;
@@ -2522,8 +2522,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = params->h * TILE_SIZE + 2*BORDER + 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;
 }
@@ -2558,7 +2558,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -2574,26 +2574,26 @@ 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->visible);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
                       int x, int y, unsigned char *hedge, unsigned char *vedge,
                       unsigned char *corners, int correct)
 {
     int cx = COORD(x), cy = COORD(y);
     char str[80];
 
-    draw_rect(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
-    draw_rect(fe, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
+    draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
+    draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
              correct ? COL_CORRECT : COL_BACKGROUND);
 
     if (grid(state,x,y)) {
        sprintf(str, "%d", grid(state,x,y));
-       draw_text(fe, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
+       draw_text(dr, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
                  TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
     }
 
@@ -2601,19 +2601,19 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
      * Draw edges.
      */
     if (!HRANGE(state,x,y) || index(state,hedge,x,y))
-       draw_rect(fe, cx, cy, TILE_SIZE+1, 2,
+       draw_rect(dr, cx, cy, TILE_SIZE+1, 2,
                   HRANGE(state,x,y) ? COLOUR(index(state,hedge,x,y)) :
                   COL_LINE);
     if (!HRANGE(state,x,y+1) || index(state,hedge,x,y+1))
-       draw_rect(fe, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
+       draw_rect(dr, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
                   HRANGE(state,x,y+1) ? COLOUR(index(state,hedge,x,y+1)) :
                   COL_LINE);
     if (!VRANGE(state,x,y) || index(state,vedge,x,y))
-       draw_rect(fe, cx, cy, 2, TILE_SIZE+1,
+       draw_rect(dr, cx, cy, 2, TILE_SIZE+1,
                   VRANGE(state,x,y) ? COLOUR(index(state,vedge,x,y)) :
                   COL_LINE);
     if (!VRANGE(state,x+1,y) || index(state,vedge,x+1,y))
-       draw_rect(fe, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
                   VRANGE(state,x+1,y) ? COLOUR(index(state,vedge,x+1,y)) :
                   COL_LINE);
 
@@ -2621,22 +2621,22 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
      * Draw corners.
      */
     if (index(state,corners,x,y))
-       draw_rect(fe, cx, cy, 2, 2,
+       draw_rect(dr, cx, cy, 2, 2,
                   COLOUR(index(state,corners,x,y)));
     if (x+1 < state->w && index(state,corners,x+1,y))
-       draw_rect(fe, cx+TILE_SIZE-1, cy, 2, 2,
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, 2,
                   COLOUR(index(state,corners,x+1,y)));
     if (y+1 < state->h && index(state,corners,x,y+1))
-       draw_rect(fe, cx, cy+TILE_SIZE-1, 2, 2,
+       draw_rect(dr, cx, cy+TILE_SIZE-1, 2, 2,
                   COLOUR(index(state,corners,x,y+1)));
     if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1))
-       draw_rect(fe, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
+       draw_rect(dr, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
                   COLOUR(index(state,corners,x+1,y+1)));
 
-    draw_update(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
+    draw_update(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
 }
 
-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)
 {
@@ -2677,13 +2677,13 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        }
 
     if (!ds->started) {
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  state->w * TILE_SIZE + 2*BORDER + 1,
                  state->h * TILE_SIZE + 2*BORDER + 1, COL_BACKGROUND);
-       draw_rect(fe, COORD(0)-1, COORD(0)-1,
+       draw_rect(dr, COORD(0)-1, COORD(0)-1,
                  ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE);
        ds->started = TRUE;
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    state->w * TILE_SIZE + 2*BORDER + 1,
                    state->h * TILE_SIZE + 2*BORDER + 1);
     }
@@ -2712,7 +2712,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                c |= CORRECT;
 
            if (index(ds,ds->visible,x,y) != c) {
-               draw_tile(fe, ds, state, x, y, hedge, vedge, corners,
+               draw_tile(dr, ds, state, x, y, hedge, vedge, corners,
                           (c & CORRECT) ? 1 : 0);
                index(ds,ds->visible,x,y) = c;
            }
@@ -2735,7 +2735,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         else if (state->completed)
             strcat(buf, "COMPLETED!");
 
-        status_bar(fe, buf);
+        status_bar(dr, buf);
     }
 
     if (hedge != state->hedge) {
@@ -2771,6 +2771,71 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 5mm squares by default.
+     */
+    game_compute_size(params, 500, &pw, &ph);
+    *x = pw / 100.0;
+    *y = ph / 100.0;
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 10);
+    draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, ink);
+
+    /*
+     * Grid. We have to make the grid lines particularly thin,
+     * because users will be drawing lines _along_ them and we want
+     * those lines to be visible.
+     */
+    print_line_width(dr, TILE_SIZE / 256);
+    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, TILE_SIZE / 10);
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           if (HRANGE(state,x,y) && hedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y), ink);
+           if (VRANGE(state,x,y) && vedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x), COORD(y+1), ink);
+       }
+
+    /*
+     * Clues.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (grid(state,x,y)) {
+               char str[80];
+               sprintf(str, "%d", grid(state,x,y));
+               draw_text(dr, COORD(x)+TILE_SIZE/2, COORD(y)+TILE_SIZE/2,
+                         FONT_VARIABLE, TILE_SIZE/2,
+                         ALIGN_HCENTRE | ALIGN_VCENTRE, ink, str);
+           }
+}
+
 #ifdef COMBINED
 #define thegame rect
 #endif
@@ -2806,6 +2871,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index 2331799862695c608256a61feaeb5eb5881f6a45..781cbcc07c0b0f69ed083b58f4d514fd189de374 100644 (file)
@@ -975,7 +975,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)
 {
     game_state *state = snew(game_state);
     char *p = desc;
@@ -1344,8 +1344,8 @@ static game_state *execute_move(game_state *from, char *move)
  * Drawing routines.
  */
 
-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->tilegap = 2;
     ds->tileinner = tilesize - ds->tilegap;
@@ -1356,7 +1356,7 @@ static void game_compute_size(game_params *params, int tilesize,
 {
     /* Ick: fake up tile size variables for macro expansion purposes */
     game_drawstate ads, *ds = &ads;
-    game_set_size(ds, params, tilesize);
+    game_set_size(NULL, ds, params, tilesize);
 
     *x = TILE_SIZE * params->w + 2 * BORDER - TILE_GAP;
     *y = TILE_SIZE * params->h + 2 * BORDER - TILE_GAP;
@@ -1424,7 +1424,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1439,7 +1439,7 @@ 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->tiles);
     sfree(ds);
@@ -1451,7 +1451,7 @@ static void game_free_drawstate(game_drawstate *ds)
  * both then we fill the teeny tiny square in the corner as well.
  */
 
-static void tile_redraw(frontend *fe, game_drawstate *ds,
+static void tile_redraw(drawing *dr, game_drawstate *ds,
                        int x, int y, int dright, int dbelow,
                         int tile, int bgcolour)
 {
@@ -1468,33 +1468,33 @@ static void tile_redraw(frontend *fe, game_drawstate *ds,
            outer = inner = col;
        }
     }
-    draw_rect(fe, COORD(x), COORD(y), TILE_INNER, TILE_INNER, outer);
-    draw_rect(fe, COORD(x)+TILE_INNER/4, COORD(y)+TILE_INNER/4,
+    draw_rect(dr, COORD(x), COORD(y), TILE_INNER, TILE_INNER, outer);
+    draw_rect(dr, COORD(x)+TILE_INNER/4, COORD(y)+TILE_INNER/4,
              TILE_INNER/2, TILE_INNER/2, inner);
 
     if (dright)
-       draw_rect(fe, COORD(x)+TILE_INNER, COORD(y), TILE_GAP, TILE_INNER,
+       draw_rect(dr, COORD(x)+TILE_INNER, COORD(y), TILE_GAP, TILE_INNER,
                  (tile & TILE_JOINRIGHT) ? outer : bgcolour);
     if (dbelow)
-       draw_rect(fe, COORD(x), COORD(y)+TILE_INNER, TILE_INNER, TILE_GAP,
+       draw_rect(dr, COORD(x), COORD(y)+TILE_INNER, TILE_INNER, TILE_GAP,
                  (tile & TILE_JOINDOWN) ? outer : bgcolour);
     if (dright && dbelow)
-       draw_rect(fe, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, TILE_GAP, TILE_GAP,
+       draw_rect(dr, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, TILE_GAP, TILE_GAP,
                  (tile & TILE_JOINDIAG) ? outer : bgcolour);
 
     if (tile & TILE_HASSEL) {
        int sx = COORD(x)+2, sy = COORD(y)+2, ssz = TILE_INNER-5;
        int scol = (outer == COL_SEL) ? COL_LOWLIGHT : COL_HIGHLIGHT;
-       draw_line(fe, sx,     sy,     sx+ssz, sy,     scol);
-       draw_line(fe, sx+ssz, sy,     sx+ssz, sy+ssz, scol);
-       draw_line(fe, sx+ssz, sy+ssz, sx,     sy+ssz, scol);
-       draw_line(fe, sx,     sy+ssz, sx,     sy,     scol);
+       draw_line(dr, sx,     sy,     sx+ssz, sy,     scol);
+       draw_line(dr, sx+ssz, sy,     sx+ssz, sy+ssz, scol);
+       draw_line(dr, sx+ssz, sy+ssz, sx,     sy+ssz, scol);
+       draw_line(dr, sx,     sy+ssz, sx,     sy,     scol);
     }
 
-    draw_update(fe, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE);
+    draw_update(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE);
 }
 
-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)
 {
@@ -1505,10 +1505,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
        int coords[10];
 
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILE_SIZE * state->params.w + 2 * BORDER,
                  TILE_SIZE * state->params.h + 2 * BORDER, COL_BACKGROUND);
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILE_SIZE * state->params.w + 2 * BORDER,
                    TILE_SIZE * state->params.h + 2 * BORDER);
 
@@ -1525,11 +1525,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        coords[9] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
        coords[6] = coords[8] + TILE_SIZE;
        coords[7] = coords[9] - TILE_SIZE;
-       draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+       draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
-       draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+       draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
        ds->started = 1;
     }
@@ -1567,7 +1567,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            if ((oldstate && COL(oldstate,x,y) != col) ||
                (ds->bgcolour != bgcolour) ||
                (tile != ds->tiles[i])) {
-               tile_redraw(fe, ds, x, y, dright, dbelow, tile, bgcolour);
+               tile_redraw(dr, ds, x, y, dright, dbelow, tile, bgcolour);
                ds->tiles[i] = tile;
            }
        }
@@ -1588,7 +1588,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                    score, ui->nselected, npoints(&state->params, ui->nselected));
        else
            sprintf(status, "%s", score);
-       status_bar(fe, status);
+       status_bar(dr, status);
     }
 }
 
@@ -1618,6 +1618,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame samegame
 #endif
@@ -1653,6 +1661,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index 457e9c9c4a7961ed44e6fa2872d1b49d21d21bc7..51771a80374adc675a0f62798b219dad8c48d477 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -450,7 +450,7 @@ static char *validate_desc(game_params *params, char *desc)
     return err;
 }
 
-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)
 {
     game_state *state = snew(game_state);
     int i;
@@ -696,8 +696,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILE_SIZE * params->h + 2 * BORDER;
 }
 
-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;
 }
@@ -716,7 +716,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -733,18 +733,18 @@ 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->tiles);
     sfree(ds);
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds,
+static void draw_tile(drawing *dr, game_drawstate *ds,
                       game_state *state, int x, int y,
                       int tile, int flash_colour)
 {
     if (tile == 0) {
-        draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
                   flash_colour);
     } else {
         int coords[6];
@@ -756,25 +756,25 @@ static void draw_tile(frontend *fe, game_drawstate *ds,
         coords[3] = y;
         coords[4] = x;
         coords[5] = y + TILE_SIZE - 1;
-        draw_polygon(fe, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
 
         coords[0] = x;
         coords[1] = y;
-        draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
-        draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+        draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
                   TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
                   flash_colour);
 
         sprintf(str, "%d", tile);
-        draw_text(fe, x + TILE_SIZE/2, y + TILE_SIZE/2,
+        draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2,
                   FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
                   COL_TEXT, str);
     }
-    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 }
 
-static void draw_arrow(frontend *fe, game_drawstate *ds,
+static void draw_arrow(drawing *dr, game_drawstate *ds,
                        int x, int y, int xdx, int xdy)
 {
     int coords[14];
@@ -792,10 +792,10 @@ static void draw_arrow(frontend *fe, game_drawstate *ds,
     POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2);   /* left concave */
     POINT(6,     TILE_SIZE / 4, TILE_SIZE / 2);   /* left corner */
 
-    draw_polygon(fe, coords, 7, COL_LOWLIGHT, COL_TEXT);
+    draw_polygon(dr, coords, 7, COL_LOWLIGHT, COL_TEXT);
 }
 
-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)
 {
@@ -810,10 +810,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
         int coords[10];
 
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILE_SIZE * state->w + 2 * BORDER,
                  TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILE_SIZE * state->w + 2 * BORDER,
                    TILE_SIZE * state->h + 2 * BORDER);
 
@@ -830,22 +830,22 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
         coords[6] = coords[8] + TILE_SIZE;
         coords[7] = coords[9] - TILE_SIZE;
-        draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
         coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
         coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
-        draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
         /*
          * Arrows for making moves.
          */
         for (i = 0; i < state->w; i++) {
-            draw_arrow(fe, ds, COORD(i), COORD(0), +1, 0);
-            draw_arrow(fe, ds, COORD(i+1), COORD(state->h), -1, 0);
+            draw_arrow(dr, ds, COORD(i), COORD(0), +1, 0);
+            draw_arrow(dr, ds, COORD(i+1), COORD(state->h), -1, 0);
         }
         for (i = 0; i < state->h; i++) {
-            draw_arrow(fe, ds, COORD(state->w), COORD(i), 0, +1);
-            draw_arrow(fe, ds, COORD(0), COORD(i+1), 0, -1);
+            draw_arrow(dr, ds, COORD(state->w), COORD(i), 0, +1);
+            draw_arrow(dr, ds, COORD(0), COORD(i+1), 0, -1);
         }
 
         ds->started = TRUE;
@@ -855,7 +855,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      * Now draw each tile.
      */
 
-    clip(fe, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h);
+    clip(dr, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h);
 
     for (i = 0; i < state->n; i++) {
        int t, t0;
@@ -943,14 +943,14 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                x2 = y2 = -1;
            }
 
-           draw_tile(fe, ds, state, x, y, t, bgcolour);
+           draw_tile(dr, ds, state, x, y, t, bgcolour);
            if (x2 != -1 || y2 != -1)
-               draw_tile(fe, ds, state, x2, y2, t, bgcolour);
+               draw_tile(dr, ds, state, x2, y2, t, bgcolour);
        }
        ds->tiles[i] = t0;
     }
 
-    unclip(fe);
+    unclip(dr);
 
     ds->bgcolour = bgcolour;
 
@@ -979,7 +979,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                         state->movetarget);
        }
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 }
 
@@ -1013,6 +1013,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame sixteen
 #endif
@@ -1048,6 +1056,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
diff --git a/slant.c b/slant.c
index 75a346326d742d54cec5d5a2444f3492f6213b81..601ea41aa35d00e75de9fce421df4cf333bf4199 100644 (file)
--- a/slant.c
+++ b/slant.c
@@ -1103,7 +1103,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);
@@ -1601,8 +1601,8 @@ 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;
 }
@@ -1637,7 +1637,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,31 +1653,32 @@ 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, long v, long 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,
+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 */;
@@ -1685,47 +1686,47 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_clues *clues,
     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 +1735,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,
+       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,
+       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,
+       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,
+       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,
+       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 +1783,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;
     }
 
@@ -1844,7 +1846,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)];
            }
@@ -1878,6 +1880,77 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    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;
+    ads.tilesize = 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
 #define thegame slant
 #endif
@@ -1913,6 +1986,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
@@ -1927,19 +2001,22 @@ const struct game thegame = {
  */
 
 void frontend_default_colour(frontend *fe, float *output) {}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+void draw_text(drawing *dr, 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,
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_polygon(drawing *dr, int *coords, int npoints,
                   int fillcolour, int outlinecolour) {}
-void draw_circle(frontend *fe, int cx, int cy, int radius,
+void draw_circle(drawing *dr, 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) {}
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
+int print_mono_colour(drawing *dr, int grey) { return 0; }
+void print_line_width(drawing *dr, int width) {}
 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)
diff --git a/solo.c b/solo.c
index 165e683745be869ed41a5095654e889bf64cc090..c6d39ebcd94635f096c112f3ae252ff50c1a4b62 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -1858,7 +1858,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)
 {
     game_state *state = snew(game_state);
     int c = params->c, r = params->r, cr = c*r, area = cr * cr;
@@ -2252,8 +2252,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = SIZE(params->c * params->r);
 }
 
-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;
 }
@@ -2292,7 +2292,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int c = state->c, r = state->r, cr = c*r;
@@ -2312,7 +2312,7 @@ 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->hl);
     sfree(ds->pencil);
@@ -2321,7 +2321,7 @@ static void game_free_drawstate(game_drawstate *ds)
     sfree(ds);
 }
 
-static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_number(drawing *dr, game_drawstate *ds, game_state *state,
                        int x, int y, int hl)
 {
     int c = state->c, r = state->r, cr = c*r;
@@ -2351,10 +2351,10 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
     if ((y+1) % c)
        ch++;
 
-    clip(fe, cx, cy, cw, ch);
+    clip(dr, cx, cy, cw, ch);
 
     /* background needs erasing */
-    draw_rect(fe, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
+    draw_rect(dr, cx, cy, cw, ch, (hl & 15) == 1 ? COL_HIGHLIGHT : COL_BACKGROUND);
 
     /* pencil-mode highlight */
     if ((hl & 15) == 2) {
@@ -2365,7 +2365,7 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
         coords[3] = cy;
         coords[4] = cx;
         coords[5] = cy+ch/2;
-        draw_polygon(fe, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
     }
 
     /* new number needs drawing? */
@@ -2374,7 +2374,7 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
        str[0] = state->grid[y*cr+x] + '0';
        if (str[0] > '9')
            str[0] += 'a' - ('9'+1);
-       draw_text(fe, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
+       draw_text(dr, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
                  FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
                  state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str);
     } else {
@@ -2409,7 +2409,7 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
                 str[0] = i + '1';
                 if (str[0] > '9')
                     str[0] += 'a' - ('9'+1);
-                draw_text(fe, tx + (4*dx+3) * TILE_SIZE / (4*pw+2),
+                draw_text(dr, tx + (4*dx+3) * TILE_SIZE / (4*pw+2),
                           ty + (4*dy+3) * TILE_SIZE / (4*ph+2),
                           FONT_VARIABLE, fontsize,
                           ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
@@ -2417,16 +2417,16 @@ static void draw_number(frontend *fe, game_drawstate *ds, game_state *state,
             }
     }
 
-    unclip(fe);
+    unclip(dr);
 
-    draw_update(fe, cx, cy, cw, ch);
+    draw_update(dr, cx, cy, cw, ch);
 
     ds->grid[y*cr+x] = state->grid[y*cr+x];
     memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr);
     ds->hl[y*cr+x] = hl;
 }
 
-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)
 {
@@ -2440,19 +2440,19 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         * all games should start by drawing a big
         * background-colour rectangle covering the whole window.
         */
-       draw_rect(fe, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND);
+       draw_rect(dr, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND);
 
        /*
         * Draw the grid.
         */
        for (x = 0; x <= cr; x++) {
            int thick = (x % r ? 0 : 1);
-           draw_rect(fe, BORDER + x*TILE_SIZE - thick, BORDER-1,
+           draw_rect(dr, BORDER + x*TILE_SIZE - thick, BORDER-1,
                      1+2*thick, cr*TILE_SIZE+3, COL_GRID);
        }
        for (y = 0; y <= cr; y++) {
            int thick = (y % c ? 0 : 1);
-           draw_rect(fe, BORDER-1, BORDER + y*TILE_SIZE - thick,
+           draw_rect(dr, BORDER-1, BORDER + y*TILE_SIZE - thick,
                      cr*TILE_SIZE+3, 1+2*thick, COL_GRID);
        }
     }
@@ -2498,7 +2498,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                      (ds->entered_items[((x/r)+(y/c)*c)*cr+d-1] & 32)))
                highlight |= 16;
 
-           draw_number(fe, ds, state, x, y, highlight);
+           draw_number(dr, ds, state, x, y, highlight);
        }
     }
 
@@ -2506,7 +2506,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
      * Update the _entire_ grid if necessary.
      */
     if (!ds->started) {
-       draw_update(fe, 0, 0, SIZE(cr), SIZE(cr));
+       draw_update(dr, 0, 0, SIZE(cr), SIZE(cr));
        ds->started = TRUE;
     }
 }
@@ -2536,6 +2536,68 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 9mm squares by default. They should be quite big
+     * for this game, because players will want to jot down no end
+     * of pencil marks in the squares.
+     */
+    game_compute_size(params, 900, &pw, &ph);
+    *x = pw / 100.0;
+    *y = ph / 100.0;
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+    int c = state->c, r = state->r, cr = c*r;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, 3 * TILE_SIZE / 40);
+    draw_rect_outline(dr, BORDER, BORDER, cr*TILE_SIZE, cr*TILE_SIZE, ink);
+
+    /*
+     * Grid.
+     */
+    for (x = 1; x < cr; x++) {
+       print_line_width(dr, (x % r ? 1 : 3) * TILE_SIZE / 40);
+       draw_line(dr, BORDER+x*TILE_SIZE, BORDER,
+                 BORDER+x*TILE_SIZE, BORDER+cr*TILE_SIZE, ink);
+    }
+    for (y = 1; y < cr; y++) {
+       print_line_width(dr, (y % c ? 1 : 3) * TILE_SIZE / 40);
+       draw_line(dr, BORDER, BORDER+y*TILE_SIZE,
+                 BORDER+cr*TILE_SIZE, BORDER+y*TILE_SIZE, ink);
+    }
+
+    /*
+     * Numbers.
+     */
+    for (y = 0; y < cr; y++)
+       for (x = 0; x < cr; x++)
+           if (state->grid[y*cr+x]) {
+               char str[2];
+               str[1] = '\0';
+               str[0] = state->grid[y*cr+x] + '0';
+               if (str[0] > '9')
+                   str[0] += 'a' - ('9'+1);
+               draw_text(dr, BORDER + x*TILE_SIZE + TILE_SIZE/2,
+                         BORDER + y*TILE_SIZE + TILE_SIZE/2,
+                         FONT_VARIABLE, TILE_SIZE/2,
+                         ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+           }
+}
+
 #ifdef COMBINED
 #define thegame solo
 #endif
@@ -2571,6 +2633,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    TRUE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
@@ -2583,17 +2646,20 @@ const struct game thegame = {
  */
 
 void frontend_default_colour(frontend *fe, float *output) {}
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+void draw_text(drawing *dr, 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,
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_polygon(drawing *dr, int *coords, int npoints,
                   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) {}
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
+int print_mono_colour(drawing *dr, int grey) { return 0; }
+void print_line_width(drawing *dr, int width) {}
 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)
index 1000ef4bef6c5dba39ee592fbf870cb1c96f63d1..8711ada2f6b7f54c8bde928fbfca01ac4901a6c7 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -460,7 +460,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)
 {
     game_state *state = snew(game_state);
     int w = params->w, h = params->h, n = params->n, wh = w*h;
@@ -755,8 +755,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *y = TILE_SIZE * params->h + 2 * BORDER;
 }
 
-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;
 }
@@ -778,7 +778,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -795,7 +795,7 @@ 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->grid);
     sfree(ds);
@@ -822,7 +822,7 @@ static void rotate(int *xy, struct rotation *rot)
     }
 }
 
-static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
+static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
                       int x, int y, int tile, int flash_colour,
                       struct rotation *rot)
 {
@@ -840,7 +840,7 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
         rot = NULL;
 
     if (rot)
-       clip(fe, rot->cx, rot->cy, rot->cw, rot->ch);
+       clip(dr, rot->cx, rot->cy, rot->cw, rot->ch);
 
     /*
      * We must draw each side of the tile's highlight separately,
@@ -860,28 +860,28 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
     coords[2] = x + TILE_SIZE - 1;
     coords[3] = y;
     rotate(coords+2, rot);
-    draw_polygon(fe, coords, 3, rot ? rot->rc : COL_LOWLIGHT,
+    draw_polygon(dr, coords, 3, rot ? rot->rc : COL_LOWLIGHT,
                 rot ? rot->rc : COL_LOWLIGHT);
 
     /* Bottom side. */
     coords[2] = x;
     coords[3] = y + TILE_SIZE - 1;
     rotate(coords+2, rot);
-    draw_polygon(fe, coords, 3, rot ? rot->bc : COL_LOWLIGHT,
+    draw_polygon(dr, coords, 3, rot ? rot->bc : COL_LOWLIGHT,
                 rot ? rot->bc : COL_LOWLIGHT);
 
     /* Left side. */
     coords[0] = x;
     coords[1] = y;
     rotate(coords+0, rot);
-    draw_polygon(fe, coords, 3, rot ? rot->lc : COL_HIGHLIGHT,
+    draw_polygon(dr, coords, 3, rot ? rot->lc : COL_HIGHLIGHT,
                 rot ? rot->lc : COL_HIGHLIGHT);
 
     /* Top side. */
     coords[2] = x + TILE_SIZE - 1;
     coords[3] = y;
     rotate(coords+2, rot);
-    draw_polygon(fe, coords, 3, rot ? rot->tc : COL_HIGHLIGHT,
+    draw_polygon(dr, coords, 3, rot ? rot->tc : COL_HIGHLIGHT,
                 rot ? rot->tc : COL_HIGHLIGHT);
 
     /*
@@ -900,9 +900,9 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
        coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
        coords[7] = y + HIGHLIGHT_WIDTH;
        rotate(coords+6, rot);
-       draw_polygon(fe, coords, 4, flash_colour, flash_colour);
+       draw_polygon(dr, coords, 4, flash_colour, flash_colour);
     } else {
-       draw_rect(fe, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+       draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
                  TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
                  flash_colour);
     }
@@ -946,21 +946,21 @@ static void draw_tile(frontend *fe, game_drawstate *ds, game_state *state,
        coords[4] = cx - displ * ydx;
        coords[5] = cy - displ * ydy;
        rotate(coords+4, rot);
-       draw_polygon(fe, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE);
+       draw_polygon(dr, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE);
     }
 
     coords[0] = x + TILE_SIZE/2;
     coords[1] = y + TILE_SIZE/2;
     rotate(coords+0, rot);
     sprintf(str, "%d", tile / 4);
-    draw_text(fe, coords[0], coords[1],
+    draw_text(dr, coords[0], coords[1],
              FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
              COL_TEXT, str);
 
     if (rot)
-       unclip(fe);
+       unclip(dr);
 
-    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 }
 
 static int highlight_colour(float angle)
@@ -1023,7 +1023,7 @@ static float game_flash_length(game_state *oldstate, game_state *newstate,
         return 0.0F;
 }
 
-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)
 {
@@ -1040,10 +1040,10 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     if (!ds->started) {
         int coords[10];
 
-       draw_rect(fe, 0, 0,
+       draw_rect(dr, 0, 0,
                  TILE_SIZE * state->w + 2 * BORDER,
                  TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
-       draw_update(fe, 0, 0,
+       draw_update(dr, 0, 0,
                    TILE_SIZE * state->w + 2 * BORDER,
                    TILE_SIZE * state->h + 2 * BORDER);
 
@@ -1060,11 +1060,11 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
         coords[6] = coords[8] + TILE_SIZE;
         coords[7] = coords[9] - TILE_SIZE;
-        draw_polygon(fe, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
         coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
         coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
-        draw_polygon(fe, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
         ds->started = TRUE;
     }
@@ -1106,7 +1106,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        rot->tc = highlight_colour(PI/2 + angle);
        rot->bc = highlight_colour(-PI/2 + angle);
 
-       draw_rect(fe, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour);
+       draw_rect(dr, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour);
     } else
        rot = NULL;
 
@@ -1135,7 +1135,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
            ds->grid[i] != t || ds->grid[i] == -1 || t == -1) {
            int x = COORD(tx), y = COORD(ty);
 
-           draw_tile(fe, ds, state, x, y, state->grid[i], bgcolour, rot);
+           draw_tile(dr, ds, state, x, y, state->grid[i], bgcolour, rot);
             ds->grid[i] = t;
         }
     }
@@ -1166,7 +1166,7 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                         state->movetarget);
         }
 
-       status_bar(fe, statusbuf);
+       status_bar(dr, statusbuf);
     }
 }
 
@@ -1180,6 +1180,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame twiddle
 #endif
@@ -1215,6 +1223,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     0,                                /* mouse_priorities */
index e74fefa76cbb5d8b423550f48415f5021299f755..a88e6d81410ef9f58536550bd63719df83760bd3 100644 (file)
@@ -796,7 +796,7 @@ static void mark_crossings(game_state *state)
        state->completed = TRUE;
 }
 
-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 n = params->n;
     game_state *state = snew(game_state);
@@ -1185,8 +1185,8 @@ static void game_compute_size(game_params *params, int tilesize,
     *x = *y = COORDLIMIT(params->n) * tilesize;
 }
 
-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;
 }
@@ -1235,7 +1235,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)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1251,7 +1251,7 @@ 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->y);
     sfree(ds->x);
@@ -1269,7 +1269,7 @@ static point mix(point a, point b, float distance)
     return ret;
 }
 
-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)
 {
@@ -1329,14 +1329,14 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     ds->bg = bg;
 
     game_compute_size(&state->params, ds->tilesize, &w, &h);
-    draw_rect(fe, 0, 0, w, h, bg);
+    draw_rect(dr, 0, 0, w, h, bg);
 
     /*
      * Draw the edges.
      */
 
     for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
-       draw_line(fe, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
+       draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
 #ifdef SHOW_CROSSINGS
                  (oldstate?oldstate:state)->crosses[i] ?
                  COL_CROSSEDLINE :
@@ -1367,23 +1367,23 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 
            if (c == thisc) {
 #ifdef VERTEX_NUMBERS
-               draw_circle(fe, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg);
+               draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg);
                {
                    char buf[80];
                    sprintf(buf, "%d", i);
-                   draw_text(fe, ds->x[i], ds->y[i], FONT_VARIABLE,
+                   draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE,
                               DRAG_THRESHOLD*3/2,
                              ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf);
                }
 #else
-               draw_circle(fe, ds->x[i], ds->y[i], CIRCLE_RADIUS,
+               draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS,
                             c, COL_OUTLINE);
 #endif
            }
        }
     }
 
-    draw_update(fe, 0, 0, w, h);
+    draw_update(dr, 0, 0, w, h);
 }
 
 static float game_anim_length(game_state *oldstate, game_state *newstate,
@@ -1417,6 +1417,14 @@ static int game_timing_state(game_state *state, game_ui *ui)
     return TRUE;
 }
 
+static void game_print_size(game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, game_state *state, int tilesize)
+{
+}
+
 #ifdef COMBINED
 #define thegame untangle
 #endif
@@ -1452,6 +1460,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    FALSE, FALSE, game_print_size, game_print,
     game_wants_statusbar,
     FALSE, game_timing_state,
     SOLVE_ANIMATES,                   /* mouse_priorities */
index f9af41ca0db24f0ae28385b9ef8ecdfb876866b5..0171baac25a50a6191f1bc54a2c40cbeb6546669 100644 (file)
--- a/windows.c
+++ b/windows.c
@@ -90,7 +90,7 @@ struct blitter {
 };
 
 struct frontend {
-    midend_data *me;
+    midend *me;
     HWND hwnd, statusbar, cfgbox;
     HINSTANCE inst;
     HBITMAP bitmap, prevbm;
@@ -136,8 +136,9 @@ void get_random_seed(void **randseed, int *randseedsize)
     *randseedsize = sizeof(time_t);
 }
 
-void status_bar(frontend *fe, char *text)
+static void win_status_bar(void *handle, char *text)
 {
+    frontend *fe = (frontend *)handle;
     char *rewritten = midend_rewrite_statusbar(fe->me, text);
     if (!fe->laststatus || strcmp(rewritten, fe->laststatus)) {
        SetWindowText(fe->statusbar, rewritten);
@@ -148,7 +149,7 @@ void status_bar(frontend *fe, char *text)
     }
 }
 
-blitter *blitter_new(int w, int h)
+static blitter *win_blitter_new(void *handle, int w, int h)
 {
     blitter *bl = snew(blitter);
 
@@ -160,7 +161,7 @@ blitter *blitter_new(int w, int h)
     return bl;
 }
 
-void blitter_free(blitter *bl)
+static void win_blitter_free(void *handle, blitter *bl)
 {
     if (bl->bitmap) DeleteObject(bl->bitmap);
     sfree(bl);
@@ -175,8 +176,9 @@ static void blitter_mkbitmap(frontend *fe, blitter *bl)
 
 /* BitBlt(dstDC, dstX, dstY, dstW, dstH, srcDC, srcX, srcY, dType) */
 
-void blitter_save(frontend *fe, blitter *bl, int x, int y)
+static void win_blitter_save(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     HDC hdc_win, hdc_blit;
     HBITMAP prev_blit;
 
@@ -201,8 +203,9 @@ void blitter_save(frontend *fe, blitter *bl, int x, int y)
     ReleaseDC(fe->hwnd, hdc_win);
 }
 
-void blitter_load(frontend *fe, blitter *bl, int x, int y)
+static void win_blitter_load(void *handle, blitter *bl, int x, int y)
 {
+    frontend *fe = (frontend *)handle;
     HDC hdc_win, hdc_blit;
     HBITMAP prev_blit;
 
@@ -233,19 +236,22 @@ void frontend_default_colour(frontend *fe, float *output)
     output[2] = (float)(GetBValue(c) / 255.0);
 }
 
-void clip(frontend *fe, int x, int y, int w, int h)
+static void win_clip(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
 }
 
-void unclip(frontend *fe)
+static void win_unclip(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     SelectClipRgn(fe->hdc_bm, NULL);
 }
 
-void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
-               int align, int colour, char *text)
+static void win_draw_text(void *handle, int x, int y, int fonttype,
+                         int fontsize, int align, int colour, char *text)
 {
+    frontend *fe = (frontend *)handle;
     int i;
 
     /*
@@ -304,8 +310,9 @@ void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
     }
 }
 
-void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
+static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour)
 {
+    frontend *fe = (frontend *)handle;
     if (w == 1 && h == 1) {
        /*
         * Rectangle() appears to get uppity if asked to draw a 1x1
@@ -323,8 +330,9 @@ 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)
+static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
 {
+    frontend *fe = (frontend *)handle;
     HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
     MoveToEx(fe->hdc_bm, x1, y1, NULL);
     LineTo(fe->hdc_bm, x2, y2);
@@ -332,9 +340,10 @@ void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
     SelectObject(fe->hdc_bm, oldpen);
 }
 
-void draw_circle(frontend *fe, int cx, int cy, int radius,
-                 int fillcolour, int outlinecolour)
+static void win_draw_circle(void *handle, int cx, int cy, int radius,
+                           int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     assert(outlinecolour >= 0);
 
     if (fillcolour >= 0) {
@@ -353,9 +362,10 @@ void draw_circle(frontend *fe, int cx, int cy, int radius,
     }
 }
 
-void draw_polygon(frontend *fe, int *coords, int npoints,
-                  int fillcolour, int outlinecolour)
+static void win_draw_polygon(void *handle, int *coords, int npoints,
+                            int fillcolour, int outlinecolour)
 {
+    frontend *fe = (frontend *)handle;
     POINT *pts = snewn(npoints+1, POINT);
     int i;
 
@@ -382,8 +392,9 @@ void draw_polygon(frontend *fe, int *coords, int npoints,
     sfree(pts);
 }
 
-void start_draw(frontend *fe)
+static void win_start_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     HDC hdc_win;
     hdc_win = GetDC(fe->hwnd);
     fe->hdc_bm = CreateCompatibleDC(hdc_win);
@@ -393,8 +404,9 @@ void start_draw(frontend *fe)
     SetMapMode(fe->hdc_bm, MM_TEXT);
 }
 
-void draw_update(frontend *fe, int x, int y, int w, int h)
+static void win_draw_update(void *handle, int x, int y, int w, int h)
 {
+    frontend *fe = (frontend *)handle;
     RECT r;
 
     r.left = x;
@@ -405,8 +417,9 @@ void draw_update(frontend *fe, int x, int y, int w, int h)
     InvalidateRect(fe->hwnd, &r, FALSE);
 }
 
-void end_draw(frontend *fe)
+static void win_end_draw(void *handle)
 {
+    frontend *fe = (frontend *)handle;
     SelectObject(fe->hdc_bm, fe->prevbm);
     DeleteDC(fe->hdc_bm);
     if (fe->clip) {
@@ -415,6 +428,26 @@ void end_draw(frontend *fe)
     }
 }
 
+const struct drawing_api win_drawing = {
+    win_draw_text,
+    win_draw_rect,
+    win_draw_line,
+    win_draw_polygon,
+    win_draw_circle,
+    win_draw_update,
+    win_clip,
+    win_unclip,
+    win_start_draw,
+    win_end_draw,
+    win_status_bar,
+    win_blitter_new,
+    win_blitter_free,
+    win_blitter_save,
+    win_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL,                             /* line_width */
+};
+
 void deactivate_timer(frontend *fe)
 {
     if (fe->hwnd) KillTimer(fe->hwnd, fe->timer);
@@ -429,6 +462,15 @@ void activate_timer(frontend *fe)
     }
 }
 
+/*
+ * Since this front end does not support printing (yet), we need
+ * this stub to satisfy the reference in midend_print_puzzle().
+ */
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2)
+{
+}
+
 void write_clip(HWND hwnd, char *data)
 {
     HGLOBAL clipdata;
@@ -560,7 +602,7 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
 
     fe = snew(frontend);
 
-    fe->me = midend_new(fe, &thegame);
+    fe->me = midend_new(fe, &thegame, &win_drawing, fe);
 
     if (game_id) {
         *error = midend_game_id(fe->me, game_id);