Quinto

``Quinto'' is a game played on a 5x5 grid of buttons, each of which is in one of two states (eg, black or white). When any button is clicked, the state of that button and each of its four neighbors (above and below, left and right) is inverted (black to white, white to black). The goal is to set all of the buttons to one color (simultaneously). The game is obviously generalizable to any size grid, and to arbitrary initial button states.

There are obvious questions: how, in general, is the game solved? Does every possible initial board (with a specified size and specified initial button states) have a solution?

This problem also appeared in the Berkeley Programming Contest in Fall 2000, as problem number one. The problems from that contest are available here in PDF format.

Before reading on, I suggest you play the game. If you have a Tcl/Tk interpreter (eg, wish) you can run Quinto.tcl. Otherwise you can try our Java applet implementation by Kaolin Fire.


Analyzing the game, the first obvious fact is that clicking a button twice is the same as not clicking it at all (negation composed with negation is the identity operation), and thus we only have to decide whether or not to click each button. Thus, we can represent any possible solution to the game played on an nxn board as an nxn array of boolean variables, where a value of true indicates that a button is clicked, and false indicates that a button is not clicked. For the following discussion, unless otherwise stated, it is assumed that all buttons start out in one state and the object is to convert them all simultaneously to the other state.

We can easily write down the requirements that a possible solution must satisfy to be an actual solution to the game. Consider the following general representation of a possible solution to the three-by-three form of the game:

a b c
d e f
g h i

I'll represent the variables from the possible solution with lower case letters, and the final button states with upper case letters. For instance, e is true iff the center button gets clicked, and E is true iff, after clicking the buttons indicated by {a,b,c,...,i}, the center button is in the second state. We can easily write the equations for {A, B, ...} in terms of {a, b, ... }:

A = a xor b xor c B = b xor c xor e xor a C = c xor f xor b D = d xor a xor e xor g E = e xor b xor f xor h xor d F = f xor c xor i xor e G = g xor d xor h H = h xor e xor i xor g I = i xor f xor h

We require than an actual solution have every button set, eg, the following holds:

Solved = A and B and C and D and E and F and G and H and I

Finding a solution to the puzzle is now reduced to satisfying the preceeding logical expression. Taking advantage of the following and other identities, we can reduce the expression to disjunctive normal form (see: boolean normal forms), from which we can read off the solutions.

