Zulip Chat Archive
Stream: general
Topic: formalizing games
Arthur Paulino (Jan 29 2022 at 14:03):
This is something that I've been contemplating (and trying) for a few days with no success. I'm trying to understand how "games" can be formalized. I will explain.
By "games" I mean structures that have to obey certain criteria. For instance, the tower of Hanoi is composed by three towers filled by discs such that a larger disc can't be on top of a smaller one. And the other characteristic of a "game" is that we want to travel through states like these in order to reach a certain state (and win).
I'm trying to formalize an old SNES game called Cu-On-Pa. You control a cube with colorful faces and make it roll on a grid. The constraint is that you can't step on a colorful cell unless the color of the top of the cube is the same color as the cell you're stepping on.
My version of the game only has one colorful goal and the grid is infinite. Here's my Lean 4 code:
inductive Face | F1 | F2 | F3 | F4 | F5 | F6
deriving DecidableEq
open Face
inductive Cube
| C12 | C13 | C14 | C15
| C21 | C23 | C24 | C26
| C31 | C32 | C35 | C36
| C41 | C42 | C45 | C46
| C51 | C53 | C54 | C56
| C62 | C63 | C64 | C65
def Cube.top : Cube → Face
| C12 => F1 | C13 => F1 | C14 => F1 | C15 => F1
| C21 => F2 | C23 => F2 | C24 => F2 | C26 => F2
| C31 => F3 | C32 => F3 | C35 => F3 | C36 => F3
| C41 => F4 | C42 => F4 | C45 => F4 | C46 => F4
| C51 => F5 | C53 => F5 | C54 => F5 | C56 => F5
| C62 => F6 | C63 => F6 | C64 => F6 | C65 => F6
/- → ↑ ← ↓ -/
abbrev CubeMoves := Cube × Cube × Cube × Cube
def CubeMoves.r (m : CubeMoves) := m.1
def CubeMoves.u (m : CubeMoves) := m.2.1
def CubeMoves.l (m : CubeMoves) := m.2.2.1
def CubeMoves.d (m : CubeMoves) := m.2.2.2
def Cube.moves : Cube → CubeMoves
| C12 => (C32, C26, C42, C51)
| C13 => (C53, C36, C23, C41)
| C14 => (C24, C46, C54, C31)
| C15 => (C45, C56, C35, C21)
| C21 => (C41, C15, C31, C62)
| C23 => (C13, C35, C63, C42)
| C24 => (C64, C45, C14, C32)
| C26 => (C36, C65, C46, C12)
| C31 => (C21, C14, C51, C63)
| C32 => (C62, C24, C12, C53)
| C35 => (C15, C54, C65, C23)
| C36 => (C56, C64, C26, C13)
| C41 => (C51, C13, C21, C64)
| C42 => (C12, C23, C62, C54)
| C45 => (C65, C53, C15, C24)
| C46 => (C26, C63, C56, C14)
| C51 => (C31, C12, C41, C65)
| C53 => (C63, C32, C13, C45)
| C54 => (C14, C42, C64, C35)
| C56 => (C46, C62, C36, C15)
| C62 => (C42, C21, C32, C56)
| C63 => (C23, C31, C53, C46)
| C64 => (C54, C41, C24, C36)
| C65 => (C35, C51, C45, C26)
structure CuOnPa where
coords : Int × Int
target : Face
cube : Cube
consistent : coords = (0, 0) → cube.top = target
def CuOnPa.isSolved (cop : CuOnPa) : Prop :=
cop.coords = (0, 0) ∧ cop.cube.top = cop.target
Now I want to prove that any state of the game is solvable, that is, there's always a series of transformations that will take a certain state to another for which isSolved
stands.
I'm not looking for a specific answer to this problem. I'm trying to understand the reasoning for formalizing these kinds of problems :pray:
Bryan Gin-ge Chen (Jan 29 2022 at 14:14):
It's not immediately clear to me what you're having trouble with, but I think you could start by thinking of the set of valid states of a game as the set of vertices of a directed graph and the transitions between valid states as edges between those vertices. Then proving the game is solvable is "just" a matter of proving that there is always a path to a "winning" vertex.
Of course there's not much you can say at this level of generality, you really need to use detailed properties of the game itself to prove that those paths exist. For example, if the game is sufficiently symmetric, you could potentially use that to show the graph is strongly connected simply by constructing a path between one pair of special vertices, etc.
Arthur Paulino (Jan 29 2022 at 14:26):
My difficulty is constraining the formalism in a way that it's impossible to "cheat". For instance, if I ask for a function that solves the game, one can simply use the CuOnPa
constructor and return the winning state independently from the provided state.
I had a similar issue trying to formalize the Hanoi tower. The function that solved the game could just return the solved problem.
Bhavik Mehta (Jan 29 2022 at 14:28):
I think the restriction you need might be something like: a "solution" function can only provide edges of the directed graph that Bryan talks about, not move along any pair of vertices
Stuart Presnell (Jan 29 2022 at 14:31):
A solution to the game should be a path from an initial state to a solved state — e.g. in the Tower of Hanoi puzzle, a series of steps from the tower-on-peg-A state to the tower-on-peg-B state.
Stuart Presnell (Jan 29 2022 at 14:46):
So for example, a solution to the 3-disc problem might look like this:
321, . , .
32, 1, .
3, 1, 2
3, . , 21
. , 3, 21
1, 3, 2
1, 32, .
. , 321, .
Kevin Buzzard (Jan 29 2022 at 14:50):
Someone on the Discord had some Hanoi code which also did graphics!
Arthur Paulino (Jan 29 2022 at 14:55):
@Stuart Presnell I was going in that direction:
def mayLeft (cop : CuOnPa) : Prop :=
cop.coords = (1, 0) → cop.cube.moves.l.top = cop.target
def mayUp (cop : CuOnPa) : Prop :=
cop.coords = (0, -1) → cop.cube.moves.u.top = cop.target
def mayRight (cop : CuOnPa) : Prop :=
cop.coords = (-1, 0) → cop.cube.moves.r.top = cop.target
def mayDown (cop : CuOnPa) : Prop :=
cop.coords = (0, 1) → cop.cube.moves.d.top = cop.target
def goLeft (cop : CuOnPa) (h : cop.mayLeft) : CuOnPa :=
let newCoords := (cop.coords.fst - 1, cop.coords.snd)
let newCube := cop.cube.moves.l
sorry
def goUp (cop : CuOnPa) (h : cop.mayUp) : CuOnPa := sorry
def goRight (cop : CuOnPa) (h : cop.mayRight) : CuOnPa := sorry
def goDown (cop : CuOnPa) (h : cop.mayDown) : CuOnPa := sorry
Then I'd say that a path would be a sequence of applications of those goUp, ...
functions and a solution would be a path that would take me to the winning state. But I got stuck because of the hypotheses h
that they need. How'd you formalize such path?
Arthur Paulino (Jan 29 2022 at 14:59):
More generally, how to formalize a path that's valid?
Arthur Paulino (Jan 29 2022 at 15:00):
(I think I saw the solution just by talking it out :open_mouth: )
Arthur Paulino (Jan 29 2022 at 15:04):
The issue is that I'm constraining consistency in my structure. Instead, I should allow the instantiation of invalid states have validity check as a prop, then require that every state in the path obeys to it
Stuart Presnell (Jan 29 2022 at 15:13):
So you could (lazily) build a tree with the starting state as root and the available moves as branches, and then search the tree for a path to a solved state, pruning branches that correspond to invalid moves.
Last updated: Dec 20 2023 at 11:08 UTC