This is more of a 'model' of TicTacToe, as you'll see it's not "gameified" in the sense that it will allow for inputs and outputs nor will it record any "active" state of the game but those things are trivial to implement.
I'm cheating a little bit on the functional paradigm in my function to rotate the grid, but this is on purpose, it's just easier to rotate the grid by mutation than by going pure functional style, in the end, the state is only manipulated in a local function anyhow so I think it's neglible.
Also there are many times where I have ignored optimizations over conciseness. One might imagine that this would not be an area where optimal performance is key so I have prioritized getting as concise and readable code as possible.
module TicTacToe
type Mark = None | Cross | Nought
type Outcome = None | NoughtWon | CrossWon | Draw
let emptyGrid = (None, Array2D.init 3 3 (fun _ _ -> Mark.None))
let flatten (x: 'a [,]) = x |> Seq.cast<'a>
let rotateCw (grid: 'a [,]) =
let out = Array2D.zeroCreate 3 3
for y in 0..2 do
for x in 0..2 do
out.[x,y] <- grid.[0 + y, 2 - x]
out
let checkOutcome grid =
let checkSeg seg =
seq { for i in 0..2 do
let count =
seg i |> Array.sumBy
(function Cross -> 1 | Nought -> 2 | _ -> 0)
if count = 6 || count = 3 then
yield (if count = 6 then NoughtWon else CrossWon) }
|> Seq.tryFind ((<>)None)
let vertical =
let rot = grid |> rotateCw
(fun x -> rot.[x, 0..2])
let horizontal = (fun y -> grid.[0..2, y])
let diagonal (m:_[,]) = (fun _ -> [| for i in 0..2 -> m.[i,i] |])
let diagonal2 = grid |> rotateCw |> diagonal
let checks =
[checkSeg vertical; checkSeg horizontal;
checkSeg (diagonal grid); checkSeg diagonal2]
if checks |> List.exists (Option.isSome) then
checks |> List.find (Option.isSome) |> Option.get
elif grid |> flatten |> Seq.forall ((<>)Mark.None) then Draw
else None
let placeMark pos mark (_, grid: Mark [,]) =
let res =
Array2D.init 3 3 (fun x y ->
if (x,y) = pos then mark else grid.[x,y])
(checkOutcome res, res)
if checks |> List.exists ...
withchecks |> List.tryFind
. TherotateCw
function also has an obvious improvement to useArray2D.init
\$\endgroup\$