chiark / gitweb /
Implemented text and clipping primitives in the frontend, and added
authorSimon Tatham <anakin@pobox.com>
Thu, 29 Apr 2004 18:10:22 +0000 (18:10 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 29 Apr 2004 18:10:22 +0000 (18:10 +0000)
two new simple games `fifteen' and `sixteen'.

[originally from svn r4173]

.cvsignore
Recipe
cube.c
fifteen.c [new file with mode: 0644]
gtk.c
misc.c [new file with mode: 0644]
puzzles.h
sixteen.c [new file with mode: 0644]
windows.c

index c09cfeaf27cb92ecd1da43fca321984e49f260e1..faa86614cbd689bef43b0fb442c6eb964527f834 100644 (file)
@@ -1,5 +1,5 @@
 Makefile*
-net cube nullgame
+net cube fifteen sixteen nullgame
 *.exe *.obj *.o
 *.map *.rsp
 *notes
diff --git a/Recipe b/Recipe
index 52b6a7685bcbe3a4cc35f5e1ebaee88274da7330..19ca3a568508407942e506ba7415b0c2668e3849 100644 (file)
--- a/Recipe
+++ b/Recipe
 !makefile cygwin Makefile.cyg
 
 WINDOWS  = windows user32.lib gdi32.lib
-COMMON   = midend malloc
+COMMON   = midend misc malloc
 NET      = net random tree234
 
 net      : [X] gtk COMMON NET
 cube     : [X] gtk COMMON cube
+fifteen  : [X] gtk COMMON fifteen
+sixteen  : [X] gtk COMMON sixteen
 
 net      : [G] WINDOWS COMMON NET
 cube     : [G] WINDOWS COMMON cube
+fifteen  : [G] WINDOWS COMMON fifteen
+sixteen  : [G] WINDOWS COMMON sixteen
 
 # The `nullgame' source file is a largely blank one, which contains
 # all the correct function definitions to compile and link, but
diff --git a/cube.c b/cube.c
index 1bc08f62ffec7f19389761a95778ecdd15c31bdf..6fef4d10f95588faf08830809d635e8c7761a3e1 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -511,15 +511,7 @@ char *new_game_seed(game_params *params)
 
     for (i = 0; i < data.nclasses; i++) {
        for (j = 0; j < facesperclass; j++) {
-           unsigned long divisor = RAND_MAX / data.nsquares[i];
-           unsigned long max = divisor * data.nsquares[i];
-           unsigned long n;
-
-           do {
-               n = rand();
-           } while (n >= max);
-
-           n /= divisor;
+            int n = rand_upto(data.nsquares[i]);
 
            assert(!flags[data.gridptrs[i][n]]);
            flags[data.gridptrs[i][n]] = TRUE;
@@ -529,7 +521,7 @@ char *new_game_seed(game_params *params)
             * better data structure for this, but for such small
             * numbers it hardly seems worth the effort.
             */
-           while ((int)n < data.nsquares[i]-1) {
+           while (n < data.nsquares[i]-1) {
                data.gridptrs[i][n] = data.gridptrs[i][n+1];
                n++;
            }
@@ -567,19 +559,7 @@ char *new_game_seed(game_params *params)
     /*
      * Choose a non-blue square for the polyhedron.
      */
-    {
-       unsigned long divisor = RAND_MAX / m;
-       unsigned long max = divisor * m;
-       unsigned long n;
-
-       do {
-           n = rand();
-       } while (n >= max);
-
-       n /= divisor;
-
-       sprintf(p, ":%d", data.gridptrs[0][n]);
-    }
+    sprintf(p, ":%d", rand_upto(m));
 
     sfree(data.gridptrs[0]);
     sfree(flags);
diff --git a/fifteen.c b/fifteen.c
new file mode 100644 (file)
index 0000000..9357d65
--- /dev/null
+++ b/fifteen.c
@@ -0,0 +1,560 @@
+/*
+ * fifteen.c: standard 15-puzzle.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+const char *const game_name = "Fifteen";
+
+#define TILE_SIZE 48
+#define BORDER    (TILE_SIZE / 2)
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define ANIM_TIME 0.1F
+#define FLASH_FRAME 0.1F
+
+#define X(state, i) ( (i) % (state)->w )
+#define Y(state, i) ( (i) / (state)->w )
+#define C(state, x, y) ( (y) * (state)->w + (x) )
+
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+};
+
+struct game_state {
+    int w, h, n;
+    int *tiles;
+    int gap_pos;
+    int completed;
+};
+
+game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 4;
+
+    return ret;
+}
+
+int game_fetch_preset(int i, char **name, game_params **params)
+{
+    return FALSE;
+}
+
+void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+game_params *dup_params(game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+int perm_parity(int *perm, int n)
+{
+    int i, j, ret;
+
+    ret = 0;
+
+    for (i = 0; i < n-1; i++)
+        for (j = i+1; j < n; j++)
+            if (perm[i] > perm[j])
+                ret = !ret;
+
+    return ret;
+}
+
+char *new_game_seed(game_params *params)
+{
+    int gap, n, i, x;
+    int x1, x2, p1, p2, parity;
+    int *tiles, *used;
+    char *ret;
+    int retlen;
+
+    n = params->w * params->h;
+
+    tiles = snewn(n, int);
+    used = snewn(n, int);
+
+    for (i = 0; i < n; i++) {
+        tiles[i] = -1;
+        used[i] = FALSE;
+    }
+
+    gap = rand_upto(n);
+    tiles[gap] = 0;
+    used[0] = TRUE;
+
+    /*
+     * Place everything else except the last two tiles.
+     */
+    for (x = 0, i = n-1; i > 2; i--) {
+        int k = rand_upto(i);
+        int j;
+
+        for (j = 0; j < n; j++)
+            if (!used[j] && (k-- == 0))
+                break;
+
+        assert(j < n && !used[j]);
+        used[j] = TRUE;
+
+        while (tiles[x] >= 0)
+            x++;
+        assert(x < n);
+        tiles[x] = j;
+    }
+
+    /*
+     * Find the last two locations, and the last two pieces.
+     */
+    while (tiles[x] >= 0)
+        x++;
+    assert(x < n);
+    x1 = x;
+    x++;
+    while (tiles[x] >= 0)
+        x++;
+    assert(x < n);
+    x2 = x;
+
+    for (i = 0; i < n; i++)
+        if (!used[i])
+            break;
+    p1 = i;
+    for (i = p1+1; i < n; i++)
+        if (!used[i])
+            break;
+    p2 = i;
+
+    /*
+     * Determine the required parity of the overall permutation.
+     * This is the XOR of:
+     * 
+     *  - The chessboard parity ((x^y)&1) of the gap square. The
+     *    bottom right, and therefore also the top left, count as
+     *    even.
+     * 
+     *  - The parity of n. (The target permutation is 1,...,n-1,0
+     *    rather than 0,...,n-1; this is a cyclic permutation of
+     *    the starting point and hence is odd iff n is even.)
+     */
+    parity = (X(params, gap) ^ Y(params, gap) ^ (n+1)) & 1;
+
+    /*
+     * Try the last two tiles one way round. If that fails, swap
+     * them.
+     */
+    tiles[x1] = p1;
+    tiles[x2] = p2;
+    if (perm_parity(tiles, n) != parity) {
+        tiles[x1] = p2;
+        tiles[x2] = p1;
+        assert(perm_parity(tiles, n) == parity);
+    }
+
+    /*
+     * Now construct the game seed, by describing the tile array as
+     * a simple sequence of comma-separated integers.
+     */
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < n; i++) {
+        char buf[80];
+        int k;
+
+        k = sprintf(buf, "%d,", tiles[i]);
+
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    ret[retlen-1] = '\0';              /* delete last comma */
+
+    sfree(tiles);
+    sfree(used);
+
+    return ret;
+}
+
+game_state *new_game(game_params *params, char *seed)
+{
+    game_state *state = snew(game_state);
+    int i;
+    char *p;
+
+    state->w = params->w;
+    state->h = params->h;
+    state->n = params->w * params->h;
+    state->tiles = snewn(state->n, int);
+
+    state->gap_pos = 0;
+    p = seed;
+    i = 0;
+    for (i = 0; i < state->n; i++) {
+        assert(*p);
+        state->tiles[i] = atoi(p);
+        if (state->tiles[i] == 0)
+            state->gap_pos = i;
+        while (*p && *p != ',')
+            p++;
+        if (*p) p++;                   /* eat comma */
+    }
+    assert(!*p);
+    assert(state->tiles[state->gap_pos] == 0);
+
+    state->completed = FALSE;
+
+    return state;
+}
+
+game_state *dup_game(game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->tiles = snewn(state->w * state->h, int);
+    memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int));
+    ret->gap_pos = state->gap_pos;
+    ret->completed = state->completed;
+
+    return ret;
+}
+
+void free_game(game_state *state)
+{
+    sfree(state);
+}
+
+game_state *make_move(game_state *from, int x, int y, int button)
+{
+    int gx, gy, dx, dy, ux, uy, up, p;
+    game_state *ret;
+
+    gx = X(from, from->gap_pos);
+    gy = Y(from, from->gap_pos);
+
+    if (button == CURSOR_RIGHT && gx > 0)
+        dx = gx - 1, dy = gy;
+    else if (button == CURSOR_LEFT && gx < from->w-1)
+        dx = gx + 1, dy = gy;
+    else if (button == CURSOR_DOWN && gy > 0)
+        dy = gy - 1, dx = gx;
+    else if (button == CURSOR_UP && gy < from->h-1)
+        dy = gy + 1, dx = gx;
+    else if (button == LEFT_BUTTON) {
+        dx = FROMCOORD(x);
+        dy = FROMCOORD(y);
+        if (dx < 0 || dx >= from->w || dy < 0 || dy >= from->h)
+            return NULL;               /* out of bounds */
+        /*
+         * Any click location should be equal to the gap location
+         * in _precisely_ one coordinate.
+         */
+        if ((dx == gx && dy == gy) || (dx != gx && dy != gy))
+            return NULL;
+    } else
+        return NULL;                   /* no move */
+
+    /*
+     * Find the unit displacement from the original gap
+     * position towards this one.
+     */
+    ux = (dx < gx ? -1 : dx > gx ? +1 : 0);
+    uy = (dy < gy ? -1 : dy > gy ? +1 : 0);
+    up = C(from, ux, uy);
+
+    ret = dup_game(from);
+
+    ret->gap_pos = C(from, dx, dy);
+    assert(ret->gap_pos >= 0 && ret->gap_pos < ret->n);
+
+    ret->tiles[ret->gap_pos] = 0;
+
+    for (p = from->gap_pos; p != ret->gap_pos; p += up) {
+        assert(p >= 0 && p < from->n);
+        ret->tiles[p] = from->tiles[p + up];
+    }
+
+    /*
+     * See if the game has been completed.
+     */
+    if (!ret->completed) {
+        ret->completed = TRUE;
+        for (p = 0; p < ret->n; p++)
+            if (ret->tiles[p] != (p < ret->n-1 ? p+1 : 0))
+                ret->completed = FALSE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+struct game_drawstate {
+    int started;
+    int w, h, bgcolour;
+    int *tiles;
+};
+
+void game_size(game_params *params, int *x, int *y)
+{
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+float *game_colours(frontend *fe, game_state *state, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+    float max;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    /*
+     * Drop the background colour so that the highlight is
+     * noticeably brighter than it while still being under 1.
+     */
+    max = ret[COL_BACKGROUND*3];
+    for (i = 1; i < 3; i++)
+        if (ret[COL_BACKGROUND*3+i] > max)
+            max = ret[COL_BACKGROUND*3+i];
+    if (max * 1.2F > 1.0F) {
+        for (i = 0; i < 3; i++)
+            ret[COL_BACKGROUND*3+i] /= (max * 1.2F);
+    }
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_HIGHLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.2F;
+        ret[COL_LOWLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F;
+        ret[COL_TEXT * 3 + i] = 0.0;
+    }
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+game_drawstate *game_new_drawstate(game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->bgcolour = COL_BACKGROUND;
+    ds->tiles = snewn(ds->w*ds->h, int);
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->tiles[i] = -1;
+
+    return ds;
+}
+
+void game_free_drawstate(game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+static void draw_tile(frontend *fe, game_state *state, int x, int y,
+                      int tile, int flash_colour)
+{
+    if (tile == 0) {
+        draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+                  flash_colour);
+    } else {
+        int coords[6];
+        char str[40];
+
+        coords[0] = x + TILE_SIZE - 1;
+        coords[1] = y + TILE_SIZE - 1;
+        coords[2] = x + TILE_SIZE - 1;
+        coords[3] = y;
+        coords[4] = x;
+        coords[5] = y + TILE_SIZE - 1;
+        draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
+
+        coords[0] = x;
+        coords[1] = y;
+        draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
+
+        draw_rect(fe, 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,
+                  FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  COL_TEXT, str);
+    }
+    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+                 game_state *state, float animtime, float flashtime)
+{
+    int i, pass, bgcolour;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[6];
+
+       draw_rect(fe, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(fe, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[4] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[5] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+        draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
+
+        ds->started = TRUE;
+    }
+
+    /*
+     * Now draw each tile. We do this in two passes to make
+     * animation easy.
+     */
+    for (pass = 0; pass < 2; pass++) {
+        for (i = 0; i < state->n; i++) {
+            int t, t0;
+            /*
+             * Figure out what should be displayed at this
+             * location. It's either a simple tile, or it's a
+             * transition between two tiles (in which case we say
+             * -1 because it must always be drawn).
+             */
+
+            if (oldstate && oldstate->tiles[i] != state->tiles[i])
+                t = -1;
+            else
+                t = state->tiles[i];
+
+            t0 = t;
+
+            if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
+                ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1) {
+                int x, y;
+
+                /*
+                 * Figure out what to _actually_ draw, and where to
+                 * draw it.
+                 */
+                if (t == -1) {
+                    int x0, y0, x1, y1;
+                    int j;
+
+                    /*
+                     * On the first pass, just blank the tile.
+                     */
+                    if (pass == 0) {
+                        x = COORD(X(state, i));
+                        y = COORD(Y(state, i));
+                        t = 0;
+                    } else {
+                        float c;
+
+                        t = state->tiles[i];
+
+                        /*
+                         * Don't bother moving the gap; just don't
+                         * draw it.
+                         */
+                        if (t == 0)
+                            continue;
+
+                        /*
+                         * Find the coordinates of this tile in the old and
+                         * new states.
+                         */
+                        x1 = COORD(X(state, i));
+                        y1 = COORD(Y(state, i));
+                        for (j = 0; j < oldstate->n; j++)
+                            if (oldstate->tiles[j] == state->tiles[i])
+                                break;
+                        assert(j < oldstate->n);
+                        x0 = COORD(X(state, j));
+                        y0 = COORD(Y(state, j));
+
+                        c = (animtime / ANIM_TIME);
+                        if (c < 0.0F) c = 0.0F;
+                        if (c > 1.0F) c = 1.0F;
+
+                        x = x0 + (int)(c * (x1 - x0));
+                        y = y0 + (int)(c * (y1 - y0));
+                    }
+
+                } else {
+                    if (pass == 0)
+                        continue;
+                    x = COORD(X(state, i));
+                    y = COORD(Y(state, i));
+                }
+
+                draw_tile(fe, state, x, y, t, bgcolour);
+            }
+            ds->tiles[i] = t0;
+        }
+    }
+    ds->bgcolour = bgcolour;
+}
+
+float game_anim_length(game_state *oldstate, game_state *newstate)
+{
+    return ANIM_TIME;
+}
+
+float game_flash_length(game_state *oldstate, game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
diff --git a/gtk.c b/gtk.c
index 78eb8e82e90522d29733fba7d9e7408e8c72c7ed..778868a614d610d2fd65eb6a350299a331dfb713 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -34,6 +34,12 @@ void fatal(char *fmt, ...)
  * GTK front end to puzzles.
  */
 
+struct font {
+    GdkFont *font;
+    int type;
+    int size;
+};
+
 /*
  * This structure holds all the data relevant to a single window.
  * In principle this would allow us to open multiple independent
@@ -53,6 +59,8 @@ struct frontend {
     GdkGC *gc;
     int bbox_l, bbox_r, bbox_u, bbox_d;
     int timer_active, timer_id;
+    struct font *fonts;
+    int nfonts, fontsize;
 };
 
 void frontend_default_colour(frontend *fe, float *output)
@@ -72,6 +80,85 @@ void start_draw(frontend *fe)
     fe->bbox_d = 0;
 }
 
+void clip(frontend *fe, int x, int y, int w, int h)
+{
+    GdkRectangle rect;
+
+    rect.x = x;
+    rect.y = y;
+    rect.width = w;
+    rect.height = h;
+
+    gdk_gc_set_clip_rectangle(fe->gc, &rect);
+}
+
+void unclip(frontend *fe)
+{
+    GdkRectangle rect;
+
+    rect.x = 0;
+    rect.y = 0;
+    rect.width = fe->w;
+    rect.height = fe->h;
+
+    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)
+{
+    int i;
+
+    /*
+     * Find or create the font.
+     */
+    for (i = 0; i < fe->nfonts; i++)
+        if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
+            break;
+
+    if (i == fe->nfonts) {
+        if (fe->fontsize <= fe->nfonts) {
+            fe->fontsize = fe->nfonts + 10;
+            fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
+        }
+
+        fe->nfonts++;
+
+        fe->fonts[i].type = fonttype;
+        fe->fonts[i].size = fontsize;
+
+        /*
+         * FIXME: Really I should make at least _some_ effort to
+         * pick the correct font.
+         */
+        fe->fonts[i].font = gdk_font_load("variable");
+    }
+
+    /*
+     * Find string dimensions and process alignment.
+     */
+    {
+        int lb, rb, wid, asc, desc;
+
+        gdk_string_extents(fe->fonts[i].font, text,
+                           &lb, &rb, &wid, &asc, &desc);
+        if (align & ALIGN_VCENTRE)
+            y += asc - (asc+desc)/2;
+
+        if (align & ALIGN_HCENTRE)
+            x -= wid / 2;
+        else if (align & ALIGN_HRIGHT)
+            x -= wid;
+
+    }
+
+    /*
+     * Set colour and actually draw text.
+     */
+    gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
+    gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text);
+}
+
 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
 {
     gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
@@ -405,6 +492,8 @@ static frontend *new_window(void)
     gtk_box_pack_end(vbox, fe->area, FALSE, FALSE, 0);
 
     fe->pixmap = NULL;
+    fe->fonts = NULL;
+    fe->nfonts = fe->fontsize = 0;
 
     fe->timer_active = FALSE;
 
diff --git a/misc.c b/misc.c
new file mode 100644 (file)
index 0000000..58c4fb7
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,25 @@
+/*
+ * misc.c: Miscellaneous helpful functions.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "puzzles.h"
+
+int rand_upto(int limit)
+{
+    unsigned long divisor = RAND_MAX / (unsigned)limit;
+    unsigned long max = divisor * (unsigned)limit;
+    unsigned long n;
+
+    assert(limit > 0);
+
+    do {
+        n = rand();
+    } while (n >= max);
+
+    n /= divisor;
+
+    return (int)n;
+}
index 5b756970a5478a0580d0219a657faf0008328d7a..cb276e21f53400731a4ee4bae775e6f7518967f3 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -37,15 +37,29 @@ typedef struct game_params game_params;
 typedef struct game_state game_state;
 typedef struct game_drawstate game_drawstate;
 
+#define ALIGN_VNORMAL 0x000
+#define ALIGN_VCENTRE 0x100
+
+#define ALIGN_HLEFT   0x000
+#define ALIGN_HCENTRE 0x001
+#define ALIGN_HRIGHT  0x002
+
+#define FONT_FIXED    0
+#define FONT_VARIABLE 1
+
 /*
  * Platform routines
  */
 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 fill, 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);
@@ -83,6 +97,11 @@ char *dupstr(char *s);
 #define sresize(array, number, type) \
     ( (type *) srealloc ((array), (number) * sizeof (type)) )
 
+/*
+ * misc.c
+ */
+int rand_upto(int limit);
+
 /*
  * random.c
  */
diff --git a/sixteen.c b/sixteen.c
new file mode 100644 (file)
index 0000000..bc21964
--- /dev/null
+++ b/sixteen.c
@@ -0,0 +1,604 @@
+/*
+ * sixteen.c: `16-puzzle', a sliding-tiles jigsaw which differs
+ * from the 15-puzzle in that you toroidally rotate a row or column
+ * at a time.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+const char *const game_name = "Sixteen";
+
+#define TILE_SIZE 48
+#define BORDER    TILE_SIZE            /* big border to fill with arrows */
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + 2*TILE_SIZE) / TILE_SIZE - 2 )
+
+#define ANIM_TIME 0.1F
+#define FLASH_FRAME 0.1F
+
+#define X(state, i) ( (i) % (state)->w )
+#define Y(state, i) ( (i) / (state)->w )
+#define C(state, x, y) ( (y) * (state)->w + (x) )
+
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+};
+
+struct game_state {
+    int w, h, n;
+    int *tiles;
+    int completed;
+};
+
+game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 4;
+
+    return ret;
+}
+
+int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    int w, h;
+    char buf[80];
+
+    switch (i) {
+      case 0: w = 3, h = 3; break;
+      case 1: w = 4, h = 3; break;
+      case 2: w = 4, h = 4; break;
+      case 3: w = 5, h = 4; break;
+      case 4: w = 5, h = 5; break;
+      default: return FALSE;
+    }
+
+    sprintf(buf, "%dx%d", w, h);
+    *name = dupstr(buf);
+    *params = ret = snew(game_params);
+    ret->w = w;
+    ret->h = h;
+    return TRUE;
+}
+
+void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+game_params *dup_params(game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+int perm_parity(int *perm, int n)
+{
+    int i, j, ret;
+
+    ret = 0;
+
+    for (i = 0; i < n-1; i++)
+        for (j = i+1; j < n; j++)
+            if (perm[i] > perm[j])
+                ret = !ret;
+
+    return ret;
+}
+
+char *new_game_seed(game_params *params)
+{
+    int stop, n, i, x;
+    int x1, x2, p1, p2;
+    int *tiles, *used;
+    char *ret;
+    int retlen;
+
+    n = params->w * params->h;
+
+    tiles = snewn(n, int);
+    used = snewn(n, int);
+
+    for (i = 0; i < n; i++) {
+        tiles[i] = -1;
+        used[i] = FALSE;
+    }
+
+    /*
+     * If both dimensions are odd, there is a parity constraint.
+     */
+    if (params->w & params->h & 1)
+        stop = 2;
+    else
+        stop = 0;
+
+    /*
+     * Place everything except (possibly) the last two tiles.
+     */
+    for (x = 0, i = n; i > stop; i--) {
+        int k = i > 1 ? rand_upto(i) : 0;
+        int j;
+
+        for (j = 0; j < n; j++)
+            if (!used[j] && (k-- == 0))
+                break;
+
+        assert(j < n && !used[j]);
+        used[j] = TRUE;
+
+        while (tiles[x] >= 0)
+            x++;
+        assert(x < n);
+        tiles[x] = j;
+    }
+
+    if (stop) {
+        /*
+         * Find the last two locations, and the last two pieces.
+         */
+        while (tiles[x] >= 0)
+            x++;
+        assert(x < n);
+        x1 = x;
+        x++;
+        while (tiles[x] >= 0)
+            x++;
+        assert(x < n);
+        x2 = x;
+
+        for (i = 0; i < n; i++)
+            if (!used[i])
+                break;
+        p1 = i;
+        for (i = p1+1; i < n; i++)
+            if (!used[i])
+                break;
+        p2 = i;
+
+        /*
+         * Try the last two tiles one way round. If that fails, swap
+         * them.
+         */
+        tiles[x1] = p1;
+        tiles[x2] = p2;
+        if (perm_parity(tiles, n) != 0) {
+            tiles[x1] = p2;
+            tiles[x2] = p1;
+            assert(perm_parity(tiles, n) == 0);
+        }
+    }
+
+    /*
+     * Now construct the game seed, by describing the tile array as
+     * a simple sequence of comma-separated integers.
+     */
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < n; i++) {
+        char buf[80];
+        int k;
+
+        k = sprintf(buf, "%d,", tiles[i]+1);
+
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    ret[retlen-1] = '\0';              /* delete last comma */
+
+    sfree(tiles);
+    sfree(used);
+
+    return ret;
+}
+
+game_state *new_game(game_params *params, char *seed)
+{
+    game_state *state = snew(game_state);
+    int i;
+    char *p;
+
+    state->w = params->w;
+    state->h = params->h;
+    state->n = params->w * params->h;
+    state->tiles = snewn(state->n, int);
+
+    p = seed;
+    i = 0;
+    for (i = 0; i < state->n; i++) {
+        assert(*p);
+        state->tiles[i] = atoi(p);
+        while (*p && *p != ',')
+            p++;
+        if (*p) p++;                   /* eat comma */
+    }
+    assert(!*p);
+
+    state->completed = FALSE;
+
+    return state;
+}
+
+game_state *dup_game(game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->tiles = snewn(state->w * state->h, int);
+    memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int));
+    ret->completed = state->completed;
+
+    return ret;
+}
+
+void free_game(game_state *state)
+{
+    sfree(state);
+}
+
+game_state *make_move(game_state *from, int x, int y, int button)
+{
+    int cx, cy;
+    int dx, dy, tx, ty, n;
+    game_state *ret;
+
+    if (button != LEFT_BUTTON)
+        return NULL;
+
+    cx = FROMCOORD(x);
+    cy = FROMCOORD(y);
+    if (cx == -1 && cy >= 0 && cy < from->h)
+        n = from->w, dx = +1, dy = 0;
+    else if (cx == from->w && cy >= 0 && cy < from->h)
+        n = from->w, dx = -1, dy = 0;
+    else if (cy == -1 && cx >= 0 && cx < from->w)
+        n = from->h, dy = +1, dx = 0;
+    else if (cy == from->h && cx >= 0 && cx < from->w)
+        n = from->h, dy = -1, dx = 0;
+    else
+        return NULL;                   /* invalid click location */
+
+    ret = dup_game(from);
+
+    do {
+        cx += dx;
+        cy += dy;
+        tx = (cx + dx + from->w) % from->w;
+        ty = (cy + dy + from->h) % from->h;
+        ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)];
+    } while (--n > 0);
+
+    /*
+     * See if the game has been completed.
+     */
+    if (!ret->completed) {
+        ret->completed = TRUE;
+        for (n = 0; n < ret->n; n++)
+            if (ret->tiles[n] != n+1)
+                ret->completed = FALSE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+struct game_drawstate {
+    int started;
+    int w, h, bgcolour;
+    int *tiles;
+};
+
+void game_size(game_params *params, int *x, int *y)
+{
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+float *game_colours(frontend *fe, game_state *state, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+    float max;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    /*
+     * Drop the background colour so that the highlight is
+     * noticeably brighter than it while still being under 1.
+     */
+    max = ret[COL_BACKGROUND*3];
+    for (i = 1; i < 3; i++)
+        if (ret[COL_BACKGROUND*3+i] > max)
+            max = ret[COL_BACKGROUND*3+i];
+    if (max * 1.2F > 1.0F) {
+        for (i = 0; i < 3; i++)
+            ret[COL_BACKGROUND*3+i] /= (max * 1.2F);
+    }
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_HIGHLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.2F;
+        ret[COL_LOWLIGHT * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F;
+        ret[COL_TEXT * 3 + i] = 0.0;
+    }
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+game_drawstate *game_new_drawstate(game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->bgcolour = COL_BACKGROUND;
+    ds->tiles = snewn(ds->w*ds->h, int);
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->tiles[i] = -1;
+
+    return ds;
+}
+
+void game_free_drawstate(game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+static void draw_tile(frontend *fe, game_state *state, int x, int y,
+                      int tile, int flash_colour)
+{
+    if (tile == 0) {
+        draw_rect(fe, x, y, TILE_SIZE, TILE_SIZE,
+                  flash_colour);
+    } else {
+        int coords[6];
+        char str[40];
+
+        coords[0] = x + TILE_SIZE - 1;
+        coords[1] = y + TILE_SIZE - 1;
+        coords[2] = x + TILE_SIZE - 1;
+        coords[3] = y;
+        coords[4] = x;
+        coords[5] = y + TILE_SIZE - 1;
+        draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
+
+        coords[0] = x;
+        coords[1] = y;
+        draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
+
+        draw_rect(fe, 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,
+                  FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  COL_TEXT, str);
+    }
+    draw_update(fe, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_arrow(frontend *fe, int x, int y, int xdx, int xdy)
+{
+    int coords[14];
+    int ydy = -xdx, ydx = xdy;
+
+#define POINT(n, xx, yy) ( \
+    coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \
+    coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy)
+
+    POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4);   /* top of arrow */
+    POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2);   /* right corner */
+    POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2);   /* right concave */
+    POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom right */
+    POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom left */
+    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, TRUE, COL_LOWLIGHT);
+    draw_polygon(fe, coords, 7, FALSE, COL_TEXT);
+}
+
+void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+                 game_state *state, float animtime, float flashtime)
+{
+    int i, pass, bgcolour;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[6];
+
+       draw_rect(fe, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(fe, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[4] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[5] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        draw_polygon(fe, coords, 3, TRUE, COL_HIGHLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+        draw_polygon(fe, coords, 3, TRUE, COL_LOWLIGHT);
+        draw_polygon(fe, coords, 3, FALSE, COL_LOWLIGHT);
+
+        /*
+         * Arrows for making moves.
+         */
+        for (i = 0; i < state->w; i++) {
+            draw_arrow(fe, COORD(i), COORD(0), +1, 0);
+            draw_arrow(fe, COORD(i+1), COORD(state->h), -1, 0);
+        }
+        for (i = 0; i < state->h; i++) {
+            draw_arrow(fe, COORD(state->w), COORD(i), 0, +1);
+            draw_arrow(fe, COORD(0), COORD(i+1), 0, -1);
+        }
+
+        ds->started = TRUE;
+    }
+
+    /*
+     * Now draw each tile. We do this in two passes to make
+     * animation easy.
+     */
+
+    clip(fe, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h);
+
+    for (pass = 0; pass < 2; pass++) {
+        for (i = 0; i < state->n; i++) {
+            int t, t0;
+            /*
+             * Figure out what should be displayed at this
+             * location. It's either a simple tile, or it's a
+             * transition between two tiles (in which case we say
+             * -1 because it must always be drawn).
+             */
+
+            if (oldstate && oldstate->tiles[i] != state->tiles[i])
+                t = -1;
+            else
+                t = state->tiles[i];
+
+            t0 = t;
+
+            if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
+                ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1) {
+                int x, y, x2, y2;
+
+                /*
+                 * Figure out what to _actually_ draw, and where to
+                 * draw it.
+                 */
+                if (t == -1) {
+                    int x0, y0, x1, y1, dx, dy;
+                    int j;
+
+                    /*
+                     * On the first pass, just blank the tile.
+                     */
+                    if (pass == 0) {
+                        x = COORD(X(state, i));
+                        y = COORD(Y(state, i));
+                        x2 = y2 = -1;
+                        t = 0;
+                    } else {
+                        float c;
+
+                        t = state->tiles[i];
+
+                        /*
+                         * FIXME: must be prepared to draw a double
+                         * tile in some situations.
+                         */
+
+                        /*
+                         * Find the coordinates of this tile in the old and
+                         * new states.
+                         */
+                        x1 = COORD(X(state, i));
+                        y1 = COORD(Y(state, i));
+                        for (j = 0; j < oldstate->n; j++)
+                            if (oldstate->tiles[j] == state->tiles[i])
+                                break;
+                        assert(j < oldstate->n);
+                        x0 = COORD(X(state, j));
+                        y0 = COORD(Y(state, j));
+
+                        dx = (x1 - x0);
+                        if (abs(dx) > TILE_SIZE) {
+                            dx = (dx < 0 ? dx + TILE_SIZE * state->w :
+                                  dx - TILE_SIZE * state->w);
+                            assert(abs(dx) == TILE_SIZE);
+                        }
+                        dy = (y1 - y0);
+                        if (abs(dy) > TILE_SIZE) {
+                            dy = (dy < 0 ? dy + TILE_SIZE * state->h :
+                                  dy - TILE_SIZE * state->h);
+                            assert(abs(dy) == TILE_SIZE);
+                        }
+
+                        c = (animtime / ANIM_TIME);
+                        if (c < 0.0F) c = 0.0F;
+                        if (c > 1.0F) c = 1.0F;
+
+                        x = x0 + (int)(c * dx);
+                        y = y0 + (int)(c * dy);
+                        x2 = x1 - dx + (int)(c * dx);
+                        y2 = y1 - dy + (int)(c * dy);
+                    }
+
+                } else {
+                    if (pass == 0)
+                        continue;
+                    x = COORD(X(state, i));
+                    y = COORD(Y(state, i));
+                    x2 = y2 = -1;
+                }
+
+                draw_tile(fe, state, x, y, t, bgcolour);
+                if (x2 != -1 || y2 != -1)
+                    draw_tile(fe, state, x2, y2, t, bgcolour);
+            }
+            ds->tiles[i] = t0;
+        }
+    }
+
+    unclip(fe);
+
+    ds->bgcolour = bgcolour;
+}
+
+float game_anim_length(game_state *oldstate, game_state *newstate)
+{
+    return ANIM_TIME;
+}
+
+float game_flash_length(game_state *oldstate, game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
index 4db17f4d7931a12bf5218c2b4be1a3ef05ba7b38..ea5d7a27393c1c4cc1bd5378d89a152d52bf3976 100644 (file)
--- a/windows.c
+++ b/windows.c
@@ -5,6 +5,7 @@
 #include <windows.h>
 
 #include <stdio.h>
+#include <assert.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <time.h>
@@ -63,6 +64,12 @@ void debug_printf(char *fmt, ...)
 
 #endif
 
+struct font {
+    HFONT font;
+    int type;
+    int size;
+};
+
 struct frontend {
     midend_data *me;
     HWND hwnd;
@@ -71,9 +78,12 @@ struct frontend {
     COLORREF *colours;
     HBRUSH *brushes;
     HPEN *pens;
+    HRGN clip;
     UINT timer;
     int npresets;
     game_params **presets;
+    struct font *fonts;
+    int nfonts, fontsize;
 };
 
 void fatal(char *fmt, ...)
@@ -99,6 +109,86 @@ 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)
+{
+    if (!fe->clip) {
+       fe->clip = CreateRectRgn(0, 0, 1, 1);
+       GetClipRgn(fe->hdc_bm, fe->clip);
+    }
+
+    IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
+}
+
+void unclip(frontend *fe)
+{
+    assert(fe->clip);
+    SelectClipRgn(fe->hdc_bm, fe->clip);
+}
+
+void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text)
+{
+    int i;
+
+    /*
+     * Find or create the font.
+     */
+    for (i = 0; i < fe->nfonts; i++)
+        if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
+            break;
+
+    if (i == fe->nfonts) {
+        if (fe->fontsize <= fe->nfonts) {
+            fe->fontsize = fe->nfonts + 10;
+            fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
+        }
+
+        fe->nfonts++;
+
+        fe->fonts[i].type = fonttype;
+        fe->fonts[i].size = fontsize;
+
+        /*
+         * FIXME: Really I should make at least _some_ effort to
+         * pick the correct font.
+         */
+        fe->fonts[i].font = CreateFont(-fontsize, 0, 0, 0, 0,
+                                      FALSE, FALSE, FALSE, DEFAULT_CHARSET,
+                                      OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
+                                      DEFAULT_QUALITY,
+                                      (fonttype == FONT_FIXED ?
+                                       FIXED_PITCH | FF_DONTCARE :
+                                       VARIABLE_PITCH | FF_SWISS),
+                                      NULL);
+    }
+
+    /*
+     * Position and draw the text.
+     */
+    {
+       HFONT oldfont;
+       TEXTMETRIC tm;
+       SIZE size;
+
+       oldfont = SelectObject(fe->hdc_bm, fe->fonts[i].font);
+       if (GetTextMetrics(fe->hdc_bm, &tm)) {
+           if (align & ALIGN_VCENTRE)
+               y -= (tm.tmAscent+tm.tmDescent)/2;
+           else
+               y -= tm.tmAscent;
+       }
+       if (GetTextExtentPoint32(fe->hdc_bm, text, strlen(text), &size)) {
+           if (align & ALIGN_HCENTRE)
+               x -= size.cx / 2;
+           else if (align & ALIGN_HRIGHT)
+               x -= size.cx;
+       }
+       SetBkMode(fe->hdc_bm, TRANSPARENT);
+       TextOut(fe->hdc_bm, x, y, text, strlen(text));
+       SelectObject(fe->hdc_bm, oldfont);
+    }
+}
+
 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
 {
     if (w == 1 && h == 1) {
@@ -161,6 +251,7 @@ void start_draw(frontend *fe)
     fe->hdc_bm = CreateCompatibleDC(hdc_win);
     fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
     ReleaseDC(fe->hwnd, hdc_win);
+    fe->clip = NULL;
 }
 
 void draw_update(frontend *fe, int x, int y, int w, int h)
@@ -179,6 +270,10 @@ void end_draw(frontend *fe)
 {
     SelectObject(fe->hdc_bm, fe->prevbm);
     DeleteDC(fe->hdc_bm);
+    if (fe->clip) {
+       DeleteObject(fe->clip);
+       fe->clip = NULL;
+    }
 }
 
 void deactivate_timer(frontend *fe)