New puzzle: `Tents'. Requires a potentially shared algorithms module

maxflow.c. Also in this checkin, fixes to the OS X and GTK back ends
to get ALIGN_VNORMAL right. This is the first time I've used it! :-)

[originally from svn r6390]
This commit is contained in:
Simon Tatham
2005-10-13 18:30:24 +00:00
parent 29afca3ef9
commit 669bb81f08
8 changed files with 2664 additions and 2 deletions

9
Recipe
View File

@ -26,10 +26,11 @@ SLANT = slant dsf
MAP = map dsf MAP = map dsf
LOOPY = loopy tree234 dsf LOOPY = loopy tree234 dsf
LIGHTUP = lightup combi LIGHTUP = lightup combi
TENTS = tents maxflow
ALL = list NET NETSLIDE cube fifteen sixteen rect pattern solo twiddle ALL = list NET NETSLIDE cube fifteen sixteen rect pattern solo twiddle
+ MINES samegame FLIP guess PEGS dominosa UNTANGLE blackbox SLANT + MINES samegame FLIP guess PEGS dominosa UNTANGLE blackbox SLANT
+ LIGHTUP MAP LOOPY inertia + LIGHTUP MAP LOOPY inertia TENTS
GTK = gtk printing ps GTK = gtk printing ps
@ -55,6 +56,7 @@ lightup : [X] GTK COMMON LIGHTUP
map : [X] GTK COMMON MAP map : [X] GTK COMMON MAP
loopy : [X] GTK COMMON LOOPY loopy : [X] GTK COMMON LOOPY
inertia : [X] GTK COMMON inertia inertia : [X] GTK COMMON inertia
tents : [X] GTK COMMON TENTS
# Auxiliary command-line programs. # Auxiliary command-line programs.
STANDALONE = nullfe random misc malloc STANDALONE = nullfe random misc malloc
@ -65,6 +67,7 @@ mineobfusc : [U] mines[STANDALONE_OBFUSCATOR] tree234 STANDALONE
slantsolver : [U] slant[STANDALONE_SOLVER] dsf STANDALONE slantsolver : [U] slant[STANDALONE_SOLVER] dsf STANDALONE
mapsolver : [U] map[STANDALONE_SOLVER] dsf STANDALONE m.lib mapsolver : [U] map[STANDALONE_SOLVER] dsf STANDALONE m.lib
lightupsolver : [U] lightup[STANDALONE_SOLVER] combi STANDALONE lightupsolver : [U] lightup[STANDALONE_SOLVER] combi STANDALONE
tentssolver : [U] tents[STANDALONE_SOLVER] maxflow STANDALONE
solosolver : [C] solo[STANDALONE_SOLVER] STANDALONE solosolver : [C] solo[STANDALONE_SOLVER] STANDALONE
patternsolver : [C] pattern[STANDALONE_SOLVER] STANDALONE patternsolver : [C] pattern[STANDALONE_SOLVER] STANDALONE
@ -72,6 +75,7 @@ mineobfusc : [C] mines[STANDALONE_OBFUSCATOR] tree234 STANDALONE
slantsolver : [C] slant[STANDALONE_SOLVER] dsf STANDALONE slantsolver : [C] slant[STANDALONE_SOLVER] dsf STANDALONE
mapsolver : [C] map[STANDALONE_SOLVER] dsf STANDALONE mapsolver : [C] map[STANDALONE_SOLVER] dsf STANDALONE
lightupsolver : [C] lightup[STANDALONE_SOLVER] combi STANDALONE lightupsolver : [C] lightup[STANDALONE_SOLVER] combi STANDALONE
tentssolver : [C] tents[STANDALONE_SOLVER] maxflow STANDALONE
# The Windows Net shouldn't be called `net.exe' since Windows # The Windows Net shouldn't be called `net.exe' since Windows
# already has a reasonably important utility program by that name! # already has a reasonably important utility program by that name!
@ -97,6 +101,7 @@ lightup : [G] WINDOWS COMMON LIGHTUP
map : [G] WINDOWS COMMON MAP map : [G] WINDOWS COMMON MAP
loopy : [G] WINDOWS COMMON LOOPY loopy : [G] WINDOWS COMMON LOOPY
inertia : [G] WINDOWS COMMON inertia inertia : [G] WINDOWS COMMON inertia
tents : [G] WINDOWS COMMON TENTS
# Mac OS X unified application containing all the puzzles. # Mac OS X unified application containing all the puzzles.
Puzzles : [MX] osx osx.icns osx-info.plist COMMON ALL Puzzles : [MX] osx osx.icns osx-info.plist COMMON ALL
@ -189,7 +194,7 @@ install:
for i in cube net netslide fifteen sixteen twiddle \ for i in cube net netslide fifteen sixteen twiddle \
pattern rect solo mines samegame flip guess \ pattern rect solo mines samegame flip guess \
pegs dominosa untangle blackbox slant lightup \ pegs dominosa untangle blackbox slant lightup \
map loopy inertia; do \ map loopy inertia tents; do \
$(INSTALL_PROGRAM) -m 755 $$i $(DESTDIR)$(gamesdir)/$$i \ $(INSTALL_PROGRAM) -m 755 $$i $(DESTDIR)$(gamesdir)/$$i \
|| exit 1; \ || exit 1; \
done done

4
gtk.c
View File

@ -290,6 +290,8 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
if (align & ALIGN_VCENTRE) if (align & ALIGN_VCENTRE)
rect.y -= rect.height / 2; rect.y -= rect.height / 2;
else
rect.y -= rect.height;
if (align & ALIGN_HCENTRE) if (align & ALIGN_HCENTRE)
rect.x -= rect.width / 2; rect.x -= rect.width / 2;
@ -317,6 +319,8 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
&lb, &rb, &wid, &asc, &desc); &lb, &rb, &wid, &asc, &desc);
if (align & ALIGN_VCENTRE) if (align & ALIGN_VCENTRE)
y += asc - (asc+desc)/2; y += asc - (asc+desc)/2;
else
y += asc;
/* /*
* ... but horizontal extents with respect to the provided * ... but horizontal extents with respect to the provided

2
list.c
View File

@ -37,6 +37,7 @@ extern const game samegame;
extern const game sixteen; extern const game sixteen;
extern const game slant; extern const game slant;
extern const game solo; extern const game solo;
extern const game tents;
extern const game twiddle; extern const game twiddle;
extern const game untangle; extern const game untangle;
@ -61,6 +62,7 @@ const game *gamelist[] = {
&sixteen, &sixteen,
&slant, &slant,
&solo, &solo,
&tents,
&twiddle, &twiddle,
&untangle, &untangle,
}; };

461
maxflow.c Normal file
View File

@ -0,0 +1,461 @@
/*
* Edmonds-Karp algorithm for finding a maximum flow and minimum
* cut in a network. Almost identical to the Ford-Fulkerson
* algorithm, but apparently using breadth-first search to find the
* _shortest_ augmenting path is a good way to guarantee
* termination and ensure the time complexity is not dependent on
* the actual value of the maximum flow. I don't understand why
* that should be, but it's claimed on the Internet that it's been
* proved, and that's good enough for me. I prefer BFS to DFS
* anyway :-)
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "maxflow.h"
#include "puzzles.h" /* for snewn/sfree */
int maxflow_with_scratch(void *scratch, int nv, int source, int sink,
int ne, const int *edges, const int *backedges,
const int *capacity, int *flow, int *cut)
{
int *todo = (int *)scratch;
int *prev = todo + nv;
int *firstedge = todo + 2*nv;
int *firstbackedge = todo + 3*nv;
int i, j, head, tail, from, to;
int totalflow;
/*
* Scan the edges array to find the index of the first edge
* from each node.
*/
j = 0;
for (i = 0; i < ne; i++)
while (j <= edges[2*i])
firstedge[j++] = i;
while (j < nv)
firstedge[j++] = ne;
assert(j == nv);
/*
* Scan the backedges array to find the index of the first edge
* _to_ each node.
*/
j = 0;
for (i = 0; i < ne; i++)
while (j <= edges[2*backedges[i]+1])
firstbackedge[j++] = i;
while (j < nv)
firstbackedge[j++] = ne;
assert(j == nv);
/*
* Start the flow off at zero on every edge.
*/
for (i = 0; i < ne; i++)
flow[i] = 0;
totalflow = 0;
/*
* Repeatedly look for an augmenting path, and follow it.
*/
while (1) {
/*
* Set up the prev array.
*/
for (i = 0; i < nv; i++)
prev[i] = -1;
/*
* Initialise the to-do list for BFS.
*/
head = tail = 0;
todo[tail++] = source;
/*
* Now do the BFS loop.
*/
while (head < tail && prev[sink] <= 0) {
from = todo[head++];
/*
* Try all the forward edges out of node `from'. For a
* forward edge to be valid, it must have flow
* currently less than its capacity.
*/
for (i = firstedge[from]; i < ne && edges[2*i] == from; i++) {
to = edges[2*i+1];
if (to == source || prev[to] >= 0)
continue;
if (capacity[i] >= 0 && flow[i] >= capacity[i])
continue;
/*
* This is a valid augmenting edge. Visit node `to'.
*/
prev[to] = 2*i;
todo[tail++] = to;
}
/*
* Try all the backward edges into node `from'. For a
* backward edge to be valid, it must have flow
* currently greater than zero.
*/
for (i = firstbackedge[from];
j = backedges[i], i < ne && edges[2*j+1]==from; i++) {
to = edges[2*j];
if (to == source || prev[to] >= 0)
continue;
if (flow[j] <= 0)
continue;
/*
* This is a valid augmenting edge. Visit node `to'.
*/
prev[to] = 2*j+1;
todo[tail++] = to;
}
}
/*
* If prev[sink] is non-null, we have found an augmenting
* path.
*/
if (prev[sink] >= 0) {
int max;
/*
* Work backwards along the path figuring out the
* maximum flow we can add.
*/
to = sink;
max = -1;
while (to != source) {
int spare;
/*
* Find the edge we're currently moving along.
*/
i = prev[to];
from = edges[i];
assert(from != to);
/*
* Determine the spare capacity of this edge.
*/
if (i & 1)
spare = flow[i / 2]; /* backward edge */
else if (capacity[i / 2] >= 0)
spare = capacity[i / 2] - flow[i / 2]; /* forward edge */
else
spare = -1; /* unlimited forward edge */
assert(spare != 0);
if (max < 0 || (spare >= 0 && spare < max))
max = spare;
to = from;
}
/*
* Fail an assertion if max is still < 0, i.e. there is
* an entirely unlimited path from source to sink. Also
* max should not _be_ zero, because by construction
* this _should_ be an augmenting path.
*/
assert(max > 0);
/*
* Now work backwards along the path again, this time
* actually adjusting the flow.
*/
to = sink;
while (to != source) {
/*
* Find the edge we're currently moving along.
*/
i = prev[to];
from = edges[i];
assert(from != to);
/*
* Adjust the edge.
*/
if (i & 1)
flow[i / 2] -= max; /* backward edge */
else
flow[i / 2] += max; /* forward edge */
to = from;
}
/*
* And adjust the overall flow counter.
*/
totalflow += max;
continue;
}
/*
* If we reach here, we have failed to find an augmenting
* path, which means we're done. Output the `cut' array if
* required, and leave.
*/
if (cut) {
for (i = 0; i < nv; i++) {
if (i == source || prev[i] >= 0)
cut[i] = 0;
else
cut[i] = 1;
}
}
return totalflow;
}
}
int maxflow_scratch_size(int nv)
{
return (nv * 4) * sizeof(int);
}
void maxflow_setup_backedges(int ne, const int *edges, int *backedges)
{
int i, n;
for (i = 0; i < ne; i++)
backedges[i] = i;
/*
* We actually can't use the C qsort() function, because we'd
* need to pass `edges' as a context parameter to its
* comparator function. So instead I'm forced to implement my
* own sorting algorithm internally, which is a pest. I'll use
* heapsort, because I like it.
*/
#define LESS(i,j) ( (edges[2*(i)+1] < edges[2*(j)+1]) || \
(edges[2*(i)+1] == edges[2*(j)+1] && \
edges[2*(i)] < edges[2*(j)]) )
#define PARENT(n) ( ((n)-1)/2 )
#define LCHILD(n) ( 2*(n)+1 )
#define RCHILD(n) ( 2*(n)+2 )
#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0)
/*
* Phase 1: build the heap. We want the _largest_ element at
* the top.
*/
n = 0;
while (n < ne) {
n++;
/*
* Swap element n with its parent repeatedly to preserve
* the heap property.
*/
i = n-1;
while (i > 0) {
int p = PARENT(i);
if (LESS(backedges[p], backedges[i])) {
SWAP(backedges[p], backedges[i]);
i = p;
} else
break;
}
}
/*
* Phase 2: repeatedly remove the largest element and stick it
* at the top of the array.
*/
while (n > 0) {
/*
* The largest element is at position 0. Put it at the top,
* and swap the arbitrary element from that position into
* position 0.
*/
n--;
SWAP(backedges[0], backedges[n]);
/*
* Now repeatedly move that arbitrary element down the heap
* by swapping it with the more suitable of its children.
*/
i = 0;
while (1) {
int lc, rc;
lc = LCHILD(i);
rc = RCHILD(i);
if (lc >= n)
break; /* we've hit bottom */
if (rc >= n) {
/*
* Special case: there is only one child to check.
*/
if (LESS(backedges[i], backedges[lc]))
SWAP(backedges[i], backedges[lc]);
/* _Now_ we've hit bottom. */
break;
} else {
/*
* The common case: there are two children and we
* must check them both.
*/
if (LESS(backedges[i], backedges[lc]) ||
LESS(backedges[i], backedges[rc])) {
/*
* Pick the more appropriate child to swap with
* (i.e. the one which would want to be the
* parent if one were above the other - as one
* is about to be).
*/
if (LESS(backedges[lc], backedges[rc])) {
SWAP(backedges[i], backedges[rc]);
i = rc;
} else {
SWAP(backedges[i], backedges[lc]);
i = lc;
}
} else {
/* This element is in the right place; we're done. */
break;
}
}
}
}
#undef LESS
#undef PARENT
#undef LCHILD
#undef RCHILD
#undef SWAP
}
int maxflow(int nv, int source, int sink,
int ne, const int *edges, const int *capacity,
int *flow, int *cut)
{
void *scratch;
int *backedges;
int size;
int ret;
/*
* Allocate the space.
*/
size = ne * sizeof(int) + maxflow_scratch_size(nv);
backedges = smalloc(size);
if (!backedges)
return -1;
scratch = backedges + ne;
/*
* Set up the backedges array.
*/
maxflow_setup_backedges(ne, edges, backedges);
/*
* Call the main function.
*/
ret = maxflow_with_scratch(scratch, nv, source, sink, ne, edges,
backedges, capacity, flow, cut);
/*
* Free the scratch space.
*/
sfree(backedges);
/*
* And we're done.
*/
return ret;
}
#ifdef TESTMODE
#define MAXEDGES 256
#define MAXVERTICES 128
#define ADDEDGE(i,j) do{edges[ne*2] = (i); edges[ne*2+1] = (j); ne++;}while(0)
int compare_edge(const void *av, const void *bv)
{
const int *a = (const int *)av;
const int *b = (const int *)bv;
if (a[0] < b[0])
return -1;
else if (a[0] > b[0])
return +1;
else if (a[1] < b[1])
return -1;
else if (a[1] > b[1])
return +1;
else
return 0;
}
int main(void)
{
int edges[MAXEDGES*2], ne, nv;
int capacity[MAXEDGES], flow[MAXEDGES], cut[MAXVERTICES];
int source, sink, p, q, i, j, ret;
/*
* Use this algorithm to find a maximal complete matching in a
* bipartite graph.
*/
ne = 0;
nv = 0;
source = nv++;
p = nv;
nv += 5;
q = nv;
nv += 5;
sink = nv++;
for (i = 0; i < 5; i++) {
capacity[ne] = 1;
ADDEDGE(source, p+i);
}
for (i = 0; i < 5; i++) {
capacity[ne] = 1;
ADDEDGE(q+i, sink);
}
j = ne;
capacity[ne] = 1; ADDEDGE(p+0,q+0);
capacity[ne] = 1; ADDEDGE(p+1,q+0);
capacity[ne] = 1; ADDEDGE(p+1,q+1);
capacity[ne] = 1; ADDEDGE(p+2,q+1);
capacity[ne] = 1; ADDEDGE(p+2,q+2);
capacity[ne] = 1; ADDEDGE(p+3,q+2);
capacity[ne] = 1; ADDEDGE(p+3,q+3);
capacity[ne] = 1; ADDEDGE(p+4,q+3);
/* capacity[ne] = 1; ADDEDGE(p+2,q+4); */
qsort(edges, ne, 2*sizeof(int), compare_edge);
ret = maxflow(nv, source, sink, ne, edges, capacity, flow, cut);
printf("ret = %d\n", ret);
for (i = 0; i < ne; i++)
printf("flow %d: %d -> %d\n", flow[i], edges[2*i], edges[2*i+1]);
for (i = 0; i < nv; i++)
if (cut[i] == 0)
printf("difficult set includes %d\n", i);
return 0;
}
#endif

