In "Theory of Moves", Steven J. Brams analyses two-player games with two strategies per player, where each player can totally rank his payoffs, although payoffs need not be comparable among players. We can thus use values 1,2,3,4 to label the payoffs of each player. This is called a 'strict ordinal' game.
An example game matrix, with player A's payoff as the first element, player B's payoff as the second element of the payoff tuples:

We now define the equivalence classes of "identical game matrices" to reduce the number of distinct cases to analyze. The game matrices in an equivalence class are related by the following operations:
- Exchange columns (i.e. relabel Player A's strategies)
- Exchange rows (i.e. relabel Player B's strategies)
- Exchange the players, which means exchanging the tuple values and mirroring the matrix along the first diagonal

For discussion purposes, add the non-elementary operation:
- Exchange columns, then rows (or the equivalent reverse): "mirroring"
Question: How many game matrix equivalence classes are there?
The author claims "There are exactly 78 such 'strict ordinal' 2x2 games". He must be right because his whole book is based on this.
And I checked with a little program, and indeed I get 78 distinct "canonical" matrixes.
However, using exhaustive enumeration is quite a bit in the 'brute force' domain and I am not even sure why the application of a single "player exchange" followed by either a "row exchange", "column exchange" or "mirroring" is sufficient to reach all the elements of the equivalence classes.
So, is there a better way to show that there are only 78 equivalence classes (which actually is strange because this is 13 * 3 * 2, why is there a factor 13?). There has got to be. I amused myself to design a way to build the canonical matrixes by exploiting symmetries but it seems I am too rusty.
Code result:
[ (4,4),(1,2) | (2,3),(3,1) ] accepted as canonical entry 1
[ (4,4),(1,3) | (2,2),(3,1) ] accepted as canonical entry 2
[ (4,3),(1,1) | (2,2),(3,4) ] accepted as canonical entry 3
[ (4,4),(1,1) | (2,3),(3,2) ] accepted as canonical entry 4
[ (4,3),(1,2) | (2,1),(3,4) ] accepted as canonical entry 5
[ (4,1),(1,4) | (2,2),(3,3) ] accepted as canonical entry 6
[ (4,1),(1,4) | (2,3),(3,2) ] accepted as canonical entry 7
[ (4,2),(1,3) | (2,4),(3,1) ] accepted as canonical entry 8
[ (4,2),(1,1) | (2,4),(3,3) ] accepted as canonical entry 9
[ (4,3),(1,4) | (2,2),(3,1) ] accepted as canonical entry 10
[ (4,3),(1,4) | (2,1),(3,2) ] accepted as canonical entry 11
[ (4,2),(1,1) | (2,3),(3,4) ] accepted as canonical entry 12
[ (4,1),(1,3) | (2,4),(3,2) ] accepted as canonical entry 13
[ (4,2),(1,4) | (2,1),(3,3) ] accepted as canonical entry 14
[ (4,3),(1,2) | (2,4),(3,1) ] accepted as canonical entry 15
[ (4,2),(1,3) | (2,1),(3,4) ] accepted as canonical entry 16
[ (4,3),(1,1) | (2,4),(3,2) ] accepted as canonical entry 17
[ (4,4),(1,1) | (2,2),(3,3) ] accepted as canonical entry 18
[ (4,2),(1,4) | (2,3),(3,1) ] equivalent to [ (4,1),(1,3) | (2,4),(3,2) ] via row ∘ player
[ (4,1),(1,3) | (2,2),(3,4) ] accepted as canonical entry 19
[ (4,4),(1,2) | (2,1),(3,3) ] accepted as canonical entry 20
[ (4,1),(1,2) | (2,3),(3,4) ] accepted as canonical entry 21
[ (4,1),(1,2) | (2,4),(3,3) ] accepted as canonical entry 22
[ (4,4),(1,3) | (2,1),(3,2) ] accepted as canonical entry 23
[ (4,4),(2,2) | (3,3),(1,1) ] accepted as canonical entry 24
[ (4,4),(2,3) | (3,2),(1,1) ] accepted as canonical entry 25
[ (4,3),(2,1) | (3,2),(1,4) ] equivalent to [ (4,1),(1,2) | (2,3),(3,4) ] via mirror ∘ player
[ (4,4),(2,1) | (3,3),(1,2) ] accepted as canonical entry 26
[ (4,3),(2,2) | (3,1),(1,4) ] accepted as canonical entry 27
[ (4,1),(2,4) | (3,2),(1,3) ] accepted as canonical entry 28
[ (4,1),(2,4) | (3,3),(1,2) ] accepted as canonical entry 29
[ (4,2),(2,3) | (3,4),(1,1) ] accepted as canonical entry 30
[ (4,2),(2,1) | (3,4),(1,3) ] accepted as canonical entry 31
[ (4,3),(2,4) | (3,2),(1,1) ] accepted as canonical entry 32
[ (4,3),(2,4) | (3,1),(1,2) ] equivalent to [ (4,2),(2,1) | (3,4),(1,3) ] via row ∘ player
[ (4,2),(2,1) | (3,3),(1,4) ] accepted as canonical entry 33
[ (4,1),(2,3) | (3,4),(1,2) ] equivalent to [ (4,3),(1,4) | (2,1),(3,2) ] via column ∘ player
[ (4,2),(2,4) | (3,1),(1,3) ] accepted as canonical entry 34
[ (4,3),(2,2) | (3,4),(1,1) ] accepted as canonical entry 35
[ (4,2),(2,3) | (3,1),(1,4) ] accepted as canonical entry 36
[ (4,3),(2,1) | (3,4),(1,2) ] accepted as canonical entry 37
[ (4,4),(2,1) | (3,2),(1,3) ] accepted as canonical entry 38
[ (4,2),(2,4) | (3,3),(1,1) ] equivalent to [ (4,2),(1,1) | (2,4),(3,3) ] via row ∘ player
[ (4,1),(2,3) | (3,2),(1,4) ] accepted as canonical entry 39
[ (4,4),(2,2) | (3,1),(1,3) ] equivalent to [ (4,4),(1,3) | (2,2),(3,1) ] via player
[ (4,1),(2,2) | (3,3),(1,4) ] accepted as canonical entry 40
[ (4,1),(2,2) | (3,4),(1,3) ] accepted as canonical entry 41
[ (4,4),(2,3) | (3,1),(1,2) ] accepted as canonical entry 42
[ (4,4),(3,2) | (1,3),(2,1) ] accepted as canonical entry 43
[ (4,4),(3,3) | (1,2),(2,1) ] equivalent to [ (4,4),(2,1) | (3,3),(1,2) ] via player
[ (4,3),(3,1) | (1,2),(2,4) ] equivalent to [ (4,2),(1,3) | (2,1),(3,4) ] via mirror ∘ player
[ (4,4),(3,1) | (1,3),(2,2) ] accepted as canonical entry 44
[ (4,3),(3,2) | (1,1),(2,4) ] accepted as canonical entry 45
[ (4,1),(3,4) | (1,2),(2,3) ] accepted as canonical entry 46
[ (4,1),(3,4) | (1,3),(2,2) ] accepted as canonical entry 47
[ (4,2),(3,3) | (1,4),(2,1) ] accepted as canonical entry 48
[ (4,2),(3,1) | (1,4),(2,3) ] equivalent to [ (4,1),(2,4) | (3,2),(1,3) ] via column ∘ player
[ (4,3),(3,4) | (1,2),(2,1) ] accepted as canonical entry 49
[ (4,3),(3,4) | (1,1),(2,2) ] equivalent to [ (4,3),(2,2) | (3,4),(1,1) ] via row ∘ player
[ (4,2),(3,1) | (1,3),(2,4) ] accepted as canonical entry 50
[ (4,1),(3,3) | (1,4),(2,2) ] equivalent to [ (4,1),(1,4) | (2,2),(3,3) ] via column ∘ player
[ (4,2),(3,4) | (1,1),(2,3) ] accepted as canonical entry 51
[ (4,3),(3,2) | (1,4),(2,1) ] equivalent to [ (4,1),(3,4) | (1,2),(2,3) ] via column ∘ player
[ (4,2),(3,3) | (1,1),(2,4) ] accepted as canonical entry 52
[ (4,3),(3,1) | (1,4),(2,2) ] accepted as canonical entry 53
[ (4,4),(3,1) | (1,2),(2,3) ] accepted as canonical entry 54
[ (4,2),(3,4) | (1,3),(2,1) ] equivalent to [ (4,3),(1,2) | (2,4),(3,1) ] via row ∘ player
[ (4,1),(3,3) | (1,2),(2,4) ] accepted as canonical entry 55
[ (4,4),(3,2) | (1,1),(2,3) ] equivalent to [ (4,4),(1,1) | (2,3),(3,2) ] via player
[ (4,1),(3,2) | (1,3),(2,4) ] equivalent to [ (4,2),(2,3) | (3,1),(1,4) ] via mirror ∘ player
[ (4,1),(3,2) | (1,4),(2,3) ] accepted as canonical entry 56
[ (4,4),(3,3) | (1,1),(2,2) ] accepted as canonical entry 57
[ (4,4),(3,2) | (2,3),(1,1) ] accepted as canonical entry 58
[ (4,4),(3,3) | (2,2),(1,1) ] equivalent to [ (4,4),(2,2) | (3,3),(1,1) ] via player
[ (4,3),(3,1) | (2,2),(1,4) ] equivalent to [ (4,1),(1,3) | (2,2),(3,4) ] via mirror ∘ player
[ (4,4),(3,1) | (2,3),(1,2) ] equivalent to [ (4,4),(3,2) | (1,3),(2,1) ] via player
[ (4,3),(3,2) | (2,1),(1,4) ] accepted as canonical entry 59
[ (4,1),(3,4) | (2,2),(1,3) ] equivalent to [ (4,3),(3,1) | (1,4),(2,2) ] via row ∘ player
[ (4,1),(3,4) | (2,3),(1,2) ] accepted as canonical entry 60
[ (4,2),(3,3) | (2,4),(1,1) ] accepted as canonical entry 61
[ (4,2),(3,1) | (2,4),(1,3) ] equivalent to [ (4,2),(2,4) | (3,1),(1,3) ] via column ∘ player
[ (4,3),(3,4) | (2,2),(1,1) ] accepted as canonical entry 62
[ (4,3),(3,4) | (2,1),(1,2) ] equivalent to [ (4,3),(2,1) | (3,4),(1,2) ] via row ∘ player
[ (4,2),(3,1) | (2,3),(1,4) ] accepted as canonical entry 63
[ (4,1),(3,3) | (2,4),(1,2) ] equivalent to [ (4,2),(1,4) | (2,1),(3,3) ] via column ∘ player
[ (4,2),(3,4) | (2,1),(1,3) ] accepted as canonical entry 64
[ (4,3),(3,2) | (2,4),(1,1) ] equivalent to [ (4,2),(3,4) | (1,1),(2,3) ] via column ∘ player
[ (4,2),(3,3) | (2,1),(1,4) ] equivalent to [ (4,1),(3,3) | (1,2),(2,4) ] via mirror ∘ player
[ (4,3),(3,1) | (2,4),(1,2) ] equivalent to [ (4,2),(3,4) | (2,1),(1,3) ] via column ∘ player
[ (4,4),(3,1) | (2,2),(1,3) ] accepted as canonical entry 65
[ (4,2),(3,4) | (2,3),(1,1) ] equivalent to [ (4,3),(1,1) | (2,4),(3,2) ] via row ∘ player
[ (4,1),(3,3) | (2,2),(1,4) ] accepted as canonical entry 66
[ (4,4),(3,2) | (2,1),(1,3) ] equivalent to [ (4,4),(1,2) | (2,3),(3,1) ] via player
[ (4,1),(3,2) | (2,3),(1,4) ] equivalent to [ (4,1),(2,3) | (3,2),(1,4) ] via mirror ∘ player
[ (4,1),(3,2) | (2,4),(1,3) ] accepted as canonical entry 67
[ (4,4),(3,3) | (2,1),(1,2) ] accepted as canonical entry 68
[ (4,4),(2,2) | (1,3),(3,1) ] equivalent to [ (4,4),(3,1) | (2,2),(1,3) ] via player
[ (4,4),(2,3) | (1,2),(3,1) ] equivalent to [ (4,4),(2,1) | (3,2),(1,3) ] via player
[ (4,3),(2,1) | (1,2),(3,4) ] equivalent to [ (4,3),(1,2) | (2,1),(3,4) ] via mirror ∘ player
[ (4,4),(2,1) | (1,3),(3,2) ] equivalent to [ (4,4),(3,1) | (1,2),(2,3) ] via player
[ (4,3),(2,2) | (1,1),(3,4) ] accepted as canonical entry 69
[ (4,1),(2,4) | (1,2),(3,3) ] equivalent to [ (4,2),(3,3) | (1,4),(2,1) ] via row ∘ player
[ (4,1),(2,4) | (1,3),(3,2) ] accepted as canonical entry 70
[ (4,2),(2,3) | (1,4),(3,1) ] equivalent to [ (4,1),(2,4) | (1,3),(3,2) ] via column ∘ player
[ (4,2),(2,1) | (1,4),(3,3) ] equivalent to [ (4,1),(2,4) | (3,3),(1,2) ] via column ∘ player
[ (4,3),(2,4) | (1,2),(3,1) ] accepted as canonical entry 71
[ (4,3),(2,4) | (1,1),(3,2) ] equivalent to [ (4,2),(2,3) | (3,4),(1,1) ] via row ∘ player
[ (4,2),(2,1) | (1,3),(3,4) ] accepted as canonical entry 72
[ (4,1),(2,3) | (1,4),(3,2) ] equivalent to [ (4,1),(1,4) | (2,3),(3,2) ] via column ∘ player
[ (4,2),(2,4) | (1,1),(3,3) ] equivalent to [ (4,2),(3,3) | (2,4),(1,1) ] via row ∘ player
[ (4,3),(2,2) | (1,4),(3,1) ] equivalent to [ (4,1),(3,4) | (1,3),(2,2) ] via column ∘ player
[ (4,2),(2,3) | (1,1),(3,4) ] equivalent to [ (4,3),(3,2) | (1,1),(2,4) ] via mirror ∘ player
[ (4,3),(2,1) | (1,4),(3,2) ] equivalent to [ (4,1),(3,4) | (2,3),(1,2) ] via column ∘ player
[ (4,4),(2,1) | (1,2),(3,3) ] accepted as canonical entry 73
[ (4,2),(2,4) | (1,3),(3,1) ] equivalent to [ (4,2),(1,3) | (2,4),(3,1) ] via row ∘ player
[ (4,1),(2,3) | (1,2),(3,4) ] equivalent to [ (4,3),(3,2) | (2,1),(1,4) ] via mirror ∘ player
[ (4,4),(2,2) | (1,1),(3,3) ] equivalent to [ (4,4),(1,1) | (2,2),(3,3) ] via player
[ (4,1),(2,2) | (1,3),(3,4) ] equivalent to [ (4,3),(2,2) | (3,1),(1,4) ] via mirror ∘ player
[ (4,1),(2,2) | (1,4),(3,3) ] accepted as canonical entry 74
[ (4,4),(2,3) | (1,1),(3,2) ] accepted as canonical entry 75
[ (4,4),(1,2) | (3,3),(2,1) ] equivalent to [ (4,4),(3,3) | (2,1),(1,2) ] via player
[ (4,4),(1,3) | (3,2),(2,1) ] equivalent to [ (4,4),(2,3) | (3,1),(1,2) ] via player
[ (4,3),(1,1) | (3,2),(2,4) ] equivalent to [ (4,2),(1,1) | (2,3),(3,4) ] via mirror ∘ player
[ (4,4),(1,1) | (3,3),(2,2) ] equivalent to [ (4,4),(3,3) | (1,1),(2,2) ] via player
[ (4,3),(1,2) | (3,1),(2,4) ] equivalent to [ (4,2),(2,1) | (1,3),(3,4) ] via mirror ∘ player
[ (4,1),(1,4) | (3,2),(2,3) ] equivalent to [ (4,1),(3,2) | (1,4),(2,3) ] via row ∘ player
[ (4,1),(1,4) | (3,3),(2,2) ] equivalent to [ (4,1),(2,2) | (1,4),(3,3) ] via row ∘ player
[ (4,2),(1,3) | (3,4),(2,1) ] equivalent to [ (4,3),(2,4) | (1,2),(3,1) ] via column ∘ player
[ (4,2),(1,1) | (3,4),(2,3) ] equivalent to [ (4,3),(2,4) | (3,2),(1,1) ] via column ∘ player
[ (4,3),(1,4) | (3,2),(2,1) ] accepted as canonical entry 76
[ (4,3),(1,4) | (3,1),(2,2) ] equivalent to [ (4,1),(2,2) | (3,4),(1,3) ] via row ∘ player
[ (4,2),(1,1) | (3,3),(2,4) ] accepted as canonical entry 77
[ (4,1),(1,3) | (3,4),(2,2) ] equivalent to [ (4,3),(1,4) | (2,2),(3,1) ] via column ∘ player
[ (4,2),(1,4) | (3,1),(2,3) ] equivalent to [ (4,1),(3,2) | (2,4),(1,3) ] via row ∘ player
[ (4,3),(1,2) | (3,4),(2,1) ] equivalent to [ (4,3),(3,4) | (1,2),(2,1) ] via column ∘ player
[ (4,2),(1,3) | (3,1),(2,4) ] equivalent to [ (4,2),(3,1) | (1,3),(2,4) ] via mirror ∘ player
[ (4,3),(1,1) | (3,4),(2,2) ] equivalent to [ (4,3),(3,4) | (2,2),(1,1) ] via column ∘ player
[ (4,4),(1,1) | (3,2),(2,3) ] equivalent to [ (4,4),(2,3) | (1,1),(3,2) ] via player
[ (4,2),(1,4) | (3,3),(2,1) ] equivalent to [ (4,1),(1,2) | (2,4),(3,3) ] via row ∘ player
[ (4,1),(1,3) | (3,2),(2,4) ] equivalent to [ (4,2),(3,1) | (2,3),(1,4) ] via mirror ∘ player
[ (4,4),(1,2) | (3,1),(2,3) ] equivalent to [ (4,4),(1,3) | (2,1),(3,2) ] via player
[ (4,1),(1,2) | (3,3),(2,4) ] equivalent to [ (4,2),(2,1) | (3,3),(1,4) ] via mirror ∘ player
[ (4,1),(1,2) | (3,4),(2,3) ] equivalent to [ (4,3),(1,4) | (3,2),(2,1) ] via column ∘ player
[ (4,4),(1,3) | (3,1),(2,2) ] accepted as canonical entry 78
Groovy code:
#!/usr/bin/groovy
// Payoff Tuple (a,b) found in game matrix position.
// The Tuple is immutable, if we need to change it, we create a new one.
// "equals()" checks for equality against another Tuple instance.
// "hashCode()" is needed for insertion/retrievel of a Tuple instance into/from
// a "Map" (in this case, the hashCode() actually a one-to-one mapping to the integers.)
class Tuple {
final int a,b
Tuple(int a,int b) {
assert 1 <= a && a <= 4
assert 1 <= b && b <= 4
this.a = a
this.b = b
}
boolean equals(def o) {
if (!(o && o instanceof Tuple)) {
return false
}
return a == o.a && b == o.b
}
int hashCode() {
return (a-1) * 4 + (b-1)
}
String toString() {
return "($a,$b)"
}
Tuple flip() {
return new Tuple(b,a)
}
}
// "GameMatrix" is an immutable structure of 2 x 2 Tuples:
// top left, top right, bottom left, bottom right
// "equals()" checks for equality against another GameMatrix instance.
// "hashCode()" is needed for insertion/retrievel of a GameMatrix instance into/from
// a "Map" (in this case, the hashCode() actually a one-to-one mapping to the integers)
class GameMatrix {
final Tuple tl, tr, bl, br
GameMatrix(Tuple tl,tr,bl,br) {
assert tl && tr && bl && br
this.tl = tl; this.tr = tr
this.bl = bl; this.br = br
}
GameMatrix colExchange() {
return new GameMatrix(tr,tl,br,bl)
}
GameMatrix rowExchange() {
return new GameMatrix(bl,br,tl,tr)
}
GameMatrix playerExchange() {
return new GameMatrix(tl.flip(),bl.flip(),tr.flip(),br.flip())
}
GameMatrix mirror() {
// columnEchange followed by rowExchange
return new GameMatrix(br,bl,tr,tl)
}
String toString() {
return "[ ${tl},${tr} | ${bl},${br} ]"
}
boolean equals(def o) {
if (!(o && o instanceof GameMatrix)) {
return false
}
return tl == o.tl && tr == o.tr && bl == o.bl && br == o.br
}
int hashCode() {
return (( tl.hashCode() * 16 + tr.hashCode() ) * 16 + bl.hashCode() ) * 16 + br.hashCode()
}
}
// Check whether a GameMatrix can be mapped to a member of the "canonicals", the set of
// equivalence class representatives, using a reduced set of transformations. Technically,
// "canonicals" is a "Map" because we want to not only ask the membership question, but
// also obtain the canonical member, which is easily done using a Map.
// The method returns the array [ canonical member, string describing the operation chain ]
// if found, [ null, null ] otherwise.
static dupCheck(GameMatrix gm, Map canonicals) {
// Applying only one of rowExchange, colExchange, mirror will
// never generate a member of "canonicals" as all of these have player A payoff 4
// at topleft, and so does gm
def q = gm.playerExchange()
def chain = "player"
if (q.tl.a == 4) {
}
else if (q.tr.a == 4) {
q = q.colExchange(); chain = "column ∘ ${chain}"
}
else if (q.bl.a == 4) {
q = q.rowExchange(); chain = "row ∘ ${chain}"
}
else if (q.br.a == 4) {
q = q.mirror(); chain = "mirror ∘ ${chain}"
}
else {
assert false : "Can't happen"
}
assert q.tl.a == 4
return (canonicals[q]) ? [ canonicals[q], chain ] : [ null, null ]
}
// Main enumerates all the possible Game Matrixes and builds the
// set of equivalence class representatives, "canonicals".
// We only bother to generate Game Matrixes of the form
// [ (4,_) , (_,_) | (_,_) , (_,_) ]
// as any other Game Matrix can be trivially transformed into the
// above form using row, column and player exchange.
static main(String[] argv) {
def canonicals = [:]
def i = 1
[3,2,1].permutations { payoffs_playerA ->
[4,3,2,1].permutations { payoffs_playerB ->
def gm = new GameMatrix(
new Tuple(4, payoffs_playerB[0]),
new Tuple(payoffs_playerA[0], payoffs_playerB[1]),
new Tuple(payoffs_playerA[1], payoffs_playerB[2]),
new Tuple(payoffs_playerA[2], payoffs_playerB[3])
)
def ( c, chain ) = dupCheck(gm,canonicals)
if (c) {
System.out << "${gm} equivalent to ${c} via ${chain}\n"
}
else {
System.out << "${gm} accepted as canonical entry ${i}\n"
canonicals[gm] = gm
i++
}
}
}
}
The symmetry operations are column exchange, row exchange, and player exchange. As you noted, it turns out that you can really only do eight different things with these operations:
Why aren't there more than eight? Well, you can't do any of "row", "column", or "player" twice in a row to get something new. "Row then column" is the same as "column then row" so 4. and 8. don't give us new ones that way. And "Row then player" is the same as "player then column", and "column then player" is the same as "player then row", so you can always put the player swaps last (or first, as you chose). Since pairs of player swaps cancel, you either have no player swaps at all (1.-4.) or one at the end/beginning (5.-8.).
Now, how do we get 78? There's a great theorem about counting, often called Burnside's Lemma, which says "when you want to count things with symmetry, the answer the the average (across all the operations) of the numbers of things (ignoring symmetry) that don't change when you do the operation.
By Burnside's Lemma, the answer is then $$\frac{24^2+0+0+0+24+0+0+24}{8}=\frac{624}8=78$$
An aside for those who know something about abstract math: Let's call the generators of the group that's acting on these tables $c$, $r$, and $p$, respectively. Note that $pcp=r$, so that $p$ and $c$ generate the group without $r$. Also note that $rc=cr$. We have $pcpc=rc=cr=cpcp$ and hence $(pc)^4=e$. Since $p^2=c^2=e$ and $(pc)^{-1}=c^{-1}p^{-1}=cp$, we have $c(pc)c=cpc^2=cp=(pc)^{-1}$. Thus, the group is dihedral of order $8$ with order-four generator $pc$ and order-two generator $c$. A priori there could be more relations, but the eight elements above definitely act as distinct transformations.