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 TicTacToetype Mark = None | Cross | Nought type Outcome = None | NoughtWon | CrossWon | Drawlet 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]    outlet 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 Nonelet 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)
Lightweight TicTacToe in F#
functional programming;f#;tic tac toe
let emptyGrid = (None, Array2D.init 3 3 (fun _ _ -> Mark.None))This can be simplified using Array2D.create:let emptyGrid = None, Array2D.create 3 3 Mark.NoneIn rotateCw and placeMark, x is used as the first index and y as the second. This is confusing, as the usual order is row then column. For instance,let test = Array2D.create 3 3 Mark.Nonetest.[2, 1] <- Crossprintfn %A test[[None; None; None] [None; None; None] [None; Cross; None]]rotateCw can be simplified (assumes the indexing changes in the previous paragraph)let rotateCw (grid : 'a[,]) = Array2D.init 3 3 (fun y x -> grid.[2 - x, y])I find this version of placeMark a bit more readablelet placeMark (x, y) mark (_, grid : Mark[,]) =    let res = Array2D.copy grid    res.[y, x] <- mark    (checkOutcome res, res)