mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Files

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]
1743 lines
48 KiB
C
1743 lines
48 KiB
C
/*
|
|
* cube.c: Cube game.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
#include "puzzles.h"
|
|
|
|
#define MAXVERTICES 20
|
|
#define MAXFACES 20
|
|
#define MAXORDER 4
|
|
struct solid {
|
|
int nvertices;
|
|
float vertices[MAXVERTICES * 3]; /* 3*npoints coordinates */
|
|
int order;
|
|
int nfaces;
|
|
int faces[MAXFACES * MAXORDER]; /* order*nfaces point indices */
|
|
float normals[MAXFACES * 3]; /* 3*npoints vector components */
|
|
float shear; /* isometric shear for nice drawing */
|
|
float border; /* border required around arena */
|
|
};
|
|
|
|
static const struct solid s_tetrahedron = {
|
|
4,
|
|
{
|
|
0.0F, -0.57735026919F, -0.20412414523F,
|
|
-0.5F, 0.28867513459F, -0.20412414523F,
|
|
0.0F, -0.0F, 0.6123724357F,
|
|
0.5F, 0.28867513459F, -0.20412414523F,
|
|
},
|
|
3, 4,
|
|
{
|
|
0,2,1, 3,1,2, 2,0,3, 1,3,0
|
|
},
|
|
{
|
|
-0.816496580928F, -0.471404520791F, 0.333333333334F,
|
|
0.0F, 0.942809041583F, 0.333333333333F,
|
|
0.816496580928F, -0.471404520791F, 0.333333333334F,
|
|
0.0F, 0.0F, -1.0F,
|
|
},
|
|
0.0F, 0.3F
|
|
};
|
|
|
|
static const struct solid s_cube = {
|
|
8,
|
|
{
|
|
-0.5F,-0.5F,-0.5F, -0.5F,-0.5F,+0.5F,
|
|
-0.5F,+0.5F,-0.5F, -0.5F,+0.5F,+0.5F,
|
|
+0.5F,-0.5F,-0.5F, +0.5F,-0.5F,+0.5F,
|
|
+0.5F,+0.5F,-0.5F, +0.5F,+0.5F,+0.5F,
|
|
},
|
|
4, 6,
|
|
{
|
|
0,1,3,2, 1,5,7,3, 5,4,6,7, 4,0,2,6, 0,4,5,1, 3,7,6,2
|
|
},
|
|
{
|
|
-1.0F,0.0F,0.0F, 0.0F,0.0F,+1.0F,
|
|
+1.0F,0.0F,0.0F, 0.0F,0.0F,-1.0F,
|
|
0.0F,-1.0F,0.0F, 0.0F,+1.0F,0.0F
|
|
},
|
|
0.3F, 0.5F
|
|
};
|
|
|
|
static const struct solid s_octahedron = {
|
|
6,
|
|
{
|
|
-0.5F, -0.28867513459472505F, 0.4082482904638664F,
|
|
0.5F, 0.28867513459472505F, -0.4082482904638664F,
|
|
-0.5F, 0.28867513459472505F, -0.4082482904638664F,
|
|
0.5F, -0.28867513459472505F, 0.4082482904638664F,
|
|
0.0F, -0.57735026918945009F, -0.4082482904638664F,
|
|
0.0F, 0.57735026918945009F, 0.4082482904638664F,
|
|
},
|
|
3, 8,
|
|
{
|
|
4,0,2, 0,5,2, 0,4,3, 5,0,3, 1,4,2, 5,1,2, 4,1,3, 1,5,3
|
|
},
|
|
{
|
|
-0.816496580928F, -0.471404520791F, -0.333333333334F,
|
|
-0.816496580928F, 0.471404520791F, 0.333333333334F,
|
|
0.0F, -0.942809041583F, 0.333333333333F,
|
|
0.0F, 0.0F, 1.0F,
|
|
0.0F, 0.0F, -1.0F,
|
|
0.0F, 0.942809041583F, -0.333333333333F,
|
|
0.816496580928F, -0.471404520791F, -0.333333333334F,
|
|
0.816496580928F, 0.471404520791F, 0.333333333334F,
|
|
},
|
|
0.0F, 0.5F
|
|
};
|
|
|
|
static const struct solid s_icosahedron = {
|
|
12,
|
|
{
|
|
0.0F, 0.57735026919F, 0.75576131408F,
|
|
0.0F, -0.93417235896F, 0.17841104489F,
|
|
0.0F, 0.93417235896F, -0.17841104489F,
|
|
0.0F, -0.57735026919F, -0.75576131408F,
|
|
-0.5F, -0.28867513459F, 0.75576131408F,
|
|
-0.5F, 0.28867513459F, -0.75576131408F,
|
|
0.5F, -0.28867513459F, 0.75576131408F,
|
|
0.5F, 0.28867513459F, -0.75576131408F,
|
|
-0.80901699437F, 0.46708617948F, 0.17841104489F,
|
|
0.80901699437F, 0.46708617948F, 0.17841104489F,
|
|
-0.80901699437F, -0.46708617948F, -0.17841104489F,
|
|
0.80901699437F, -0.46708617948F, -0.17841104489F,
|
|
},
|
|
3, 20,
|
|
{
|
|
8,0,2, 0,9,2, 1,10,3, 11,1,3, 0,4,6,
|
|
4,1,6, 5,2,7, 3,5,7, 4,8,10, 8,5,10,
|
|
9,6,11, 7,9,11, 0,8,4, 9,0,6, 10,1,4,
|
|
1,11,6, 8,2,5, 2,9,7, 3,10,5, 11,3,7,
|
|
},
|
|
{
|
|
-0.356822089773F, 0.87267799625F, 0.333333333333F,
|
|
0.356822089773F, 0.87267799625F, 0.333333333333F,
|
|
-0.356822089773F, -0.87267799625F, -0.333333333333F,
|
|
0.356822089773F, -0.87267799625F, -0.333333333333F,
|
|
-0.0F, 0.0F, 1.0F,
|
|
0.0F, -0.666666666667F, 0.745355992501F,
|
|
0.0F, 0.666666666667F, -0.745355992501F,
|
|
0.0F, 0.0F, -1.0F,
|
|
-0.934172358963F, -0.12732200375F, 0.333333333333F,
|
|
-0.934172358963F, 0.12732200375F, -0.333333333333F,
|
|
0.934172358963F, -0.12732200375F, 0.333333333333F,
|
|
0.934172358963F, 0.12732200375F, -0.333333333333F,
|
|
-0.57735026919F, 0.333333333334F, 0.745355992501F,
|
|
0.57735026919F, 0.333333333334F, 0.745355992501F,
|
|
-0.57735026919F, -0.745355992501F, 0.333333333334F,
|
|
0.57735026919F, -0.745355992501F, 0.333333333334F,
|
|
-0.57735026919F, 0.745355992501F, -0.333333333334F,
|
|
0.57735026919F, 0.745355992501F, -0.333333333334F,
|
|
-0.57735026919F, -0.333333333334F, -0.745355992501F,
|
|
0.57735026919F, -0.333333333334F, -0.745355992501F,
|
|
},
|
|
0.0F, 0.8F
|
|
};
|
|
|
|
enum {
|
|
TETRAHEDRON, CUBE, OCTAHEDRON, ICOSAHEDRON
|
|
};
|
|
static const struct solid *solids[] = {
|
|
&s_tetrahedron, &s_cube, &s_octahedron, &s_icosahedron
|
|
};
|
|
|
|
enum {
|
|
COL_BACKGROUND,
|
|
COL_BORDER,
|
|
COL_BLUE,
|
|
NCOLOURS
|
|
};
|
|
|
|
enum { LEFT, RIGHT, UP, DOWN, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT };
|
|
|
|
#define PREFERRED_GRID_SCALE 48
|
|
#define GRID_SCALE (ds->gridscale)
|
|
#define ROLLTIME 0.13F
|
|
|
|
#define SQ(x) ( (x) * (x) )
|
|
|
|
#define MATMUL(ra,m,a) do { \
|
|
float rx, ry, rz, xx = (a)[0], yy = (a)[1], zz = (a)[2], *mat = (m); \
|
|
rx = mat[0] * xx + mat[3] * yy + mat[6] * zz; \
|
|
ry = mat[1] * xx + mat[4] * yy + mat[7] * zz; \
|
|
rz = mat[2] * xx + mat[5] * yy + mat[8] * zz; \
|
|
(ra)[0] = rx; (ra)[1] = ry; (ra)[2] = rz; \
|
|
} while (0)
|
|
|
|
#define APPROXEQ(x,y) ( SQ(x-y) < 0.1 )
|
|
|
|
struct grid_square {
|
|
float x, y;
|
|
int npoints;
|
|
float points[8]; /* maximum */
|
|
int directions[8]; /* bit masks showing point pairs */
|
|
int flip;
|
|
int blue;
|
|
int tetra_class;
|
|
};
|
|
|
|
struct game_params {
|
|
int solid;
|
|
/*
|
|
* Grid dimensions. For a square grid these are width and
|
|
* height respectively; otherwise the grid is a hexagon, with
|
|
* the top side and the two lower diagonals having length d1
|
|
* and the remaining three sides having length d2 (so that
|
|
* d1==d2 gives a regular hexagon, and d2==0 gives a triangle).
|
|
*/
|
|
int d1, d2;
|
|
};
|
|
|
|
struct game_state {
|
|
struct game_params params;
|
|
const struct solid *solid;
|
|
int *facecolours;
|
|
struct grid_square *squares;
|
|
int nsquares;
|
|
int current; /* index of current grid square */
|
|
int sgkey[2]; /* key-point indices into grid sq */
|
|
int dgkey[2]; /* key-point indices into grid sq */
|
|
int spkey[2]; /* key-point indices into polyhedron */
|
|
int dpkey[2]; /* key-point indices into polyhedron */
|
|
int previous;
|
|
float angle;
|
|
int completed;
|
|
int movecount;
|
|
};
|
|
|
|
static game_params *default_params(void)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->solid = CUBE;
|
|
ret->d1 = 4;
|
|
ret->d2 = 4;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int game_fetch_preset(int i, char **name, game_params **params)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
char *str;
|
|
|
|
switch (i) {
|
|
case 0:
|
|
str = "Cube";
|
|
ret->solid = CUBE;
|
|
ret->d1 = 4;
|
|
ret->d2 = 4;
|
|
break;
|
|
case 1:
|
|
str = "Tetrahedron";
|
|
ret->solid = TETRAHEDRON;
|
|
ret->d1 = 1;
|
|
ret->d2 = 2;
|
|
break;
|
|
case 2:
|
|
str = "Octahedron";
|
|
ret->solid = OCTAHEDRON;
|
|
ret->d1 = 2;
|
|
ret->d2 = 2;
|
|
break;
|
|
case 3:
|
|
str = "Icosahedron";
|
|
ret->solid = ICOSAHEDRON;
|
|
ret->d1 = 3;
|
|
ret->d2 = 3;
|
|
break;
|
|
default:
|
|
sfree(ret);
|
|
return FALSE;
|
|
}
|
|
|
|
*name = dupstr(str);
|
|
*params = ret;
|
|
return TRUE;
|
|
}
|
|
|
|
static void free_params(game_params *params)
|
|
{
|
|
sfree(params);
|
|
}
|
|
|
|
static game_params *dup_params(game_params *params)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
*ret = *params; /* structure copy */
|
|
return ret;
|
|
}
|
|
|
|
static void decode_params(game_params *ret, char const *string)
|
|
{
|
|
switch (*string) {
|
|
case 't': ret->solid = TETRAHEDRON; string++; break;
|
|
case 'c': ret->solid = CUBE; string++; break;
|
|
case 'o': ret->solid = OCTAHEDRON; string++; break;
|
|
case 'i': ret->solid = ICOSAHEDRON; string++; break;
|
|
default: break;
|
|
}
|
|
ret->d1 = ret->d2 = atoi(string);
|
|
while (*string && isdigit((unsigned char)*string)) string++;
|
|
if (*string == 'x') {
|
|
string++;
|
|
ret->d2 = atoi(string);
|
|
}
|
|
}
|
|
|
|
static char *encode_params(game_params *params, int full)
|
|
{
|
|
char data[256];
|
|
|
|
assert(params->solid >= 0 && params->solid < 4);
|
|
sprintf(data, "%c%dx%d", "tcoi"[params->solid], params->d1, params->d2);
|
|
|
|
return dupstr(data);
|
|
}
|
|
typedef void (*egc_callback)(void *, struct grid_square *);
|
|
|
|
static void enum_grid_squares(game_params *params, egc_callback callback, void *ctx)
|
|
{
|
|
const struct solid *solid = solids[params->solid];
|
|
|
|
if (solid->order == 4) {
|
|
int x, y;
|
|
|
|
for (y = 0; y < params->d2; y++)
|
|
for (x = 0; x < params->d1; x++) {
|
|
struct grid_square sq;
|
|
|
|
sq.x = (float)x;
|
|
sq.y = (float)y;
|
|
sq.points[0] = x - 0.5F;
|
|
sq.points[1] = y - 0.5F;
|
|
sq.points[2] = x - 0.5F;
|
|
sq.points[3] = y + 0.5F;
|
|
sq.points[4] = x + 0.5F;
|
|
sq.points[5] = y + 0.5F;
|
|
sq.points[6] = x + 0.5F;
|
|
sq.points[7] = y - 0.5F;
|
|
sq.npoints = 4;
|
|
|
|
sq.directions[LEFT] = 0x03; /* 0,1 */
|
|
sq.directions[RIGHT] = 0x0C; /* 2,3 */
|
|
sq.directions[UP] = 0x09; /* 0,3 */
|
|
sq.directions[DOWN] = 0x06; /* 1,2 */
|
|
sq.directions[UP_LEFT] = 0; /* no diagonals in a square */
|
|
sq.directions[UP_RIGHT] = 0; /* no diagonals in a square */
|
|
sq.directions[DOWN_LEFT] = 0; /* no diagonals in a square */
|
|
sq.directions[DOWN_RIGHT] = 0; /* no diagonals in a square */
|
|
|
|
sq.flip = FALSE;
|
|
|
|
/*
|
|
* This is supremely irrelevant, but just to avoid
|
|
* having any uninitialised structure members...
|
|
*/
|
|
sq.tetra_class = 0;
|
|
|
|
callback(ctx, &sq);
|
|
}
|
|
} else {
|
|
int row, rowlen, other, i, firstix = -1;
|
|
float theight = (float)(sqrt(3) / 2.0);
|
|
|
|
for (row = 0; row < params->d1 + params->d2; row++) {
|
|
if (row < params->d2) {
|
|
other = +1;
|
|
rowlen = row + params->d1;
|
|
} else {
|
|
other = -1;
|
|
rowlen = 2*params->d2 + params->d1 - row;
|
|
}
|
|
|
|
/*
|
|
* There are `rowlen' down-pointing triangles.
|
|
*/
|
|
for (i = 0; i < rowlen; i++) {
|
|
struct grid_square sq;
|
|
int ix;
|
|
float x, y;
|
|
|
|
ix = (2 * i - (rowlen-1));
|
|
x = ix * 0.5F;
|
|
y = theight * row;
|
|
sq.x = x;
|
|
sq.y = y + theight / 3;
|
|
sq.points[0] = x - 0.5F;
|
|
sq.points[1] = y;
|
|
sq.points[2] = x;
|
|
sq.points[3] = y + theight;
|
|
sq.points[4] = x + 0.5F;
|
|
sq.points[5] = y;
|
|
sq.npoints = 3;
|
|
|
|
sq.directions[LEFT] = 0x03; /* 0,1 */
|
|
sq.directions[RIGHT] = 0x06; /* 1,2 */
|
|
sq.directions[UP] = 0x05; /* 0,2 */
|
|
sq.directions[DOWN] = 0; /* invalid move */
|
|
|
|
/*
|
|
* Down-pointing triangle: both the up diagonals go
|
|
* up, and the down ones go left and right.
|
|
*/
|
|
sq.directions[UP_LEFT] = sq.directions[UP_RIGHT] =
|
|
sq.directions[UP];
|
|
sq.directions[DOWN_LEFT] = sq.directions[LEFT];
|
|
sq.directions[DOWN_RIGHT] = sq.directions[RIGHT];
|
|
|
|
sq.flip = TRUE;
|
|
|
|
if (firstix < 0)
|
|
firstix = ix & 3;
|
|
ix -= firstix;
|
|
sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3);
|
|
|
|
callback(ctx, &sq);
|
|
}
|
|
|
|
/*
|
|
* There are `rowlen+other' up-pointing triangles.
|
|
*/
|
|
for (i = 0; i < rowlen+other; i++) {
|
|
struct grid_square sq;
|
|
int ix;
|
|
float x, y;
|
|
|
|
ix = (2 * i - (rowlen+other-1));
|
|
x = ix * 0.5F;
|
|
y = theight * row;
|
|
sq.x = x;
|
|
sq.y = y + 2*theight / 3;
|
|
sq.points[0] = x + 0.5F;
|
|
sq.points[1] = y + theight;
|
|
sq.points[2] = x;
|
|
sq.points[3] = y;
|
|
sq.points[4] = x - 0.5F;
|
|
sq.points[5] = y + theight;
|
|
sq.npoints = 3;
|
|
|
|
sq.directions[LEFT] = 0x06; /* 1,2 */
|
|
sq.directions[RIGHT] = 0x03; /* 0,1 */
|
|
sq.directions[DOWN] = 0x05; /* 0,2 */
|
|
sq.directions[UP] = 0; /* invalid move */
|
|
|
|
/*
|
|
* Up-pointing triangle: both the down diagonals go
|
|
* down, and the up ones go left and right.
|
|
*/
|
|
sq.directions[DOWN_LEFT] = sq.directions[DOWN_RIGHT] =
|
|
sq.directions[DOWN];
|
|
sq.directions[UP_LEFT] = sq.directions[LEFT];
|
|
sq.directions[UP_RIGHT] = sq.directions[RIGHT];
|
|
|
|
sq.flip = FALSE;
|
|
|
|
if (firstix < 0)
|
|
firstix = (ix - 1) & 3;
|
|
ix -= firstix;
|
|
sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3);
|
|
|
|
callback(ctx, &sq);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int grid_area(int d1, int d2, int order)
|
|
{
|
|
/*
|
|
* An NxM grid of squares has NM squares in it.
|
|
*
|
|
* A grid of triangles with dimensions A and B has a total of
|
|
* A^2 + B^2 + 4AB triangles in it. (You can divide it up into
|
|
* a side-A triangle containing A^2 subtriangles, a side-B
|
|
* triangle containing B^2, and two congruent parallelograms,
|
|
* each with side lengths A and B, each therefore containing AB
|
|
* two-triangle rhombuses.)
|
|
*/
|
|
if (order == 4)
|
|
return d1 * d2;
|
|
else
|
|
return d1*d1 + d2*d2 + 4*d1*d2;
|
|
}
|
|
|
|
static config_item *game_configure(game_params *params)
|
|
{
|
|
config_item *ret = snewn(4, config_item);
|
|
char buf[80];
|
|
|
|
ret[0].name = "Type of solid";
|
|
ret[0].type = C_CHOICES;
|
|
ret[0].sval = ":Tetrahedron:Cube:Octahedron:Icosahedron";
|
|
ret[0].ival = params->solid;
|
|
|
|
ret[1].name = "Width / top";
|
|
ret[1].type = C_STRING;
|
|
sprintf(buf, "%d", params->d1);
|
|
ret[1].sval = dupstr(buf);
|
|
ret[1].ival = 0;
|
|
|
|
ret[2].name = "Height / bottom";
|
|
ret[2].type = C_STRING;
|
|
sprintf(buf, "%d", params->d2);
|
|
ret[2].sval = dupstr(buf);
|
|
ret[2].ival = 0;
|
|
|
|
ret[3].name = NULL;
|
|
ret[3].type = C_END;
|
|
ret[3].sval = NULL;
|
|
ret[3].ival = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static game_params *custom_params(config_item *cfg)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->solid = cfg[0].ival;
|
|
ret->d1 = atoi(cfg[1].sval);
|
|
ret->d2 = atoi(cfg[2].sval);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void count_grid_square_callback(void *ctx, struct grid_square *sq)
|
|
{
|
|
int *classes = (int *)ctx;
|
|
int thisclass;
|
|
|
|
if (classes[4] == 4)
|
|
thisclass = sq->tetra_class;
|
|
else if (classes[4] == 2)
|
|
thisclass = sq->flip;
|
|
else
|
|
thisclass = 0;
|
|
|
|
classes[thisclass]++;
|
|
}
|
|
|
|
static char *validate_params(game_params *params, int full)
|
|
{
|
|
int classes[5];
|
|
int i;
|
|
|
|
if (params->solid < 0 || params->solid >= lenof(solids))
|
|
return "Unrecognised solid type";
|
|
|
|
if (solids[params->solid]->order == 4) {
|
|
if (params->d1 <= 0 || params->d2 <= 0)
|
|
return "Both grid dimensions must be greater than zero";
|
|
} else {
|
|
if (params->d1 <= 0 && params->d2 <= 0)
|
|
return "At least one grid dimension must be greater than zero";
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
classes[i] = 0;
|
|
if (params->solid == TETRAHEDRON)
|
|
classes[4] = 4;
|
|
else if (params->solid == OCTAHEDRON)
|
|
classes[4] = 2;
|
|
else
|
|
classes[4] = 1;
|
|
enum_grid_squares(params, count_grid_square_callback, classes);
|
|
|
|
for (i = 0; i < classes[4]; i++)
|
|
if (classes[i] < solids[params->solid]->nfaces / classes[4])
|
|
return "Not enough grid space to place all blue faces";
|
|
|
|
if (grid_area(params->d1, params->d2, solids[params->solid]->order) <
|
|
solids[params->solid]->nfaces + 1)
|
|
return "Not enough space to place the solid on an empty square";
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct grid_data {
|
|
int *gridptrs[4];
|
|
int nsquares[4];
|
|
int nclasses;
|
|
int squareindex;
|
|
};
|
|
|
|
static void classify_grid_square_callback(void *ctx, struct grid_square *sq)
|
|
{
|
|
struct grid_data *data = (struct grid_data *)ctx;
|
|
int thisclass;
|
|
|
|
if (data->nclasses == 4)
|
|
thisclass = sq->tetra_class;
|
|
else if (data->nclasses == 2)
|
|
thisclass = sq->flip;
|
|
else
|
|
thisclass = 0;
|
|
|
|
data->gridptrs[thisclass][data->nsquares[thisclass]++] =
|
|
data->squareindex++;
|
|
}
|
|
|
|
static char *new_game_desc(game_params *params, random_state *rs,
|
|
char **aux, int interactive)
|
|
{
|
|
struct grid_data data;
|
|
int i, j, k, m, area, facesperclass;
|
|
int *flags;
|
|
char *desc, *p;
|
|
|
|
/*
|
|
* Enumerate the grid squares, dividing them into equivalence
|
|
* classes as appropriate. (For the tetrahedron, there is one
|
|
* equivalence class for each face; for the octahedron there
|
|
* are two classes; for the other two solids there's only one.)
|
|
*/
|
|
|
|
area = grid_area(params->d1, params->d2, solids[params->solid]->order);
|
|
if (params->solid == TETRAHEDRON)
|
|
data.nclasses = 4;
|
|
else if (params->solid == OCTAHEDRON)
|
|
data.nclasses = 2;
|
|
else
|
|
data.nclasses = 1;
|
|
data.gridptrs[0] = snewn(data.nclasses * area, int);
|
|
for (i = 0; i < data.nclasses; i++) {
|
|
data.gridptrs[i] = data.gridptrs[0] + i * area;
|
|
data.nsquares[i] = 0;
|
|
}
|
|
data.squareindex = 0;
|
|
enum_grid_squares(params, classify_grid_square_callback, &data);
|
|
|
|
facesperclass = solids[params->solid]->nfaces / data.nclasses;
|
|
|
|
for (i = 0; i < data.nclasses; i++)
|
|
assert(data.nsquares[i] >= facesperclass);
|
|
assert(data.squareindex == area);
|
|
|
|
/*
|
|
* So now we know how many faces to allocate in each class. Get
|
|
* on with it.
|
|
*/
|
|
flags = snewn(area, int);
|
|
for (i = 0; i < area; i++)
|
|
flags[i] = FALSE;
|
|
|
|
for (i = 0; i < data.nclasses; i++) {
|
|
for (j = 0; j < facesperclass; j++) {
|
|
int n = random_upto(rs, data.nsquares[i]);
|
|
|
|
assert(!flags[data.gridptrs[i][n]]);
|
|
flags[data.gridptrs[i][n]] = TRUE;
|
|
|
|
/*
|
|
* Move everything else up the array. I ought to use a
|
|
* better data structure for this, but for such small
|
|
* numbers it hardly seems worth the effort.
|
|
*/
|
|
while (n < data.nsquares[i]-1) {
|
|
data.gridptrs[i][n] = data.gridptrs[i][n+1];
|
|
n++;
|
|
}
|
|
data.nsquares[i]--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now we know precisely which squares are blue. Encode this
|
|
* information in hex. While we're looping over this, collect
|
|
* the non-blue squares into a list in the now-unused gridptrs
|
|
* array.
|
|
*/
|
|
desc = snewn(area / 4 + 40, char);
|
|
p = desc;
|
|
j = 0;
|
|
k = 8;
|
|
m = 0;
|
|
for (i = 0; i < area; i++) {
|
|
if (flags[i]) {
|
|
j |= k;
|
|
} else {
|
|
data.gridptrs[0][m++] = i;
|
|
}
|
|
k >>= 1;
|
|
if (!k) {
|
|
*p++ = "0123456789ABCDEF"[j];
|
|
k = 8;
|
|
j = 0;
|
|
}
|
|
}
|
|
if (k != 8)
|
|
*p++ = "0123456789ABCDEF"[j];
|
|
|
|
/*
|
|
* Choose a non-blue square for the polyhedron.
|
|
*/
|
|
sprintf(p, ",%d", data.gridptrs[0][random_upto(rs, m)]);
|
|
|
|
sfree(data.gridptrs[0]);
|
|
sfree(flags);
|
|
|
|
return desc;
|
|
}
|
|
|
|
static void add_grid_square_callback(void *ctx, struct grid_square *sq)
|
|
{
|
|
game_state *state = (game_state *)ctx;
|
|
|
|
state->squares[state->nsquares] = *sq; /* structure copy */
|
|
state->squares[state->nsquares].blue = FALSE;
|
|
state->nsquares++;
|
|
}
|
|
|
|
static int lowest_face(const struct solid *solid)
|
|
{
|
|
int i, j, best;
|
|
float zmin;
|
|
|
|
best = 0;
|
|
zmin = 0.0;
|
|
for (i = 0; i < solid->nfaces; i++) {
|
|
float z = 0;
|
|
|
|
for (j = 0; j < solid->order; j++) {
|
|
int f = solid->faces[i*solid->order + j];
|
|
z += solid->vertices[f*3+2];
|
|
}
|
|
|
|
if (i == 0 || zmin > z) {
|
|
zmin = z;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
static int align_poly(const struct solid *solid, struct grid_square *sq,
|
|
int *pkey)
|
|
{
|
|
float zmin;
|
|
int i, j;
|
|
int flip = (sq->flip ? -1 : +1);
|
|
|
|
/*
|
|
* First, find the lowest z-coordinate present in the solid.
|
|
*/
|
|
zmin = 0.0;
|
|
for (i = 0; i < solid->nvertices; i++)
|
|
if (zmin > solid->vertices[i*3+2])
|
|
zmin = solid->vertices[i*3+2];
|
|
|
|
/*
|
|
* Now go round the grid square. For each point in the grid
|
|
* square, we're looking for a point of the polyhedron with the
|
|
* same x- and y-coordinates (relative to the square's centre),
|
|
* and z-coordinate equal to zmin (near enough).
|
|
*/
|
|
for (j = 0; j < sq->npoints; j++) {
|
|
int matches, index;
|
|
|
|
matches = 0;
|
|
index = -1;
|
|
|
|
for (i = 0; i < solid->nvertices; i++) {
|
|
float dist = 0;
|
|
|
|
dist += SQ(solid->vertices[i*3+0] * flip - sq->points[j*2+0] + sq->x);
|
|
dist += SQ(solid->vertices[i*3+1] * flip - sq->points[j*2+1] + sq->y);
|
|
dist += SQ(solid->vertices[i*3+2] - zmin);
|
|
|
|
if (dist < 0.1) {
|
|
matches++;
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
if (matches != 1 || index < 0)
|
|
return FALSE;
|
|
pkey[j] = index;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void flip_poly(struct solid *solid, int flip)
|
|
{
|
|
int i;
|
|
|
|
if (flip) {
|
|
for (i = 0; i < solid->nvertices; i++) {
|
|
solid->vertices[i*3+0] *= -1;
|
|
solid->vertices[i*3+1] *= -1;
|
|
}
|
|
for (i = 0; i < solid->nfaces; i++) {
|
|
solid->normals[i*3+0] *= -1;
|
|
solid->normals[i*3+1] *= -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct solid *transform_poly(const struct solid *solid, int flip,
|
|
int key0, int key1, float angle)
|
|
{
|
|
struct solid *ret = snew(struct solid);
|
|
float vx, vy, ax, ay;
|
|
float vmatrix[9], amatrix[9], vmatrix2[9];
|
|
int i;
|
|
|
|
*ret = *solid; /* structure copy */
|
|
|
|
flip_poly(ret, flip);
|
|
|
|
/*
|
|
* Now rotate the polyhedron through the given angle. We must
|
|
* rotate about the Z-axis to bring the two vertices key0 and
|
|
* key1 into horizontal alignment, then rotate about the
|
|
* X-axis, then rotate back again.
|
|
*/
|
|
vx = ret->vertices[key1*3+0] - ret->vertices[key0*3+0];
|
|
vy = ret->vertices[key1*3+1] - ret->vertices[key0*3+1];
|
|
assert(APPROXEQ(vx*vx + vy*vy, 1.0));
|
|
|
|
vmatrix[0] = vx; vmatrix[3] = vy; vmatrix[6] = 0;
|
|
vmatrix[1] = -vy; vmatrix[4] = vx; vmatrix[7] = 0;
|
|
vmatrix[2] = 0; vmatrix[5] = 0; vmatrix[8] = 1;
|
|
|
|
ax = (float)cos(angle);
|
|
ay = (float)sin(angle);
|
|
|
|
amatrix[0] = 1; amatrix[3] = 0; amatrix[6] = 0;
|
|
amatrix[1] = 0; amatrix[4] = ax; amatrix[7] = ay;
|
|
amatrix[2] = 0; amatrix[5] = -ay; amatrix[8] = ax;
|
|
|
|
memcpy(vmatrix2, vmatrix, sizeof(vmatrix));
|
|
vmatrix2[1] = vy;
|
|
vmatrix2[3] = -vy;
|
|
|
|
for (i = 0; i < ret->nvertices; i++) {
|
|
MATMUL(ret->vertices + 3*i, vmatrix, ret->vertices + 3*i);
|
|
MATMUL(ret->vertices + 3*i, amatrix, ret->vertices + 3*i);
|
|
MATMUL(ret->vertices + 3*i, vmatrix2, ret->vertices + 3*i);
|
|
}
|
|
for (i = 0; i < ret->nfaces; i++) {
|
|
MATMUL(ret->normals + 3*i, vmatrix, ret->normals + 3*i);
|
|
MATMUL(ret->normals + 3*i, amatrix, ret->normals + 3*i);
|
|
MATMUL(ret->normals + 3*i, vmatrix2, ret->normals + 3*i);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *validate_desc(game_params *params, char *desc)
|
|
{
|
|
int area = grid_area(params->d1, params->d2, solids[params->solid]->order);
|
|
int i, j;
|
|
|
|
i = (area + 3) / 4;
|
|
for (j = 0; j < i; j++) {
|
|
int c = desc[j];
|
|
if (c >= '0' && c <= '9') continue;
|
|
if (c >= 'A' && c <= 'F') continue;
|
|
if (c >= 'a' && c <= 'f') continue;
|
|
return "Not enough hex digits at start of string";
|
|
/* NB if desc[j]=='\0' that will also be caught here, so we're safe */
|
|
}
|
|
|
|
if (desc[i] != ',')
|
|
return "Expected ',' after hex digits";
|
|
|
|
i++;
|
|
do {
|
|
if (desc[i] < '0' || desc[i] > '9')
|
|
return "Expected decimal integer after ','";
|
|
i++;
|
|
} while (desc[i]);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static game_state *new_game(midend *me, game_params *params, char *desc)
|
|
{
|
|
game_state *state = snew(game_state);
|
|
int area;
|
|
|
|
state->params = *params; /* structure copy */
|
|
state->solid = solids[params->solid];
|
|
|
|
area = grid_area(params->d1, params->d2, state->solid->order);
|
|
state->squares = snewn(area, struct grid_square);
|
|
state->nsquares = 0;
|
|
enum_grid_squares(params, add_grid_square_callback, state);
|
|
assert(state->nsquares == area);
|
|
|
|
state->facecolours = snewn(state->solid->nfaces, int);
|
|
memset(state->facecolours, 0, state->solid->nfaces * sizeof(int));
|
|
|
|
/*
|
|
* Set up the blue squares and polyhedron position according to
|
|
* the game description.
|
|
*/
|
|
{
|
|
char *p = desc;
|
|
int i, j, v;
|
|
|
|
j = 8;
|
|
v = 0;
|
|
for (i = 0; i < state->nsquares; i++) {
|
|
if (j == 8) {
|
|
v = *p++;
|
|
if (v >= '0' && v <= '9')
|
|
v -= '0';
|
|
else if (v >= 'A' && v <= 'F')
|
|
v -= 'A' - 10;
|
|
else if (v >= 'a' && v <= 'f')
|
|
v -= 'a' - 10;
|
|
else
|
|
break;
|
|
}
|
|
if (v & j)
|
|
state->squares[i].blue = TRUE;
|
|
j >>= 1;
|
|
if (j == 0)
|
|
j = 8;
|
|
}
|
|
|
|
if (*p == ',')
|
|
p++;
|
|
|
|
state->current = atoi(p);
|
|
if (state->current < 0 || state->current >= state->nsquares)
|
|
state->current = 0; /* got to do _something_ */
|
|
}
|
|
|
|
/*
|
|
* Align the polyhedron with its grid square and determine
|
|
* initial key points.
|
|
*/
|
|
{
|
|
int pkey[4];
|
|
int ret;
|
|
|
|
ret = align_poly(state->solid, &state->squares[state->current], pkey);
|
|
assert(ret);
|
|
|
|
state->dpkey[0] = state->spkey[0] = pkey[0];
|
|
state->dpkey[1] = state->spkey[0] = pkey[1];
|
|
state->dgkey[0] = state->sgkey[0] = 0;
|
|
state->dgkey[1] = state->sgkey[0] = 1;
|
|
}
|
|
|
|
state->previous = state->current;
|
|
state->angle = 0.0;
|
|
state->completed = 0;
|
|
state->movecount = 0;
|
|
|
|
return state;
|
|
}
|
|
|
|
static game_state *dup_game(game_state *state)
|
|
{
|
|
game_state *ret = snew(game_state);
|
|
|
|
ret->params = state->params; /* structure copy */
|
|
ret->solid = state->solid;
|
|
ret->facecolours = snewn(ret->solid->nfaces, int);
|
|
memcpy(ret->facecolours, state->facecolours,
|
|
ret->solid->nfaces * sizeof(int));
|
|
ret->nsquares = state->nsquares;
|
|
ret->current = state->current;
|
|
ret->squares = snewn(ret->nsquares, struct grid_square);
|
|
memcpy(ret->squares, state->squares,
|
|
ret->nsquares * sizeof(struct grid_square));
|
|
ret->dpkey[0] = state->dpkey[0];
|
|
ret->dpkey[1] = state->dpkey[1];
|
|
ret->dgkey[0] = state->dgkey[0];
|
|
ret->dgkey[1] = state->dgkey[1];
|
|
ret->spkey[0] = state->spkey[0];
|
|
ret->spkey[1] = state->spkey[1];
|
|
ret->sgkey[0] = state->sgkey[0];
|
|
ret->sgkey[1] = state->sgkey[1];
|
|
ret->previous = state->previous;
|
|
ret->angle = state->angle;
|
|
ret->completed = state->completed;
|
|
ret->movecount = state->movecount;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_game(game_state *state)
|
|
{
|
|
sfree(state->squares);
|
|
sfree(state->facecolours);
|
|
sfree(state);
|
|
}
|
|
|
|
static char *solve_game(game_state *state, game_state *currstate,
|
|
char *aux, char **error)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static char *game_text_format(game_state *state)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static game_ui *new_ui(game_state *state)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void free_ui(game_ui *ui)
|
|
{
|
|
}
|
|
|
|
static char *encode_ui(game_ui *ui)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void decode_ui(game_ui *ui, char *encoding)
|
|
{
|
|
}
|
|
|
|
static void game_changed_state(game_ui *ui, game_state *oldstate,
|
|
game_state *newstate)
|
|
{
|
|
}
|
|
|
|
struct game_drawstate {
|
|
float gridscale;
|
|
int ox, oy; /* pixel position of float origin */
|
|
};
|
|
|
|
/*
|
|
* Code shared between interpret_move() and execute_move().
|
|
*/
|
|
static int find_move_dest(game_state *from, int direction,
|
|
int *skey, int *dkey)
|
|
{
|
|
int mask, dest, i, j;
|
|
float points[4];
|
|
|
|
/*
|
|
* Find the two points in the current grid square which
|
|
* correspond to this move.
|
|
*/
|
|
mask = from->squares[from->current].directions[direction];
|
|
if (mask == 0)
|
|
return -1;
|
|
for (i = j = 0; i < from->squares[from->current].npoints; i++)
|
|
if (mask & (1 << i)) {
|
|
points[j*2] = from->squares[from->current].points[i*2];
|
|
points[j*2+1] = from->squares[from->current].points[i*2+1];
|
|
skey[j] = i;
|
|
j++;
|
|
}
|
|
assert(j == 2);
|
|
|
|
/*
|
|
* Now find the other grid square which shares those points.
|
|
* This is our move destination.
|
|
*/
|
|
dest = -1;
|
|
for (i = 0; i < from->nsquares; i++)
|
|
if (i != from->current) {
|
|
int match = 0;
|
|
float dist;
|
|
|
|
for (j = 0; j < from->squares[i].npoints; j++) {
|
|
dist = (SQ(from->squares[i].points[j*2] - points[0]) +
|
|
SQ(from->squares[i].points[j*2+1] - points[1]));
|
|
if (dist < 0.1)
|
|
dkey[match++] = j;
|
|
dist = (SQ(from->squares[i].points[j*2] - points[2]) +
|
|
SQ(from->squares[i].points[j*2+1] - points[3]));
|
|
if (dist < 0.1)
|
|
dkey[match++] = j;
|
|
}
|
|
|
|
if (match == 2) {
|
|
dest = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
|
|
int x, int y, int button)
|
|
{
|
|
int direction, mask, i;
|
|
int skey[2], dkey[2];
|
|
|
|
button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
|
|
|
|
/*
|
|
* Moves can be made with the cursor keys or numeric keypad, or
|
|
* alternatively you can left-click and the polyhedron will
|
|
* move in the general direction of the mouse pointer.
|
|
*/
|
|
if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
|
|
direction = UP;
|
|
else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
|
|
direction = DOWN;
|
|
else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
|
|
direction = LEFT;
|
|
else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
|
|
direction = RIGHT;
|
|
else if (button == (MOD_NUM_KEYPAD | '7'))
|
|
direction = UP_LEFT;
|
|
else if (button == (MOD_NUM_KEYPAD | '1'))
|
|
direction = DOWN_LEFT;
|
|
else if (button == (MOD_NUM_KEYPAD | '9'))
|
|
direction = UP_RIGHT;
|
|
else if (button == (MOD_NUM_KEYPAD | '3'))
|
|
direction = DOWN_RIGHT;
|
|
else if (button == LEFT_BUTTON) {
|
|
/*
|
|
* Find the bearing of the click point from the current
|
|
* square's centre.
|
|
*/
|
|
int cx, cy;
|
|
double angle;
|
|
|
|
cx = state->squares[state->current].x * GRID_SCALE + ds->ox;
|
|
cy = state->squares[state->current].y * GRID_SCALE + ds->oy;
|
|
|
|
if (x == cx && y == cy)
|
|
return NULL; /* clicked in exact centre! */
|
|
angle = atan2(y - cy, x - cx);
|
|
|
|
/*
|
|
* There are three possibilities.
|
|
*
|
|
* - This square is a square, so we choose between UP,
|
|
* DOWN, LEFT and RIGHT by dividing the available angle
|
|
* at the 45-degree points.
|
|
*
|
|
* - This square is an up-pointing triangle, so we choose
|
|
* between DOWN, LEFT and RIGHT by dividing into
|
|
* 120-degree arcs.
|
|
*
|
|
* - This square is a down-pointing triangle, so we choose
|
|
* between UP, LEFT and RIGHT in the inverse manner.
|
|
*
|
|
* Don't forget that since our y-coordinates increase
|
|
* downwards, `angle' is measured _clockwise_ from the
|
|
* x-axis, not anticlockwise as most mathematicians would
|
|
* instinctively assume.
|
|
*/
|
|
if (state->squares[state->current].npoints == 4) {
|
|
/* Square. */
|
|
if (fabs(angle) > 3*PI/4)
|
|
direction = LEFT;
|
|
else if (fabs(angle) < PI/4)
|
|
direction = RIGHT;
|
|
else if (angle > 0)
|
|
direction = DOWN;
|
|
else
|
|
direction = UP;
|
|
} else if (state->squares[state->current].directions[UP] == 0) {
|
|
/* Up-pointing triangle. */
|
|
if (angle < -PI/2 || angle > 5*PI/6)
|
|
direction = LEFT;
|
|
else if (angle > PI/6)
|
|
direction = DOWN;
|
|
else
|
|
direction = RIGHT;
|
|
} else {
|
|
/* Down-pointing triangle. */
|
|
assert(state->squares[state->current].directions[DOWN] == 0);
|
|
if (angle > PI/2 || angle < -5*PI/6)
|
|
direction = LEFT;
|
|
else if (angle < -PI/6)
|
|
direction = UP;
|
|
else
|
|
direction = RIGHT;
|
|
}
|
|
} else
|
|
return NULL;
|
|
|
|
mask = state->squares[state->current].directions[direction];
|
|
if (mask == 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* Translate diagonal directions into orthogonal ones.
|
|
*/
|
|
if (direction > DOWN) {
|
|
for (i = LEFT; i <= DOWN; i++)
|
|
if (state->squares[state->current].directions[i] == mask) {
|
|
direction = i;
|
|
break;
|
|
}
|
|
assert(direction <= DOWN);
|
|
}
|
|
|
|
if (find_move_dest(state, direction, skey, dkey) < 0)
|
|
return NULL;
|
|
|
|
if (direction == LEFT) return dupstr("L");
|
|
if (direction == RIGHT) return dupstr("R");
|
|
if (direction == UP) return dupstr("U");
|
|
if (direction == DOWN) return dupstr("D");
|
|
|
|
return NULL; /* should never happen */
|
|
}
|
|
|
|
static game_state *execute_move(game_state *from, char *move)
|
|
{
|
|
game_state *ret;
|
|
float angle;
|
|
struct solid *poly;
|
|
int pkey[2];
|
|
int skey[2], dkey[2];
|
|
int i, j, dest;
|
|
int direction;
|
|
|
|
switch (*move) {
|
|
case 'L': direction = LEFT; break;
|
|
case 'R': direction = RIGHT; break;
|
|
case 'U': direction = UP; break;
|
|
case 'D': direction = DOWN; break;
|
|
default: return NULL;
|
|
}
|
|
|
|
dest = find_move_dest(from, direction, skey, dkey);
|
|
if (dest < 0)
|
|
return NULL;
|
|
|
|
ret = dup_game(from);
|
|
ret->current = dest;
|
|
|
|
/*
|
|
* So we know what grid square we're aiming for, and we also
|
|
* know the two key points (as indices in both the source and
|
|
* destination grid squares) which are invariant between source
|
|
* and destination.
|
|
*
|
|
* Next we must roll the polyhedron on to that square. So we
|
|
* find the indices of the key points within the polyhedron's
|
|
* vertex array, then use those in a call to transform_poly,
|
|
* and align the result on the new grid square.
|
|
*/
|
|
{
|
|
int all_pkey[4];
|
|
align_poly(from->solid, &from->squares[from->current], all_pkey);
|
|
pkey[0] = all_pkey[skey[0]];
|
|
pkey[1] = all_pkey[skey[1]];
|
|
/*
|
|
* Now pkey[0] corresponds to skey[0] and dkey[0], and
|
|
* likewise [1].
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Now find the angle through which to rotate the polyhedron.
|
|
* Do this by finding the two faces that share the two vertices
|
|
* we've found, and taking the dot product of their normals.
|
|
*/
|
|
{
|
|
int f[2], nf = 0;
|
|
float dp;
|
|
|
|
for (i = 0; i < from->solid->nfaces; i++) {
|
|
int match = 0;
|
|
for (j = 0; j < from->solid->order; j++)
|
|
if (from->solid->faces[i*from->solid->order + j] == pkey[0] ||
|
|
from->solid->faces[i*from->solid->order + j] == pkey[1])
|
|
match++;
|
|
if (match == 2) {
|
|
assert(nf < 2);
|
|
f[nf++] = i;
|
|
}
|
|
}
|
|
|
|
assert(nf == 2);
|
|
|
|
dp = 0;
|
|
for (i = 0; i < 3; i++)
|
|
dp += (from->solid->normals[f[0]*3+i] *
|
|
from->solid->normals[f[1]*3+i]);
|
|
angle = (float)acos(dp);
|
|
}
|
|
|
|
/*
|
|
* Now transform the polyhedron. We aren't entirely sure
|
|
* whether we need to rotate through angle or -angle, and the
|
|
* simplest way round this is to try both and see which one
|
|
* aligns successfully!
|
|
*
|
|
* Unfortunately, _both_ will align successfully if this is a
|
|
* cube, which won't tell us anything much. So for that
|
|
* particular case, I resort to gross hackery: I simply negate
|
|
* the angle before trying the alignment, depending on the
|
|
* direction. Which directions work which way is determined by
|
|
* pure trial and error. I said it was gross :-/
|
|
*/
|
|
{
|
|
int all_pkey[4];
|
|
int success;
|
|
|
|
if (from->solid->order == 4 && direction == UP)
|
|
angle = -angle; /* HACK */
|
|
|
|
poly = transform_poly(from->solid,
|
|
from->squares[from->current].flip,
|
|
pkey[0], pkey[1], angle);
|
|
flip_poly(poly, from->squares[ret->current].flip);
|
|
success = align_poly(poly, &from->squares[ret->current], all_pkey);
|
|
|
|
if (!success) {
|
|
sfree(poly);
|
|
angle = -angle;
|
|
poly = transform_poly(from->solid,
|
|
from->squares[from->current].flip,
|
|
pkey[0], pkey[1], angle);
|
|
flip_poly(poly, from->squares[ret->current].flip);
|
|
success = align_poly(poly, &from->squares[ret->current], all_pkey);
|
|
}
|
|
|
|
assert(success);
|
|
}
|
|
|
|
/*
|
|
* Now we have our rotated polyhedron, which we expect to be
|
|
* exactly congruent to the one we started with - but with the
|
|
* faces permuted. So we map that congruence and thereby figure
|
|
* out how to permute the faces as a result of the polyhedron
|
|
* having rolled.
|
|
*/
|
|
{
|
|
int *newcolours = snewn(from->solid->nfaces, int);
|
|
|
|
for (i = 0; i < from->solid->nfaces; i++)
|
|
newcolours[i] = -1;
|
|
|
|
for (i = 0; i < from->solid->nfaces; i++) {
|
|
int nmatch = 0;
|
|
|
|
/*
|
|
* Now go through the transformed polyhedron's faces
|
|
* and figure out which one's normal is approximately
|
|
* equal to this one.
|
|
*/
|
|
for (j = 0; j < poly->nfaces; j++) {
|
|
float dist;
|
|
int k;
|
|
|
|
dist = 0;
|
|
|
|
for (k = 0; k < 3; k++)
|
|
dist += SQ(poly->normals[j*3+k] -
|
|
from->solid->normals[i*3+k]);
|
|
|
|
if (APPROXEQ(dist, 0)) {
|
|
nmatch++;
|
|
newcolours[i] = ret->facecolours[j];
|
|
}
|
|
}
|
|
|
|
assert(nmatch == 1);
|
|
}
|
|
|
|
for (i = 0; i < from->solid->nfaces; i++)
|
|
assert(newcolours[i] != -1);
|
|
|
|
sfree(ret->facecolours);
|
|
ret->facecolours = newcolours;
|
|
}
|
|
|
|
ret->movecount++;
|
|
|
|
/*
|
|
* And finally, swap the colour between the bottom face of the
|
|
* polyhedron and the face we've just landed on.
|
|
*
|
|
* We don't do this if the game is already complete, since we
|
|
* allow the user to roll the fully blue polyhedron around the
|
|
* grid as a feeble reward.
|
|
*/
|
|
if (!ret->completed) {
|
|
i = lowest_face(from->solid);
|
|
j = ret->facecolours[i];
|
|
ret->facecolours[i] = ret->squares[ret->current].blue;
|
|
ret->squares[ret->current].blue = j;
|
|
|
|
/*
|
|
* Detect game completion.
|
|
*/
|
|
j = 0;
|
|
for (i = 0; i < ret->solid->nfaces; i++)
|
|
if (ret->facecolours[i])
|
|
j++;
|
|
if (j == ret->solid->nfaces)
|
|
ret->completed = ret->movecount;
|
|
}
|
|
|
|
sfree(poly);
|
|
|
|
/*
|
|
* Align the normal polyhedron with its grid square, to get key
|
|
* points for non-animated display.
|
|
*/
|
|
{
|
|
int pkey[4];
|
|
int success;
|
|
|
|
success = align_poly(ret->solid, &ret->squares[ret->current], pkey);
|
|
assert(success);
|
|
|
|
ret->dpkey[0] = pkey[0];
|
|
ret->dpkey[1] = pkey[1];
|
|
ret->dgkey[0] = 0;
|
|
ret->dgkey[1] = 1;
|
|
}
|
|
|
|
|
|
ret->spkey[0] = pkey[0];
|
|
ret->spkey[1] = pkey[1];
|
|
ret->sgkey[0] = skey[0];
|
|
ret->sgkey[1] = skey[1];
|
|
ret->previous = from->current;
|
|
ret->angle = angle;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Drawing routines.
|
|
*/
|
|
|
|
struct bbox {
|
|
float l, r, u, d;
|
|
};
|
|
|
|
static void find_bbox_callback(void *ctx, struct grid_square *sq)
|
|
{
|
|
struct bbox *bb = (struct bbox *)ctx;
|
|
int i;
|
|
|
|
for (i = 0; i < sq->npoints; i++) {
|
|
if (bb->l > sq->points[i*2]) bb->l = sq->points[i*2];
|
|
if (bb->r < sq->points[i*2]) bb->r = sq->points[i*2];
|
|
if (bb->u > sq->points[i*2+1]) bb->u = sq->points[i*2+1];
|
|
if (bb->d < sq->points[i*2+1]) bb->d = sq->points[i*2+1];
|
|
}
|
|
}
|
|
|
|
static struct bbox find_bbox(game_params *params)
|
|
{
|
|
struct bbox bb;
|
|
|
|
/*
|
|
* These should be hugely more than the real bounding box will
|
|
* be.
|
|
*/
|
|
bb.l = 2.0F * (params->d1 + params->d2);
|
|
bb.r = -2.0F * (params->d1 + params->d2);
|
|
bb.u = 2.0F * (params->d1 + params->d2);
|
|
bb.d = -2.0F * (params->d1 + params->d2);
|
|
enum_grid_squares(params, find_bbox_callback, &bb);
|
|
|
|
return bb;
|
|
}
|
|
|
|
#define XSIZE(gs, bb, solid) \
|
|
((int)(((bb).r - (bb).l + 2*(solid)->border) * gs))
|
|
#define YSIZE(gs, bb, solid) \
|
|
((int)(((bb).d - (bb).u + 2*(solid)->border) * gs))
|
|
|
|
static void game_compute_size(game_params *params, int tilesize,
|
|
int *x, int *y)
|
|
{
|
|
struct bbox bb = find_bbox(params);
|
|
|
|
*x = XSIZE(tilesize, bb, solids[params->solid]);
|
|
*y = YSIZE(tilesize, bb, solids[params->solid]);
|
|
}
|
|
|
|
static void game_set_size(drawing *dr, game_drawstate *ds,
|
|
game_params *params, int tilesize)
|
|
{
|
|
struct bbox bb = find_bbox(params);
|
|
|
|
ds->gridscale = tilesize;
|
|
ds->ox = (int)(-(bb.l - solids[params->solid]->border) * ds->gridscale);
|
|
ds->oy = (int)(-(bb.u - solids[params->solid]->border) * ds->gridscale);
|
|
}
|
|
|
|
static float *game_colours(frontend *fe, game_state *state, int *ncolours)
|
|
{
|
|
float *ret = snewn(3 * NCOLOURS, float);
|
|
|
|
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
|
|
|
|
ret[COL_BORDER * 3 + 0] = 0.0;
|
|
ret[COL_BORDER * 3 + 1] = 0.0;
|
|
ret[COL_BORDER * 3 + 2] = 0.0;
|
|
|
|
ret[COL_BLUE * 3 + 0] = 0.0;
|
|
ret[COL_BLUE * 3 + 1] = 0.0;
|
|
ret[COL_BLUE * 3 + 2] = 1.0;
|
|
|
|
*ncolours = NCOLOURS;
|
|
return ret;
|
|
}
|
|
|
|
static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
|
|
{
|
|
struct game_drawstate *ds = snew(struct game_drawstate);
|
|
|
|
ds->ox = ds->oy = ds->gridscale = 0.0F;/* not decided yet */
|
|
|
|
return ds;
|
|
}
|
|
|
|
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
|
|
{
|
|
sfree(ds);
|
|
}
|
|
|
|
static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
|
|
game_state *state, int dir, game_ui *ui,
|
|
float animtime, float flashtime)
|
|
{
|
|
int i, j;
|
|
struct bbox bb = find_bbox(&state->params);
|
|
struct solid *poly;
|
|
int *pkey, *gkey;
|
|
float t[3];
|
|
float angle;
|
|
game_state *newstate;
|
|
int square;
|
|
|
|
draw_rect(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
|
|
YSIZE(GRID_SCALE, bb, state->solid), COL_BACKGROUND);
|
|
|
|
if (dir < 0) {
|
|
game_state *t;
|
|
|
|
/*
|
|
* This is an Undo. So reverse the order of the states, and
|
|
* run the roll timer backwards.
|
|
*/
|
|
assert(oldstate);
|
|
|
|
t = oldstate;
|
|
oldstate = state;
|
|
state = t;
|
|
|
|
animtime = ROLLTIME - animtime;
|
|
}
|
|
|
|
if (!oldstate) {
|
|
oldstate = state;
|
|
angle = 0.0;
|
|
square = state->current;
|
|
pkey = state->dpkey;
|
|
gkey = state->dgkey;
|
|
} else {
|
|
angle = state->angle * animtime / ROLLTIME;
|
|
square = state->previous;
|
|
pkey = state->spkey;
|
|
gkey = state->sgkey;
|
|
}
|
|
newstate = state;
|
|
state = oldstate;
|
|
|
|
for (i = 0; i < state->nsquares; i++) {
|
|
int coords[8];
|
|
|
|
for (j = 0; j < state->squares[i].npoints; j++) {
|
|
coords[2*j] = ((int)(state->squares[i].points[2*j] * GRID_SCALE)
|
|
+ ds->ox);
|
|
coords[2*j+1] = ((int)(state->squares[i].points[2*j+1]*GRID_SCALE)
|
|
+ ds->oy);
|
|
}
|
|
|
|
draw_polygon(dr, coords, state->squares[i].npoints,
|
|
state->squares[i].blue ? COL_BLUE : COL_BACKGROUND,
|
|
COL_BORDER);
|
|
}
|
|
|
|
/*
|
|
* Now compute and draw the polyhedron.
|
|
*/
|
|
poly = transform_poly(state->solid, state->squares[square].flip,
|
|
pkey[0], pkey[1], angle);
|
|
|
|
/*
|
|
* Compute the translation required to align the two key points
|
|
* on the polyhedron with the same key points on the current
|
|
* face.
|
|
*/
|
|
for (i = 0; i < 3; i++) {
|
|
float tc = 0.0;
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
float grid_coord;
|
|
|
|
if (i < 2) {
|
|
grid_coord =
|
|
state->squares[square].points[gkey[j]*2+i];
|
|
} else {
|
|
grid_coord = 0.0;
|
|
}
|
|
|
|
tc += (grid_coord - poly->vertices[pkey[j]*3+i]);
|
|
}
|
|
|
|
t[i] = tc / 2;
|
|
}
|
|
for (i = 0; i < poly->nvertices; i++)
|
|
for (j = 0; j < 3; j++)
|
|
poly->vertices[i*3+j] += t[j];
|
|
|
|
/*
|
|
* Now actually draw each face.
|
|
*/
|
|
for (i = 0; i < poly->nfaces; i++) {
|
|
float points[8];
|
|
int coords[8];
|
|
|
|
for (j = 0; j < poly->order; j++) {
|
|
int f = poly->faces[i*poly->order + j];
|
|
points[j*2] = (poly->vertices[f*3+0] -
|
|
poly->vertices[f*3+2] * poly->shear);
|
|
points[j*2+1] = (poly->vertices[f*3+1] -
|
|
poly->vertices[f*3+2] * poly->shear);
|
|
}
|
|
|
|
for (j = 0; j < poly->order; j++) {
|
|
coords[j*2] = (int)floor(points[j*2] * GRID_SCALE) + ds->ox;
|
|
coords[j*2+1] = (int)floor(points[j*2+1] * GRID_SCALE) + ds->oy;
|
|
}
|
|
|
|
/*
|
|
* Find out whether these points are in a clockwise or
|
|
* anticlockwise arrangement. If the latter, discard the
|
|
* face because it's facing away from the viewer.
|
|
*
|
|
* This would involve fiddly winding-number stuff for a
|
|
* general polygon, but for the simple parallelograms we'll
|
|
* be seeing here, all we have to do is check whether the
|
|
* corners turn right or left. So we'll take the vector
|
|
* from point 0 to point 1, turn it right 90 degrees,
|
|
* and check the sign of the dot product with that and the
|
|
* next vector (point 1 to point 2).
|
|
*/
|
|
{
|
|
float v1x = points[2]-points[0];
|
|
float v1y = points[3]-points[1];
|
|
float v2x = points[4]-points[2];
|
|
float v2y = points[5]-points[3];
|
|
float dp = v1x * v2y - v1y * v2x;
|
|
|
|
if (dp <= 0)
|
|
continue;
|
|
}
|
|
|
|
draw_polygon(dr, coords, poly->order,
|
|
state->facecolours[i] ? COL_BLUE : COL_BACKGROUND,
|
|
COL_BORDER);
|
|
}
|
|
sfree(poly);
|
|
|
|
draw_update(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
|
|
YSIZE(GRID_SCALE, bb, state->solid));
|
|
|
|
/*
|
|
* Update the status bar.
|
|
*/
|
|
{
|
|
char statusbuf[256];
|
|
|
|
sprintf(statusbuf, "%sMoves: %d",
|
|
(state->completed ? "COMPLETED! " : ""),
|
|
(state->completed ? state->completed : state->movecount));
|
|
|
|
status_bar(dr, statusbuf);
|
|
}
|
|
}
|
|
|
|
static float game_anim_length(game_state *oldstate,
|
|
game_state *newstate, int dir, game_ui *ui)
|
|
{
|
|
return ROLLTIME;
|
|
}
|
|
|
|
static float game_flash_length(game_state *oldstate,
|
|
game_state *newstate, int dir, game_ui *ui)
|
|
{
|
|
return 0.0F;
|
|
}
|
|
|
|
static int game_wants_statusbar(void)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
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
|
|
|
|
const struct game thegame = {
|
|
"Cube", "games.cube",
|
|
default_params,
|
|
game_fetch_preset,
|
|
decode_params,
|
|
encode_params,
|
|
free_params,
|
|
dup_params,
|
|
TRUE, game_configure, custom_params,
|
|
validate_params,
|
|
new_game_desc,
|
|
validate_desc,
|
|
new_game,
|
|
dup_game,
|
|
free_game,
|
|
FALSE, solve_game,
|
|
FALSE, game_text_format,
|
|
new_ui,
|
|
free_ui,
|
|
encode_ui,
|
|
decode_ui,
|
|
game_changed_state,
|
|
interpret_move,
|
|
execute_move,
|
|
PREFERRED_GRID_SCALE, game_compute_size, game_set_size,
|
|
game_colours,
|
|
game_new_drawstate,
|
|
game_free_drawstate,
|
|
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 */
|
|
};
|