#!/usr/bin/env runghc
i=9;e=i+2;main=interact$u 0 s;f 'o'='-';f '-'='o';r=(`rem`m).(2469*);v=fromEnum
c=map((\(y,x)->l(x+1)$y+1).(`quotRem`i).fst)$filter((>m).(*2).snd)$zip[0..i^2-1]
 $ iterate r 1;a p g=let(x,y:z)=splitAt p g in x++f y:z;j=length c;k=' ':take i
 ['a'..]++"\n";n p=let(y,x)=quotRem p e;r z=[z-1..z+1]in l<$>r x<*>r y;m=65521
b z d f=if z>0&&z<=i then f z else d;s=(foldr1(.)$h<$>take j c)t;l x y=x+y*e;
t=let z=quot e 2 in a(l z z)$replicate(e^2)'-';h=foldr1(.).map a.n;o _""=k++"> "
o z g=let(r,t)=splitAt e g in r++b z""((' ':).show)++'\n':o(z+1)t;u _""_="X\n"
u c g ~(x:y:_:z)="\ESCc"++show(c,j)++"\n"++k++o 0 g++if g==t then(if c<=j then
 "Wow!!\n"else"Good!\n")else b(v x-96)"X\n"$flip(u$c+1)z.b(v y-48)"".((`h`g).).l
{-
Author: Ose Pedro, https://github.com/OsePedro
Category: prelude-10-80

================================================================================
                                HOW TO PLAY
================================================================================

The goal of the game is to flip 3*3 blocks of dots 'o' and dashes '-' until the
board contains exactly one dot, in the middle (if you try an even board size
(see below), you can see where the target board's "middle" dot should be by
loading this file in GHCi and running "putStrLn $ o 0 t"). You pick a block by
typing its central coordinates in the format <column letter><row number><enter>.
E.g. given the following 5*5 board:

-----
-ooo- 1
-o-o- 2
-ooo- 3
-----
 abc

you could win the game in one move by flipping the middle block. To do this,
you'd type "b2" and <enter>, giving:

-----
----- 1
--o-- 2
----- 3
-----
 abc

Be careful when you type; the game will print "X" and terminate if you type
invalid input (sorry!). Backspace works though, so you can at least change what
you've typed before you press <enter>.

Score
=====

The numbers displayed at the top of the screen are the number of moves you've
made and the minimum number of moves required to win the game. Finishing in any
number of moves is worthy of celebration, but finishing in the minimum number of
moves is an achievement that you can be proud of!

Board Size
==========

You can change the board size by changing the i variable (at the start of line
2) to any value in [2..9]. This controls the size of the board's interior.

Random Seed
===========

You can alter the starting board by changing the pseudorandom number generator
seed. This is the second argument of the call to "iterate", at the start of line
4.

Conjectures
===========

I'm pretty sure that you can't win in fewer moves than the displayed "minimum
number of moves", but I haven't proven this. If anyone thinks of a proof, or
finds a counterargument, do let me know! FYI the starting board is generated by
making a random decision about whether or not to flip the 3*3 block around each
interior point.

I'd also be very interested in knowing if anyone manages to reduce the board to
a single dot that is _not_ in the middle. Again, I'm reasonably sure that this
is impossible (note that the game will not treat this as a win if it happens),
but I haven't proven it.


================================================================================
                                DOCUMENTATION
================================================================================

This is an alphabetical listing of all of the functions and constants that I
wrote:

a :: Int -> String -> String
  *Applies* f to the Char at the given index (i.e. it flips that symbol).

b :: Int -> a -> (Int -> a) -> a
  A *Branch* function. (b z d f) returns (f z) if z is in [1..i], otherwise it
  returns d.

c :: [Int]
  Pseudorandom linearised *Coordinates* in the board's interior.

e :: Int
  The width/height of the board's *Exterior*, i.e. i+2.

f :: Char -> Char
  *Flips* 'o' to '-' and vice-versa.

h :: Int -> String -> String
  Flips the *neighbourHood* of the given linearised point.

i :: Int
  The width/height of the board's *Interior*. Must be in [2..9].

j :: Int
  The number of times the target board is *Jumbled* by randomly flipping
  neighbourhoods, in order to produce the start board.

k :: String
  The *Keys* that you should type to pick a column.

l :: Int -> Int -> Int
  (l x y) *Linearises* 2D coordinate (x,y). I.e. it turns it into an index in
  [0..e^2-1]. x and y must be in [0..e-1].

m :: Int
  LCG (linear congruential generator) *Modulus*.

n :: Int -> [Int]
  Maps a linearised point to its 3*3 *Neighbourhood*.

o :: Int -> String -> String
  Formats the given board for *Output*. The Int is the index of the String's
  initial row.

r :: Int -> Int
  Returns the next number in an LCG *pseudoRandom* sequence.

s :: String
  The *Start* board.

t :: String
  The *Target* board. This is not necessarily the only solution, but it is a
  known solution.

u :: Int -> String -> String -> String
  The *User* input processing loop. (u moveCount gameBoard input) reads the
  first two characters and '\n' character from input, interprets the first two
  characters as coordinates, flips the neighbourhood of the coordinates and
  loops until there's one dot left, or until you type invalid input.

v :: Char -> Int
  The ASCII *Value* of a Char.
-}
