HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Lightweight TicTacToe in F#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
lightweighttictactoestackoverflow

Problem

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

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] 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 ->

Solution

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.None


In 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.None
test.[2, 1] <- Cross
printfn "%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 readable

let placeMark (x, y) mark (_, grid : Mark[,]) =
    let res = Array2D.copy grid
    res.[y, x] <- mark
    (checkOutcome res, res)

Code Snippets

let emptyGrid = (None, Array2D.init 3 3 (fun _ _ -> Mark.None))
let emptyGrid = None, Array2D.create 3 3 Mark.None
let test = Array2D.create 3 3 Mark.None
test.[2, 1] <- Cross
printfn "%A" test

[[None; None; None]
 [None; None; None]
 [None; Cross; None]]
let rotateCw (grid : 'a[,]) = Array2D.init 3 3 (fun y x -> grid.[2 - x, y])
let placeMark (x, y) mark (_, grid : Mark[,]) =
    let res = Array2D.copy grid
    res.[y, x] <- mark
    (checkOutcome res, res)

Context

StackExchange Code Review Q#79326, answer score: 2

Revisions (0)

No revisions yet.