forked from len0rd/rockbox
This brings the puzzles source in sync with Simon's branch, commit fd304c5 (from March 2024), with some added Rockbox-specific compatibility changes: https://www.franklinwei.com/git/puzzles/commit/?h=rockbox-devel&id=516830d9d76bdfe64fe5ccf2a9b59c33f5c7c078 There are quite a lot of backend changes, including a new "Mosaic" puzzle. In addition, some new frontend changes were necessary: - New "Preferences" menu to access the user preferences system. - Enabled spacebar input for several games. Change-Id: I94c7df674089c92f32d5f07025f6a1059068af1e
331 lines
9.9 KiB
C
331 lines
9.9 KiB
C
/*
|
|
* Implementation of matching.h.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "puzzles.h"
|
|
#include "matching.h"
|
|
|
|
struct scratch {
|
|
/*
|
|
* Current contents of the in-progress matching. LtoR is an array
|
|
* of nl integers, each of which holds a value in {0,1,...,nr-1},
|
|
* or -1 for no current assignment. RtoL is exactly the reverse.
|
|
*
|
|
* Invariant: LtoR[i] is non-empty and equal to j if and only if
|
|
* RtoL[j] is non-empty and equal to i.
|
|
*/
|
|
int *LtoR, *RtoL;
|
|
|
|
/*
|
|
* Arrays of nl and nr integer respectively, giving the layer
|
|
* assigned to each integer in the breadth-first search step of
|
|
* the algorithm.
|
|
*/
|
|
int *Llayer, *Rlayer;
|
|
|
|
/*
|
|
* Arrays of nl and nr integers respectively, used to hold the
|
|
* to-do queues in the breadth-first search.
|
|
*/
|
|
int *Lqueue, *Rqueue;
|
|
|
|
/*
|
|
* An augmenting path of vertices, alternating between L vertices
|
|
* (in the even-numbered positions, starting at 0) and R (in the
|
|
* odd positions). Must be long enough to hold any such path that
|
|
* never repeats a vertex, i.e. must be at least 2*min(nl,nr) in
|
|
* size.
|
|
*/
|
|
int *augpath;
|
|
|
|
/*
|
|
* Track the progress of the depth-first search at each
|
|
* even-numbered layer. Has one element for each even-numbered
|
|
* position in augpath.
|
|
*/
|
|
int *dfsstate;
|
|
|
|
/*
|
|
* Store a random permutation of the L vertex indices, if we're
|
|
* randomising the dfs phase.
|
|
*/
|
|
int *Lorder;
|
|
};
|
|
|
|
size_t matching_scratch_size(int nl, int nr)
|
|
{
|
|
size_t n;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
n = (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
n += nl; /* LtoR */
|
|
n += nr; /* RtoL */
|
|
n += nl; /* Llayer */
|
|
n += nr; /* Rlayer */
|
|
n += nl; /* Lqueue */
|
|
n += nr; /* Rqueue */
|
|
n += 2*nmin; /* augpath */
|
|
n += nmin; /* dfsstate */
|
|
n += nl; /* Lorder */
|
|
return n * sizeof(int);
|
|
}
|
|
|
|
int matching_with_scratch(void *scratchv,
|
|
int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int L, R, i, j;
|
|
|
|
/*
|
|
* Set up the various array pointers in the scratch space.
|
|
*/
|
|
{
|
|
int *p = scratchv;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
p += (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
s->LtoR = p; p += nl;
|
|
s->RtoL = p; p += nr;
|
|
s->Llayer = p; p += nl;
|
|
s->Rlayer = p; p += nr;
|
|
s->Lqueue = p; p += nl;
|
|
s->Rqueue = p; p += nr;
|
|
s->augpath = p; p += 2*nmin;
|
|
s->dfsstate = p; p += nmin;
|
|
s->Lorder = p; p += nl;
|
|
}
|
|
|
|
/*
|
|
* Set up the initial matching, which is empty.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->LtoR[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->RtoL[R] = -1;
|
|
|
|
while (1) {
|
|
/*
|
|
* Breadth-first search starting from the unassigned left
|
|
* vertices, traversing edges from left to right only if they
|
|
* are _not_ part of the matching, and from right to left only
|
|
* if they _are_. We assign a 'layer number' to all vertices
|
|
* visited by this search, with the starting vertices being
|
|
* layer 0 and every successor of a layer-n node being layer
|
|
* n+1.
|
|
*/
|
|
int Lqs, Rqs, layer, target_layer;
|
|
|
|
for (L = 0; L < nl; L++)
|
|
s->Llayer[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->Rlayer[R] = -1;
|
|
|
|
Lqs = 0;
|
|
for (L = 0; L < nl; L++) {
|
|
if (s->LtoR[L] == -1) {
|
|
s->Llayer[L] = 0;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
|
|
layer = 0;
|
|
while (1) {
|
|
bool found_free_R_vertex = false;
|
|
|
|
Rqs = 0;
|
|
for (i = 0; i < Lqs; i++) {
|
|
L = s->Lqueue[i];
|
|
assert(s->Llayer[L] == layer);
|
|
|
|
for (j = 0; j < adjsizes[L]; j++) {
|
|
R = adjlists[L][j];
|
|
if (R != s->LtoR[L] && s->Rlayer[R] == -1) {
|
|
s->Rlayer[R] = layer+1;
|
|
s->Rqueue[Rqs++] = R;
|
|
if (s->RtoL[R] == -1)
|
|
found_free_R_vertex = true;
|
|
}
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (found_free_R_vertex)
|
|
break;
|
|
|
|
if (Rqs == 0)
|
|
goto done;
|
|
|
|
Lqs = 0;
|
|
for (j = 0; j < Rqs; j++) {
|
|
R = s->Rqueue[j];
|
|
assert(s->Rlayer[R] == layer);
|
|
if ((L = s->RtoL[R]) != -1 && s->Llayer[L] == -1) {
|
|
s->Llayer[L] = layer+1;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (Lqs == 0)
|
|
goto done;
|
|
}
|
|
|
|
target_layer = layer;
|
|
|
|
/*
|
|
* Vertices in the target layer are only interesting if
|
|
* they're actually unassigned. Blanking out the others here
|
|
* will save us a special case in the dfs loop below.
|
|
*/
|
|
for (R = 0; R < nr; R++)
|
|
if (s->Rlayer[R] == target_layer && s->RtoL[R] != -1)
|
|
s->Rlayer[R] = -1;
|
|
|
|
/*
|
|
* Choose an ordering in which to try the L vertices at the
|
|
* start of the next pass.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->Lorder[L] = L;
|
|
if (rs)
|
|
shuffle(s->Lorder, nl, sizeof(*s->Lorder), rs);
|
|
|
|
/*
|
|
* Now depth-first search through that layered set of vertices
|
|
* to find as many (vertex-)disjoint augmenting paths as we
|
|
* can, and for each one we find, augment the matching.
|
|
*/
|
|
s->dfsstate[0] = 0;
|
|
i = 0;
|
|
while (1) {
|
|
/*
|
|
* Find the next vertex to go on the end of augpath.
|
|
*/
|
|
if (i == 0) {
|
|
/* In this special case, we're just looking for L
|
|
* vertices that are not yet assigned. */
|
|
if (s->dfsstate[i] == nl)
|
|
break; /* entire DFS has finished */
|
|
|
|
L = s->Lorder[s->dfsstate[i]++];
|
|
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
} else {
|
|
/* In the more usual case, we're going through the
|
|
* adjacency list for the previous L vertex. */
|
|
L = s->augpath[2*i-2];
|
|
j = s->dfsstate[i]++;
|
|
if (j == adjsizes[L]) {
|
|
/* Run out of neighbours of the previous vertex. */
|
|
i--;
|
|
continue;
|
|
}
|
|
if (rs && adjsizes[L] - j > 1) {
|
|
int which = j + random_upto(rs, adjsizes[L] - j);
|
|
int tmp = adjlists[L][which];
|
|
adjlists[L][which] = adjlists[L][j];
|
|
adjlists[L][j] = tmp;
|
|
}
|
|
R = adjlists[L][j];
|
|
|
|
if (s->Rlayer[R] != 2*i-1)
|
|
continue; /* skip this vertex */
|
|
|
|
s->augpath[2*i-1] = R;
|
|
s->Rlayer[R] = -1; /* mark vertex as visited */
|
|
|
|
if (2*i-1 == target_layer) {
|
|
/*
|
|
* We've found an augmenting path, in the form of
|
|
* an even-sized list of vertices alternating
|
|
* L,R,...,L,R, with the initial L and final R
|
|
* vertex free and otherwise each R currently
|
|
* connected to the next L. Adjust so that each L
|
|
* connects to the next R, increasing the edge
|
|
* count in the matching by 1.
|
|
*/
|
|
for (j = 0; j < 2*i; j += 2) {
|
|
s->LtoR[s->augpath[j]] = s->augpath[j+1];
|
|
s->RtoL[s->augpath[j+1]] = s->augpath[j];
|
|
}
|
|
|
|
/*
|
|
* Having dealt with that path, and already marked
|
|
* all its vertices as visited, rewind right to
|
|
* the start and resume our DFS from a new
|
|
* starting L-vertex.
|
|
*/
|
|
i = 0;
|
|
continue;
|
|
}
|
|
|
|
L = s->RtoL[R];
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
}
|
|
|
|
s->augpath[2*i] = L;
|
|
s->Llayer[L] = -1; /* mark vertex as visited */
|
|
i++;
|
|
s->dfsstate[i] = 0;
|
|
}
|
|
}
|
|
|
|
done:
|
|
/*
|
|
* Fill in the output arrays.
|
|
*/
|
|
if (outl) {
|
|
for (i = 0; i < nl; i++)
|
|
outl[i] = s->LtoR[i];
|
|
}
|
|
if (outr) {
|
|
for (j = 0; j < nr; j++)
|
|
outr[j] = s->RtoL[j];
|
|
}
|
|
|
|
/*
|
|
* Return the number of matching edges.
|
|
*/
|
|
for (i = j = 0; i < nl; i++)
|
|
if (s->LtoR[i] != -1)
|
|
j++;
|
|
return j;
|
|
}
|
|
|
|
int matching(int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
void *scratch;
|
|
int size;
|
|
int ret;
|
|
|
|
size = matching_scratch_size(nl, nr);
|
|
scratch = malloc(size);
|
|
if (!scratch)
|
|
return -1;
|
|
|
|
ret = matching_with_scratch(scratch, nl, nr, adjlists, adjsizes,
|
|
rs, outl, outr);
|
|
|
|
free(scratch);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void matching_witness(void *scratchv, int nl, int nr, int *witness)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int i, j;
|
|
|
|
for (i = 0; i < nl; i++)
|
|
witness[i] = s->Llayer[i] == -1;
|
|
for (j = 0; j < nr; j++)
|
|
witness[nl + j] = s->Rlayer[j] == -1;
|
|
}
|