95
maxflow.h Normal file
View File

@ -0,0 +1,95 @@
/*
* Edmonds-Karp algorithm for finding a maximum flow and minimum
* cut in a network. Almost identical to the Ford-Fulkerson
* algorithm, but apparently using breadth-first search to find the
* _shortest_ augmenting path is a good way to guarantee
* termination and ensure the time complexity is not dependent on
* the actual value of the maximum flow. I don't understand why
* that should be, but it's claimed on the Internet that it's been
* proved, and that's good enough for me. I prefer BFS to DFS
* anyway :-)
*/
#ifndef MAXFLOW_MAXFLOW_H
#define MAXFLOW_MAXFLOW_H
/*
* The actual algorithm.
*
* Inputs:
*
* - `scratch' is previously allocated scratch space of a size
* previously determined by calling `maxflow_scratch_size'.
*
* - `nv' is the number of vertices. Vertices are assumed to be
* numbered from 0 to nv-1.
*
* - `source' and `sink' are the distinguished source and sink
* vertices.
*
* - `ne' is the number of edges in the graph.
*
* - `edges' is an array of 2*ne integers, giving a (source, dest)
* pair for each network edge. Edge pairs are expected to be
* sorted in lexicographic order.
*
* - `backedges' is an array of `ne' integers, each a distinct
* index into `edges'. The edges in `edges', if permuted as
* specified by this array, should end up sorted in the _other_
* lexicographic order, i.e. dest taking priority over source.
*
* - `capacity' is an array of `ne' integers, giving a maximum
* flow capacity for each edge. A negative value is taken to
* indicate unlimited capacity on that edge, but note that there
* may not be any unlimited-capacity _path_ from source to sink
* or an assertion will be failed.
*
* Output:
*
* - `flow' must be non-NULL. It is an array of `ne' integers,
* each giving the final flow along each edge.
*
* - `cut' may be NULL. If non-NULL, it is an array of `nv'
* integers, which will be set to zero or one on output, in such
* a way that:
* + the set of zero vertices includes the source
* + the set of one vertices includes the sink
* + the maximum flow capacity between the zero and one vertex
* sets is achieved (i.e. all edges from a zero vertex to a
* one vertex are at full capacity, while all edges from a
* one vertex to a zero vertex have no flow at all).
*
* - the returned value from the function is the total flow
* achieved.
*/
int maxflow_with_scratch(void *scratch, int nv, int source, int sink,
int ne, const int *edges, const int *backedges,
const int *capacity, int *flow, int *cut);
/*
* The above function expects its `scratch' and `backedges'
* parameters to have already been set up. This allows you to set
* them up once and use them in multiple invocates of the
* algorithm. Now I provide functions to actually do the setting
* up.
*/
int maxflow_scratch_size(int nv);
void maxflow_setup_backedges(int ne, const int *edges, int *backedges);
/*
* Simplified version of the above function. All parameters are the
* same, except that `scratch' and `backedges' are constructed
* internally. This is the simplest way to call the algorithm as a
* one-off; however, if you need to call it multiple times on the
* same network, it is probably better to call the above version
* directly so that you only construct `scratch' and `backedges'
* once.
*
* Additional return value is now -1, meaning that scratch space
* could not be allocated.
*/
int maxflow(int nv, int source, int sink,
int ne, const int *edges, const int *capacity,
int *flow, int *cut);
#endif /* MAXFLOW_MAXFLOW_H */