x xor y = (x and (not y)) or ((not x) and y)   [definition of xor]
x and (y or z) = (x and y) or (x and z)        [distributive property]
not (x or y) = (not x) and (not y)             [De Morgan's law]
not (x and y) = (not x) or (not y)             [De Morgan's law]

I found it expedient to implement these operations in Scheme (owing, probably, to a similar assignment from CS70) in which logical operations are written in prefix notation. For instance, a and b and c will be written (and a b c).

The logical expression for a solution to the 3x3 puzzle may be expressed as:


(make-and
 (make-xor 'a 'b 'd)
 (make-xor 'b 'c 'e 'a)
 (make-xor 'c 'f 'b)
 (make-xor 'd 'a 'e 'g)
 (make-xor 'e 'b 'f 'h 'd)
 (make-xor 'f 'c 'i 'e)
 (make-xor 'g 'd 'h)
 (make-xor 'h 'e 'i 'g)
 (make-xor 'i 'f 'h))

And the scheme interpreter spits out the disjunctive normal form equivalent. Notice that a DNF expression is composed of the disjunction ("or") of a series of terms, each of which is the conjunction ("and") of one or more boolean variables (literals), each of which may possibly be negated. Thus each term represents a distinct solution to the puzzle. If a literal is plain (not negated) then the corresponding button must be clicked in this solution, otherwise it must not be clicked:

(and i (not h) g (not f) c e a (not b) (not d))

Evidently, for the 3x3 case, there is only a single solution, in which {a,c,e,g,i} must be clicked, and {b,d,f,h} must not be clicked. Visually (dark indicating that a button must be clicked):

a b c
d e f
g h i

The four-by-four case results in sixteen solutions, consisting of five distinct patterns and their rotations:

0 0 0 0
1 1 1 1
1 0 0 1
1 1 1 1
0 0 0 1
1 1 0 0
1 1 0 0
0 0 0 1
0 0 1 0
1 0 0 0
0 0 0 1
0 1 0 0
0 0 1 1
1 0 1 1
0 1 0 0
1 0 1 0
1 1 0 1
1 1 1 0
0 1 1 1
1 0 1 1

Unfortunately the complexity of this algorithm grows very quickly. For example, a 5x5 grid contains 25 variables, so the resulting DNF expression could conceivably contain as many as 2^25 or nearly 30 million terms. It's important to realize, however, that this is not really a brute force approach, as it doesn't actually check each of the possible assignments, but rather it logically deduces what the successful assignments must be. Unfortunately the amount of algebra involved in this process grows rapidly with the number of variables. My basic DNF program, furthermore, is in no way optimized for efficiency. I briefly considered implementing the same system in Prolog, but then I remembered Mathematica...


Doing the same thing using Mathematica

As you should expect, Mathematica can manipulate boolean expressions and includes a "//LogicalExpand" imperative that reforms an expression in disjunctive normal form. The 3x3 example may be expressed in Mathematica as:

And[Xor[a,b,d],
    Xor[b,c,e,a],
    Xor[c,f,b],
    Xor[d,a,e,g],
    Xor[e,b,f,h,d],
    Xor[f,c,i,e],
    Xor[g,d,h],
    Xor[h,e,i,g],
    Xor[i,f,h]]//LogicalExpand

Mathematica quickly returns the expected solution (where && is logical and, || is logical or, and ! is logical not, as in C):

a && c && e && g && i && !b && !d && !f && !h

Mathematica, being the amazingly lightning-fast program that it is, is able to solve the 5x5 case in about a second:

In[7] = And[Xor[a,b,f],Xor[b,a,g,c],Xor[c,b,h,d],Xor[d,c,i,e],Xor[e,d,j],
    Xor[f,a,g,k],Xor[g,b,h,l,f],Xor[h,c,i,m,g],Xor[i,d,j,n,h],Xor[j,e,i,o],
    Xor[k,f,l,p],Xor[l,g,m,q,k],Xor[m,h,n,r,l],Xor[n,i,o,s,m],Xor[o,j,n,t],
    Xor[p,k,q,u],Xor[q,l,r,v,p],Xor[r,m,s,w,q],Xor[s,n,t,x,r],Xor[t,o,y,s],
    Xor[u,p,v],Xor[v,u,q,w],Xor[w,r,x,v],Xor[x,s,y,w],Xor[y,t,x]]//LogicalExpand

Out[7]= a && b && f && g && i && j && m && n && o && q && r && s && v && w &&
>     y && !c && !d && !e && !h && !k && !l && !p && !t && !u && !x ||
>    a && c && d && g && h && i && k && l && m && p && q && s && t && x &&
>     y && !b && !e && !f && !j && !n && !o && !r && !u && !v && !w ||
>    b && c && e && g && h && i && m && n && o && p && q && s && t && u &&
>     v && !a && !d && !f && !j && !k && !l && !r && !w && !x && !y ||
>    d && e && f && g && i && j && k && l && m && q && r && s && u && w &&
>     x && !a && !b && !c && !h && !n && !o && !p && !t && !v && !y

Another Approach

I find the above approach particularly appealing because it locates the solutions through a straightforward application of algebra. It's a completely deterministic logical deduction from the statement of the problem to the formation of the solution. However, the trouble is that it is slow, and, at least in the naive application of algebra given above, intractable for large grids. The intermediate algebraic expressions simply grow to be huge.

Another glaring sore in the above approach is that the fundamental locality in the puzzle is only implicitly considered. What is this locality? The final state of a cell is determined only by actions performed on it and its four neighbors. Thus, it is possible to start a brute force search of the solution space. However, instead of only checking whether a wholly-decided potential solution is an actual solution, we can check the partially-decided candidates too. If at any point the finally-decided actions on a cell and its neighbors indicate that that cell will not be satisfied, we can abort this branch of the search and backtrack. This decreases the complexity of the search vastly. Indeed, this search proves to be extremely efficient.

Chris Spitzer implemented this method in C, and his implementation will find solutions with astonishing speed.

An open question is, given a board size X by Y buttons, how many distinct solutions exist? Chris has tabulated this for a few board sizes.

I implemented this algorithm in Python as an excercise in learning that language: quinto.py.

Copyright © 2001 by Tobin Fricke. Created April 4, 2001 at Berkeley, California. If you have comments or questions, please email me.