librarygames/lights-out/Lamps.xtl

Lights out's rules, a solver and the board's picture, a library: lights-out.xtl (scripted), play.xtl (the game: a board you click) and the web page all use these. The board is 25 lights, row by row, 1 on. A press toggles a light and its four neighbors: the press matrix, 25 by 25, says which, and a whole board of presses is one product with it, mod 2. A game's state: the moves made, the square a hint shows (0 none), then the 25 lights.

source · imports sv: lib/Svg.xtl

The press matrix

row : Int

value (private) · line 14
row ← ((r̲ange 25) − 1) d̲iv 5

col : Int

value (private) · line 15
col ← ((r̲ange 25) − 1) m̲od 5

ˡp̲resses : (Num a, Truthy a) => Unit -> a

function · line 27

Which lights each press toggles: row i is square i's press, a 1 on every square at most one step away (itself and its four neighbors), all 625 pairs at once by two tables of distances.

      ˡᵒ⁼u̲se< "Lamps"
      5 5 r̲eshape 13 s̲elect ˡᵒp̲resses @
0 0 0 0 0
0 0 1 0 0
0 1 1 1 0
0 0 1 0 0
0 0 0 0 0
ˡp̲resses ← { @ → 0 + 1 ≥ (a̲bs row '− t̲able row) + a̲bs col '− t̲able col }
Used in: ˡa̲fter, ˡs̲olve, P, ok

ˡa̲fter : Int -> Int -> Int

function · line 32

The board after pressing the squares p (a 0/1 vector of 25) on board b: each light flips once for every press that reaches it, mod 2, one inner product for all of them.

ˡa̲fter ← { b p → (b + p '+ '× i̲nner ˡp̲resses @) m̲od 2 }

Solving it

ʰs̲tep : Int -> Int -> Int

function (private) · line 41

One column of Gaussian elimination mod 2 on the augmented matrix m (the press matrix, the board as its last column), rc the next pivot row and the column: a row with a 1 in the column is swapped up, and added (mod 2) to every other row with a 1 there; a column without one is a free press, left 0.

ʰs̲tep ← { m rc →
  r ← f̲irst rc
  c ← 2 s̲elect rc
  (c > 25) ∨ r > 25 ? m
  cand ← w̲here (1 = c s̲elect₂ m) ∧ (r̲ange 25) ≥ r
  0 = t̲ally cand ? m ʰs̲tep r c̲at c + 1
  p ← f̲irst cand
  k ← r̲ange 25
  m ← (k + ((p − r) × k = r) + (r − p) × k = p) s̲elect m
  hit ← 0 + (1 = c s̲elect₂ m) ∧ k ≠ r
  m ← (m + hit '× t̲able r s̲elect m) m̲od 2
  m ʰs̲tep (r + 1) c̲at c + 1
}

ˡs̲olve : Int -> Int

function · line 62

The presses that turn board b off (a 0/1 vector of 25), when there are any: the matrix reduced mod 2, each pivot's press read from the board's column; the free presses (5 by 5 has two) left 0.

      ˡᵒ⁼u̲se< "Lamps"
      b ← 25 r̲eshape 0 0 1 0 0
      0 = '+ r̲/ b ˡᵒa̲fter ˡᵒs̲olve b
1
ˡs̲olve ← { b →
  m ← ((ˡp̲resses @) c̲at₂ b) ʰs̲tep 1 1
  lead ← '{ i → f̲irst (w̲here 1 = 25 t̲ake i s̲elect m) c̲at 0 } e̲ach r̲ange 25
  last ← 26 s̲elect₂ m
  '{ j → '+ r̲/ (lead = j) × last } e̲ach r̲ange 25
}
Used in: ˡh̲int, x

The game

ˡn̲ew : Any a => a -> Int

function · line 74

A new puzzle: some squares pressed at random on a board all off, so it can always be solved (a board left all off gets its middle pressed).

ˡn̲ew ← { x →
  p ← 0 + 1 = r̲oll! 25 r̲eshape 2
  p ← p + (0 = '+ r̲/ p) × 13 = r̲ange 25
  0 0 c̲at (25 r̲eshape 0) ˡa̲fter p
}

ˡm̲oves : a -> a

function · line 81

The moves made.

ˡm̲oves ← { s → f̲irst s }

ˡl̲ights : a -> a

function · line 84

The 25 lights.

ˡl̲ights ← { s → 2 d̲rop s }

ˡp̲ress : Int -> Int -> Int

function · line 87

Square k (1 to 25) pressed: one more move, the hint gone.

ˡp̲ress ← { s k → (1 + ˡm̲oves s) c̲at 0 c̲at (ˡl̲ights s) ˡa̲fter 0 + k = r̲ange 25 }

ˡh̲int : Int -> Int

function · line 90

A hint: the first square of a solution of the board as it is.

ˡh̲int ← { s → (ˡm̲oves s) c̲at (f̲irst (w̲here 1 = ˡs̲olve ˡl̲ights s) c̲at 0) c̲at ˡl̲ights s }
Used in: ˡc̲lick

ˡs̲tatus : (Num a, Num b, Truthy b) => a -> b

function · line 93

0 playing, 1 every light off.

ˡs̲tatus ← { s → 0 + 0 = '+ r̲/ ˡl̲ights s }
Used in: ˡs̲ay, ok

The picture and the clicks

view : Float

value (private) · line 99
view ← 0.0 0.0 2500.0 1000.0

ˡs̲quares : Unit -> Float

function · line 102

The squares as rectangles (x, y, width, height), one row each, 1 to 25.

ˡs̲quares ← { @ → o̲\ (4 c̲at 25) r̲eshape (200.0 + 170 × f̲loat col) c̲at (130.0 + 170 × f̲loat row) c̲at (25 r̲eshape 160.0) c̲at 25 r̲eshape 160.0 }

ˡc̲lick : Int -> Float -> Int

function · line 106

A click at xy: a square presses it; the first button starts a new puzzle, the second shows a hint.

ˡc̲lick ← { s xy →
  b ← (view ˢᵛb̲uttons 2) ˢᵛh̲it xy
  b = 1 ? ˡn̲ew 0
  b = 2 ? ˡh̲int s
  k ← (ˡs̲quares @) ˢᵛh̲it xy
  k > 0 ? s ˡp̲ress k
  s
}
Used in: t, ᵘl̲oop

ˡs̲ay : Num a => a -> Char

function · line 116

What to say: how many lights are on and how many moves, or the win.

ˡs̲ay ← { s →
  1 = ˡs̲tatus s ? @ f̲ormat< "All the lights are out, in {h:c_ount l:m_oves s}!"
  (2 s̲elect s) > 0 ? "Hint: press the square ringed in blue."
  @ f̲ormat< "{'+ r_/ l:l_ights s} lights on, {h:c_ount l:m_oves s}."
}
f̲ormat< expands to
("All the lights are out, in " c̲at (f̲ormat (ʰc̲ount ˡm̲oves s)) c̲at "!")
f̲ormat< expands to
(f̲ormat ('+ r̲/ ˡl̲ights s)) c̲at " lights on, " c̲at (f̲ormat (ʰc̲ount ˡm̲oves s)) c̲at "."

ʰc̲ount : Num a => a -> Char

function (private) · line 122

A count of moves in words: 1 move, 2 moves.

ʰc̲ount ← { n → 1 = n ? "1 move"◆ @ f̲ormat< "{n} moves" }
f̲ormat< expands to
(f̲ormat (n)) c̲at " moves"
Used in: ˡs̲ay

ʰf̲ill : Int -> Char

function (private) · line 124

A light's fill: on yellow, off dark.

ʰf̲ill ← { on → d̲isclose (1 + on) s̲elect "#2b2d42" "#ffd84d" }
Used in: ˡd̲raw

ˡd̲raw : Int -> Char

function · line 128

The picture, as SVG text: the buttons, the 25 lights (the hint's square ringed), and what to say. Every coordinate is computed here.

ˡd̲raw ← { s →
  q ← ˡs̲quares @
  b ← ˡl̲ights s
  bg ← view ˢᵛr̲ect "fill=\"#14213d\""
  lights ← '{ k → (k s̲elect q) ˢᵛr̲ect @ f̲ormat< "class=\"light\" rx=\"18\" fill=\"{h:f_ill k s_elect b}\" stroke=\"#000000\" stroke-width=\"4\"" } e̲ach r̲ange 25
  h ← 2 s̲elect s
  ring ← (h > 0) r̲eplicate '{ k → (((k s̲elect q) + -12 -12 24 24.0)) ˢᵛr̲ect "fill=\"none\" stroke=\"#4cc9f0\" stroke-width=\"14\" rx=\"26\"" } e̲ach 1 m̲ax h
  words ← (1250 450 44.0) ˢᵛw̲ords ˡs̲ay s
  view ˢᵛp̲icture bg c̲at ((view c̲at 0.0) ˢᵛl̲abeled "New puzzle" "Hint") c̲at lights c̲at ring c̲at words
}
f̲ormat< expands to
("class=\"light\" rx=\"18\" fill=\"" c̲at (f̲ormat (ʰf̲ill k s̲elect b)) c̲at "\" stroke=\"#000000\" stroke-width=\"4\"")
Used in: pic, ᵘl̲oop