mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 15:41:30 -07:00
Adapt Untangle into a graph editor.
This builds a secondary GUI program sharing most of the Untangle code, similar to galaxieseditor. You can still drag points around as in the actual Untangle game, but also you can right-drag to add or remove an edge between two points. And the 'copy to clipboard' action generates the Untangle game id corresponding to whatever you ended up with. This could be used for hand-designing actual Untangle games, but my more immediate use for it is as a convenient method of constructing test cases for the new graph testing code.
This commit is contained in:
@ -268,6 +268,8 @@ puzzle(untangle
|
|||||||
DISPLAYNAME "Untangle"
|
DISPLAYNAME "Untangle"
|
||||||
DESCRIPTION "Planar graph layout puzzle"
|
DESCRIPTION "Planar graph layout puzzle"
|
||||||
OBJECTIVE "Reposition the points so that the lines do not cross.")
|
OBJECTIVE "Reposition the points so that the lines do not cross.")
|
||||||
|
guiprogram(grapheditor untangle.c
|
||||||
|
COMPILE_DEFINITIONS EDITOR)
|
||||||
|
|
||||||
add_subdirectory(unfinished)
|
add_subdirectory(unfinished)
|
||||||
add_subdirectory(auxiliary)
|
add_subdirectory(auxiliary)
|
||||||
|
355
untangle.c
355
untangle.c
@ -94,9 +94,11 @@ struct game_state {
|
|||||||
game_params params;
|
game_params params;
|
||||||
int w, h; /* extent of coordinate system only */
|
int w, h; /* extent of coordinate system only */
|
||||||
point *pts;
|
point *pts;
|
||||||
int *crosses; /* mark edges which are crossed */
|
|
||||||
struct graph *graph;
|
struct graph *graph;
|
||||||
|
#ifndef EDITOR
|
||||||
|
int *crosses; /* mark edges which are crossed */
|
||||||
bool completed, cheated, just_solved;
|
bool completed, cheated, just_solved;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static int edgecmpC(const void *av, const void *bv)
|
static int edgecmpC(const void *av, const void *bv)
|
||||||
@ -205,13 +207,19 @@ static game_params *custom_params(const config_item *cfg)
|
|||||||
|
|
||||||
static const char *validate_params(const game_params *params, bool full)
|
static const char *validate_params(const game_params *params, bool full)
|
||||||
{
|
{
|
||||||
|
#ifndef EDITOR
|
||||||
if (params->n < 4)
|
if (params->n < 4)
|
||||||
return "Number of points must be at least four";
|
return "Number of points must be at least four";
|
||||||
|
#else
|
||||||
|
if (params->n < 1)
|
||||||
|
return "Number of points must be at least one";
|
||||||
|
#endif
|
||||||
if (params->n > INT_MAX / 3)
|
if (params->n > INT_MAX / 3)
|
||||||
return "Number of points must not be unreasonably large";
|
return "Number of points must not be unreasonably large";
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
/* ----------------------------------------------------------------------
|
/* ----------------------------------------------------------------------
|
||||||
* Small number of 64-bit integer arithmetic operations, to prevent
|
* Small number of 64-bit integer arithmetic operations, to prevent
|
||||||
* integer overflow at the very core of cross().
|
* integer overflow at the very core of cross().
|
||||||
@ -402,6 +410,8 @@ static bool cross(point a1, point a2, point b1, point b2)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif /* EDITOR */
|
||||||
|
|
||||||
static unsigned long squarert(unsigned long n) {
|
static unsigned long squarert(unsigned long n) {
|
||||||
unsigned long d, a, b, di;
|
unsigned long d, a, b, di;
|
||||||
|
|
||||||
@ -443,6 +453,21 @@ static void addedge(tree234 *edges, int a, int b)
|
|||||||
sfree(e);
|
sfree(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
static void deledge(tree234 *edges, int a, int b)
|
||||||
|
{
|
||||||
|
edge e, *found;
|
||||||
|
|
||||||
|
assert(a != b);
|
||||||
|
|
||||||
|
e.a = min(a, b);
|
||||||
|
e.b = max(a, b);
|
||||||
|
|
||||||
|
found = del234(edges, &e);
|
||||||
|
sfree(found);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool isedge(tree234 *edges, int a, int b)
|
static bool isedge(tree234 *edges, int a, int b)
|
||||||
{
|
{
|
||||||
edge e;
|
edge e;
|
||||||
@ -460,6 +485,7 @@ typedef struct vertex {
|
|||||||
int vindex;
|
int vindex;
|
||||||
} vertex;
|
} vertex;
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
static int vertcmpC(const void *av, const void *bv)
|
static int vertcmpC(const void *av, const void *bv)
|
||||||
{
|
{
|
||||||
const vertex *a = (const vertex *)av;
|
const vertex *a = (const vertex *)av;
|
||||||
@ -476,6 +502,7 @@ static int vertcmpC(const void *av, const void *bv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); }
|
static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); }
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Construct point coordinates for n points arranged in a circle,
|
* Construct point coordinates for n points arranged in a circle,
|
||||||
@ -511,9 +538,73 @@ static void make_circle(point *pts, int n, int w)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encode a graph in Untangle's game id: a comma-separated list of
|
||||||
|
* dash-separated vertex number pairs, numbered from zero.
|
||||||
|
*
|
||||||
|
* If params != NULL, then the number of vertices is prefixed to the
|
||||||
|
* front to make a full Untangle game id. Otherwise, we return just
|
||||||
|
* the game description part.
|
||||||
|
*
|
||||||
|
* If mapping != NULL, then it is expected to be a mapping from the
|
||||||
|
* graph's original vertex numbers to output vertex numbers.
|
||||||
|
*/
|
||||||
|
static char *encode_graph(const game_params *params, tree234 *edges,
|
||||||
|
const long *mapping)
|
||||||
|
{
|
||||||
|
const char *sep;
|
||||||
|
char buf[80];
|
||||||
|
int i, k, m, retlen;
|
||||||
|
edge *e, *ea;
|
||||||
|
char *ret;
|
||||||
|
|
||||||
|
retlen = 0;
|
||||||
|
if (params)
|
||||||
|
retlen += sprintf(buf, "%d:", params->n);
|
||||||
|
|
||||||
|
m = count234(edges);
|
||||||
|
ea = snewn(m, edge);
|
||||||
|
for (i = 0; (e = index234(edges, i)) != NULL; i++) {
|
||||||
|
int ma, mb;
|
||||||
|
assert(i < m);
|
||||||
|
if (mapping) {
|
||||||
|
ma = mapping[e->a];
|
||||||
|
mb = mapping[e->b];
|
||||||
|
} else {
|
||||||
|
ma = e->a;
|
||||||
|
mb = e->b;
|
||||||
|
}
|
||||||
|
ea[i].a = min(ma, mb);
|
||||||
|
ea[i].b = max(ma, mb);
|
||||||
|
if (i > 0)
|
||||||
|
retlen++; /* comma separator after the previous edge */
|
||||||
|
retlen += sprintf(buf, "%d-%d", ea[i].a, ea[i].b);
|
||||||
|
}
|
||||||
|
assert(i == m);
|
||||||
|
/* Re-sort to prevent side channels, if mapping was used */
|
||||||
|
qsort(ea, m, sizeof(*ea), edgecmpC);
|
||||||
|
|
||||||
|
ret = snewn(retlen + 1, char);
|
||||||
|
sep = "";
|
||||||
|
k = 0;
|
||||||
|
if (params)
|
||||||
|
k += sprintf(ret + k, "%d:", params->n);
|
||||||
|
|
||||||
|
for (i = 0; i < m; i++) {
|
||||||
|
k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b);
|
||||||
|
sep = ",";
|
||||||
|
}
|
||||||
|
assert(k == retlen);
|
||||||
|
|
||||||
|
sfree(ea);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static char *new_game_desc(const game_params *params, random_state *rs,
|
static char *new_game_desc(const game_params *params, random_state *rs,
|
||||||
char **aux, bool interactive)
|
char **aux, bool interactive)
|
||||||
{
|
{
|
||||||
|
#ifndef EDITOR
|
||||||
int n = params->n, i;
|
int n = params->n, i;
|
||||||
long w, h, j, k, m;
|
long w, h, j, k, m;
|
||||||
point *pts, *pts2;
|
point *pts, *pts2;
|
||||||
@ -672,42 +763,9 @@ static char *new_game_desc(const game_params *params, random_state *rs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We're done. Now encode the graph in a string format. Let's
|
* We're done. Encode the output graph as a string.
|
||||||
* use a comma-separated list of dash-separated vertex number
|
|
||||||
* pairs, numbered from zero. We'll sort the list to prevent
|
|
||||||
* side channels.
|
|
||||||
*/
|
*/
|
||||||
ret = NULL;
|
ret = encode_graph(NULL, edges, tmp);
|
||||||
{
|
|
||||||
const char *sep;
|
|
||||||
char buf[80];
|
|
||||||
int retlen;
|
|
||||||
edge *ea;
|
|
||||||
|
|
||||||
retlen = 0;
|
|
||||||
m = count234(edges);
|
|
||||||
ea = snewn(m, edge);
|
|
||||||
for (i = 0; (e = index234(edges, i)) != NULL; i++) {
|
|
||||||
assert(i < m);
|
|
||||||
ea[i].a = min(tmp[e->a], tmp[e->b]);
|
|
||||||
ea[i].b = max(tmp[e->a], tmp[e->b]);
|
|
||||||
retlen += 1 + sprintf(buf, "%d-%d", ea[i].a, ea[i].b);
|
|
||||||
}
|
|
||||||
assert(i == m);
|
|
||||||
qsort(ea, m, sizeof(*ea), edgecmpC);
|
|
||||||
|
|
||||||
ret = snewn(retlen, char);
|
|
||||||
sep = "";
|
|
||||||
k = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < m; i++) {
|
|
||||||
k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b);
|
|
||||||
sep = ",";
|
|
||||||
}
|
|
||||||
assert(k < retlen);
|
|
||||||
|
|
||||||
sfree(ea);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Encode the solution we started with as an aux_info string.
|
* Encode the solution we started with as an aux_info string.
|
||||||
@ -752,6 +810,9 @@ static char *new_game_desc(const game_params *params, random_state *rs,
|
|||||||
sfree(pts);
|
sfree(pts);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
#else
|
||||||
|
return dupstr("");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *validate_desc(const game_params *params, const char *desc)
|
static const char *validate_desc(const game_params *params, const char *desc)
|
||||||
@ -782,6 +843,7 @@ static const char *validate_desc(const game_params *params, const char *desc)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
static void mark_crossings(game_state *state)
|
static void mark_crossings(game_state *state)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
@ -815,6 +877,7 @@ static void mark_crossings(game_state *state)
|
|||||||
if (ok)
|
if (ok)
|
||||||
state->completed = true;
|
state->completed = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static game_state *new_game(midend *me, const game_params *params,
|
static game_state *new_game(midend *me, const game_params *params,
|
||||||
const char *desc)
|
const char *desc)
|
||||||
@ -830,7 +893,9 @@ static game_state *new_game(midend *me, const game_params *params,
|
|||||||
state->graph = snew(struct graph);
|
state->graph = snew(struct graph);
|
||||||
state->graph->refcount = 1;
|
state->graph->refcount = 1;
|
||||||
state->graph->edges = newtree234(edgecmp);
|
state->graph->edges = newtree234(edgecmp);
|
||||||
|
#ifndef EDITOR
|
||||||
state->completed = state->cheated = state->just_solved = false;
|
state->completed = state->cheated = state->just_solved = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
while (*desc) {
|
while (*desc) {
|
||||||
a = atoi(desc);
|
a = atoi(desc);
|
||||||
@ -848,8 +913,10 @@ static game_state *new_game(midend *me, const game_params *params,
|
|||||||
addedge(state->graph->edges, a, b);
|
addedge(state->graph->edges, a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
state->crosses = snewn(count234(state->graph->edges), int);
|
state->crosses = snewn(count234(state->graph->edges), int);
|
||||||
mark_crossings(state); /* sets up `crosses' and `completed' */
|
mark_crossings(state); /* sets up `crosses' and `completed' */
|
||||||
|
#endif
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -864,6 +931,7 @@ static game_state *dup_game(const game_state *state)
|
|||||||
ret->h = state->h;
|
ret->h = state->h;
|
||||||
ret->pts = snewn(n, point);
|
ret->pts = snewn(n, point);
|
||||||
memcpy(ret->pts, state->pts, n * sizeof(point));
|
memcpy(ret->pts, state->pts, n * sizeof(point));
|
||||||
|
#ifndef EDITOR
|
||||||
ret->graph = state->graph;
|
ret->graph = state->graph;
|
||||||
ret->graph->refcount++;
|
ret->graph->refcount++;
|
||||||
ret->completed = state->completed;
|
ret->completed = state->completed;
|
||||||
@ -872,6 +940,19 @@ static game_state *dup_game(const game_state *state)
|
|||||||
ret->crosses = snewn(count234(ret->graph->edges), int);
|
ret->crosses = snewn(count234(ret->graph->edges), int);
|
||||||
memcpy(ret->crosses, state->crosses,
|
memcpy(ret->crosses, state->crosses,
|
||||||
count234(ret->graph->edges) * sizeof(int));
|
count234(ret->graph->edges) * sizeof(int));
|
||||||
|
#else
|
||||||
|
/* For the graph editor, we must clone the whole graph */
|
||||||
|
ret->graph = snew(struct graph);
|
||||||
|
ret->graph->refcount = 1;
|
||||||
|
ret->graph->edges = newtree234(edgecmp);
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct edge *edge;
|
||||||
|
for (i = 0; (edge = index234(state->graph->edges, i)) != NULL; i++) {
|
||||||
|
addedge(ret->graph->edges, edge->a, edge->b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -889,6 +970,7 @@ static void free_game(game_state *state)
|
|||||||
sfree(state);
|
sfree(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
static char *solve_game(const game_state *state, const game_state *currstate,
|
static char *solve_game(const game_state *state, const game_state *currstate,
|
||||||
const char *aux, const char **error)
|
const char *aux, const char **error)
|
||||||
{
|
{
|
||||||
@ -1034,6 +1116,21 @@ static char *solve_game(const game_state *state, const game_state *currstate,
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
#endif /* EDITOR */
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
static bool game_can_format_as_text_now(const game_params *params)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *game_text_format(const game_state *state)
|
||||||
|
{
|
||||||
|
return encode_graph(&state->params, state->graph->edges, NULL);
|
||||||
|
}
|
||||||
|
#endif /* EDITOR */
|
||||||
|
|
||||||
|
typedef enum DragType { DRAG_MOVE_POINT, DRAG_TOGGLE_EDGE } DragType;
|
||||||
|
|
||||||
struct game_ui {
|
struct game_ui {
|
||||||
/* Invariant: at most one of {dragpoint, cursorpoint} may be valid
|
/* Invariant: at most one of {dragpoint, cursorpoint} may be valid
|
||||||
@ -1048,6 +1145,8 @@ struct game_ui {
|
|||||||
bool just_moved; /* _set_ in game_changed_state */
|
bool just_moved; /* _set_ in game_changed_state */
|
||||||
float anim_length;
|
float anim_length;
|
||||||
|
|
||||||
|
DragType dragtype;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* User preference option to snap dragged points to a coarse-ish
|
* User preference option to snap dragged points to a coarse-ish
|
||||||
* grid. Requested by a user who otherwise found themself spending
|
* grid. Requested by a user who otherwise found themself spending
|
||||||
@ -1196,22 +1295,19 @@ static float normsq(point pt) {
|
|||||||
return (pt.x * pt.x + pt.y * pt.y) / (pt.d * pt.d);
|
return (pt.x * pt.x + pt.y * pt.y) / (pt.d * pt.d);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *interpret_move(const game_state *state, game_ui *ui,
|
/*
|
||||||
const game_drawstate *ds,
|
* Find a vertex within DRAG_THRESHOLD of the pointer, or return -1 if
|
||||||
int x, int y, int button)
|
* no such point exists. In case of more than one, we return the one
|
||||||
|
* _nearest_ to the pointer, so that if two points are very close it's
|
||||||
|
* still possible to drag a specific one of them.
|
||||||
|
*/
|
||||||
|
static int point_under_mouse(const game_state *state,
|
||||||
|
const game_drawstate *ds, int x, int y)
|
||||||
{
|
{
|
||||||
int n = state->params.n;
|
int n = state->params.n;
|
||||||
|
|
||||||
if (IS_MOUSE_DOWN(button)) {
|
|
||||||
int i, best;
|
int i, best;
|
||||||
long bestd;
|
long bestd;
|
||||||
|
|
||||||
/*
|
|
||||||
* Begin drag. We drag the vertex _nearest_ to the pointer,
|
|
||||||
* just in case one is nearly on top of another and we want
|
|
||||||
* to drag the latter. However, we drag nothing at all if
|
|
||||||
* the nearest vertex is outside DRAG_THRESHOLD.
|
|
||||||
*/
|
|
||||||
best = -1;
|
best = -1;
|
||||||
bestd = 0;
|
bestd = 0;
|
||||||
|
|
||||||
@ -1228,17 +1324,40 @@ static char *interpret_move(const game_state *state, game_ui *ui,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) {
|
if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD)
|
||||||
ui->dragpoint = best;
|
return best;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *interpret_move(const game_state *state, game_ui *ui,
|
||||||
|
const game_drawstate *ds,
|
||||||
|
int x, int y, int button)
|
||||||
|
{
|
||||||
|
int n = state->params.n;
|
||||||
|
|
||||||
|
if (IS_MOUSE_DOWN(button)) {
|
||||||
|
int p = point_under_mouse(state, ds, x, y);
|
||||||
|
if (p >= 0) {
|
||||||
|
ui->dragtype = DRAG_MOVE_POINT;
|
||||||
|
#ifdef EDITOR
|
||||||
|
if (button == RIGHT_BUTTON)
|
||||||
|
ui->dragtype = DRAG_TOGGLE_EDGE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ui->dragpoint = p;
|
||||||
ui->cursorpoint = -1; /* eliminate the cursor point, if any */
|
ui->cursorpoint = -1; /* eliminate the cursor point, if any */
|
||||||
|
if (ui->dragtype == DRAG_MOVE_POINT)
|
||||||
place_dragged_point(state, ui, ds, x, y);
|
place_dragged_point(state, ui, ds, x, y);
|
||||||
return MOVE_UI_UPDATE;
|
return MOVE_UI_UPDATE;
|
||||||
}
|
}
|
||||||
return MOVE_NO_EFFECT;
|
return MOVE_NO_EFFECT;
|
||||||
} else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) {
|
} else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0 &&
|
||||||
|
ui->dragtype == DRAG_MOVE_POINT) {
|
||||||
place_dragged_point(state, ui, ds, x, y);
|
place_dragged_point(state, ui, ds, x, y);
|
||||||
return MOVE_UI_UPDATE;
|
return MOVE_UI_UPDATE;
|
||||||
} else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) {
|
} else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0 &&
|
||||||
|
ui->dragtype == DRAG_MOVE_POINT) {
|
||||||
int p = ui->dragpoint;
|
int p = ui->dragpoint;
|
||||||
char buf[80];
|
char buf[80];
|
||||||
|
|
||||||
@ -1263,7 +1382,32 @@ static char *interpret_move(const game_state *state, game_ui *ui,
|
|||||||
ui->newpoint.x, ui->newpoint.y, ui->newpoint.d);
|
ui->newpoint.x, ui->newpoint.y, ui->newpoint.d);
|
||||||
ui->just_dragged = true;
|
ui->just_dragged = true;
|
||||||
return dupstr(buf);
|
return dupstr(buf);
|
||||||
} else if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
|
#ifdef EDITOR
|
||||||
|
} else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0 &&
|
||||||
|
ui->dragtype == DRAG_TOGGLE_EDGE) {
|
||||||
|
ui->cursorpoint = point_under_mouse(state, ds, x, y);
|
||||||
|
return MOVE_UI_UPDATE;
|
||||||
|
} else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0 &&
|
||||||
|
ui->dragtype == DRAG_TOGGLE_EDGE) {
|
||||||
|
int p = ui->dragpoint;
|
||||||
|
int q = point_under_mouse(state, ds, x, y);
|
||||||
|
char buf[80];
|
||||||
|
|
||||||
|
ui->dragpoint = -1; /* terminate drag, no matter what */
|
||||||
|
ui->cursorpoint = -1; /* also eliminate the cursor point */
|
||||||
|
|
||||||
|
if (q < 0 || p == q)
|
||||||
|
return MOVE_UI_UPDATE;
|
||||||
|
|
||||||
|
sprintf(buf, "E%c%d,%d",
|
||||||
|
isedge(state->graph->edges, p, q) ? 'D' : 'A',
|
||||||
|
p, q);
|
||||||
|
return dupstr(buf);
|
||||||
|
#endif /* EDITOR */
|
||||||
|
} else if (IS_MOUSE_DRAG(button)) {
|
||||||
|
return MOVE_NO_EFFECT;
|
||||||
|
} else if (IS_MOUSE_RELEASE(button)) {
|
||||||
|
assert(ui->dragpoint == -1);
|
||||||
return MOVE_NO_EFFECT;
|
return MOVE_NO_EFFECT;
|
||||||
}
|
}
|
||||||
else if(IS_CURSOR_MOVE(button)) {
|
else if(IS_CURSOR_MOVE(button)) {
|
||||||
@ -1455,14 +1599,59 @@ static game_state *execute_move(const game_state *state, const char *move)
|
|||||||
long x, y, d;
|
long x, y, d;
|
||||||
game_state *ret = dup_game(state);
|
game_state *ret = dup_game(state);
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
ret->just_solved = false;
|
ret->just_solved = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
if (*move == 'E') {
|
||||||
|
bool add;
|
||||||
|
int a, b;
|
||||||
|
|
||||||
|
move++;
|
||||||
|
if (*move == 'A')
|
||||||
|
add = true;
|
||||||
|
else if (*move == 'D')
|
||||||
|
add = false;
|
||||||
|
else {
|
||||||
|
free_game(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
move++;
|
||||||
|
a = atoi(move);
|
||||||
|
while (*move && isdigit((unsigned char)*move))
|
||||||
|
move++;
|
||||||
|
|
||||||
|
if (*move != ',') {
|
||||||
|
free_game(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
move++;
|
||||||
|
|
||||||
|
b = atoi(move);
|
||||||
|
|
||||||
|
if (a >= 0 && a < n && b >= 0 && b < n && a != b) {
|
||||||
|
if (add)
|
||||||
|
addedge(ret->graph->edges, a, b);
|
||||||
|
else
|
||||||
|
deledge(ret->graph->edges, a, b);
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
free_game(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
while (*move) {
|
while (*move) {
|
||||||
|
#ifndef EDITOR
|
||||||
if (*move == 'S') {
|
if (*move == 'S') {
|
||||||
move++;
|
move++;
|
||||||
if (*move == ';') move++;
|
if (*move == ';') move++;
|
||||||
ret->cheated = ret->just_solved = true;
|
ret->cheated = ret->just_solved = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (*move == 'P' &&
|
if (*move == 'P' &&
|
||||||
sscanf(move+1, "%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k) == 4 &&
|
sscanf(move+1, "%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k) == 4 &&
|
||||||
p >= 0 && p < n && d > 0) {
|
p >= 0 && p < n && d > 0) {
|
||||||
@ -1478,7 +1667,9 @@ static game_state *execute_move(const game_state *state, const char *move)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EDITOR
|
||||||
mark_crossings(ret);
|
mark_crossings(ret);
|
||||||
|
#endif
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1598,6 +1789,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
|
|||||||
int i, j;
|
int i, j;
|
||||||
int bg;
|
int bg;
|
||||||
bool points_moved;
|
bool points_moved;
|
||||||
|
#ifdef EDITOR
|
||||||
|
bool edges_changed;
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* There's no terribly sensible way to do partial redraws of
|
* There's no terribly sensible way to do partial redraws of
|
||||||
@ -1627,7 +1821,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
|
|||||||
point p = state->pts[i];
|
point p = state->pts[i];
|
||||||
long x, y;
|
long x, y;
|
||||||
|
|
||||||
if (ui->dragpoint == i)
|
if (ui->dragpoint == i && ui->dragtype == DRAG_MOVE_POINT)
|
||||||
p = ui->newpoint;
|
p = ui->newpoint;
|
||||||
|
|
||||||
if (oldstate)
|
if (oldstate)
|
||||||
@ -1643,9 +1837,33 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
|
|||||||
ds->y[i] = y;
|
ds->y[i] = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
edges_changed = false;
|
||||||
|
if (oldstate) {
|
||||||
|
for (i = 0;; i++) {
|
||||||
|
edge *eold = index234(oldstate->graph->edges, i);
|
||||||
|
edge *enew = index234(state->graph->edges, i);
|
||||||
|
if (!eold && !enew)
|
||||||
|
break;
|
||||||
|
if (!eold || !enew) {
|
||||||
|
edges_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (eold->a != enew->a || eold->b != enew->b) {
|
||||||
|
edges_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (ds->bg == bg &&
|
if (ds->bg == bg &&
|
||||||
ds->dragpoint == ui->dragpoint &&
|
ds->dragpoint == ui->dragpoint &&
|
||||||
ds->cursorpoint == ui->cursorpoint && !points_moved)
|
ds->cursorpoint == ui->cursorpoint &&
|
||||||
|
#ifdef EDITOR
|
||||||
|
!edges_changed &&
|
||||||
|
#endif
|
||||||
|
!points_moved)
|
||||||
return; /* nothing to do */
|
return; /* nothing to do */
|
||||||
|
|
||||||
ds->dragpoint = ui->dragpoint;
|
ds->dragpoint = ui->dragpoint;
|
||||||
@ -1660,10 +1878,16 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
|
for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
|
||||||
draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
|
#ifndef EDITOR
|
||||||
ui->show_crossed_edges &&
|
int colour = ui->show_crossed_edges &&
|
||||||
(oldstate?oldstate:state)->crosses[i] ?
|
(oldstate?oldstate:state)->crosses[i] ?
|
||||||
COL_CROSSEDLINE : COL_LINE);
|
COL_CROSSEDLINE : COL_LINE;
|
||||||
|
#else
|
||||||
|
int colour = COL_LINE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
|
||||||
|
colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1721,19 +1945,25 @@ static float game_anim_length(const game_state *oldstate,
|
|||||||
{
|
{
|
||||||
if (ui->just_moved)
|
if (ui->just_moved)
|
||||||
return 0.0F;
|
return 0.0F;
|
||||||
|
#ifndef EDITOR
|
||||||
if ((dir < 0 ? oldstate : newstate)->just_solved)
|
if ((dir < 0 ? oldstate : newstate)->just_solved)
|
||||||
ui->anim_length = SOLVEANIM_TIME;
|
ui->anim_length = SOLVEANIM_TIME;
|
||||||
else
|
else
|
||||||
ui->anim_length = ANIM_TIME;
|
ui->anim_length = ANIM_TIME;
|
||||||
|
#else
|
||||||
|
ui->anim_length = ANIM_TIME;
|
||||||
|
#endif
|
||||||
return ui->anim_length;
|
return ui->anim_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float game_flash_length(const game_state *oldstate,
|
static float game_flash_length(const game_state *oldstate,
|
||||||
const game_state *newstate, int dir, game_ui *ui)
|
const game_state *newstate, int dir, game_ui *ui)
|
||||||
{
|
{
|
||||||
|
#ifndef EDITOR
|
||||||
if (!oldstate->completed && newstate->completed &&
|
if (!oldstate->completed && newstate->completed &&
|
||||||
!oldstate->cheated && !newstate->cheated)
|
!oldstate->cheated && !newstate->cheated)
|
||||||
return FLASH_TIME;
|
return FLASH_TIME;
|
||||||
|
#endif
|
||||||
return 0.0F;
|
return 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1744,7 +1974,7 @@ static void game_get_cursor_location(const game_ui *ui,
|
|||||||
int *x, int *y, int *w, int *h)
|
int *x, int *y, int *w, int *h)
|
||||||
{
|
{
|
||||||
point pt;
|
point pt;
|
||||||
if(ui->dragpoint >= 0)
|
if (ui->dragpoint >= 0 && ui->dragtype == DRAG_MOVE_POINT)
|
||||||
pt = ui->newpoint;
|
pt = ui->newpoint;
|
||||||
else if(ui->cursorpoint >= 0)
|
else if(ui->cursorpoint >= 0)
|
||||||
pt = state->pts[ui->cursorpoint];
|
pt = state->pts[ui->cursorpoint];
|
||||||
@ -1761,7 +1991,11 @@ static void game_get_cursor_location(const game_ui *ui,
|
|||||||
|
|
||||||
static int game_status(const game_state *state)
|
static int game_status(const game_state *state)
|
||||||
{
|
{
|
||||||
|
#ifdef EDITOR
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
return state->completed ? +1 : 0;
|
return state->completed ? +1 : 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COMBINED
|
#ifdef COMBINED
|
||||||
@ -1783,8 +2017,13 @@ const struct game thegame = {
|
|||||||
new_game,
|
new_game,
|
||||||
dup_game,
|
dup_game,
|
||||||
free_game,
|
free_game,
|
||||||
|
#ifndef EDITOR
|
||||||
true, solve_game,
|
true, solve_game,
|
||||||
false, NULL, NULL, /* can_format_as_text_now, text_format */
|
false, NULL, NULL, /* can_format_as_text_now, text_format */
|
||||||
|
#else
|
||||||
|
false, NULL,
|
||||||
|
true, game_can_format_as_text_now, game_text_format,
|
||||||
|
#endif
|
||||||
get_prefs, set_prefs,
|
get_prefs, set_prefs,
|
||||||
new_ui,
|
new_ui,
|
||||||
free_ui,
|
free_ui,
|
||||||
|
Reference in New Issue
Block a user