2
osx.m
View File

@ -1341,6 +1341,8 @@ static void osx_draw_text(void *handle, int x, int y, int fonttype,
point.x -= size.width / 2; point.x -= size.width / 2;
if (align & ALIGN_VCENTRE) if (align & ALIGN_VCENTRE)
point.y -= size.height / 2; point.y -= size.height / 2;
else
point.y -= size.height;
[string drawAtPoint:point withAttributes:attr]; [string drawAtPoint:point withAttributes:attr];
} }

View File

@ -1802,6 +1802,58 @@ These parameters are available from the \q{Custom...} option on the
\dd Size of grid in squares. \dd Size of grid in squares.
\C{tents} \i{Tents}
\cfg{winhelp-topic}{games.tents}
You have a grid of squares, some of which contain trees. Your aim is
to place tents in some of the remaining squares, in such a way that
the following conditions are met:
\b There are exactly as many tents as trees.
\b The tents and trees can be matched up in such a way that each
tent is directly adjacent (horizontally or vertically, but not
diagonally) to its own tree. However, a tent may be adjacent to
other trees as well as its own.
\b No two tents are adjacent horizontally, vertically \e{or
diagonally}.
\b The number of tents in each row, and in each column, matches the
numbers given round the sides of the grid.
This puzzle can be found in several places on the Internet, and was
brought to my attention by e-mail. I don't know who I should credit
for inventing it.
\H{tents-controls} \i{Tents controls}
\IM{Tents controls} controls, for Tents
Left-clicking in a blank square will place a tent in it.
Right-clicking in a blank square will colour it green, indicating
that you are sure it \e{isn't} a tent. Clicking either button in an
occupied square will clear it.
(All the actions described in \k{common-actions} are also available.)
\H{tents-parameters} \I{parameters, for Tents}Tents parameters
These parameters are available from the \q{Custom...} option on the
\q{Type} menu.
\dt \e{Width}, \e{Height}
\dd Size of grid in squares.
\dt \e{Difficulty}
\dd Controls the difficulty of the generated puzzle. More difficult
puzzles require more complex deductions, but at present none of the
available difficulty levels requires guesswork or backtracking.
\A{licence} \I{MIT licence}\ii{Licence} \A{licence} \I{MIT licence}\ii{Licence}
This software is \i{copyright} 2004-2005 Simon Tatham. This software is \i{copyright} 2004-2005 Simon Tatham.

2041
tents.c Normal file

File diff suppressed because it is too large Load Diff