Inspired by this question, I went on with the following version. My goal was to focus on pure functions, to avoid mutable variables and to be strictly functional.
I have ignored the computer player part - as it will not change the overall design.
An issue may be that an unchanged player survives from one game state to the next. That's maybe not a good idea, but will it work here because game states are not used anywhere else?
module BattleShipGame open System let boardSize = 10 type Direction = Horz | Vert type ShipType = | AircraftCarrier | BattleShip | Frigate | Submarine | Minesweeper type Point(x : int, y : int) = struct member this.X = x member this.Y = y end type Ship(position : Point, shipType : ShipType, direction : Direction, hits : Point list) = member this.Position = position member this.Type = shipType member this.Direction = direction member this.Hits = hits member this.Length = match this.Type with | AircraftCarrier -> 5 | BattleShip -> 4 | Frigate | Submarine -> 3 | Minesweeper -> 2 member this.IsFinished = this.Hits.Length >= this.Length member this.HitTest(pos : Point) = let offset, dirValid = if this.Direction = Horz then pos.X - this.Position.X, pos.Y = this.Position.Y else pos.Y - this.Position.Y, pos.X = this.Position.X if not dirValid then false else offset >= 0 && offset < this.Length static member Copy(ship: Ship, hit : Point) = Ship(ship.Position, ship.Type, ship.Direction, hit :: ship.Hits) type Player(name : string, ships : Ship list, enemyHits : Point list) = member this.Name = name member this.Ships = ships member this.EnemyHits = enemyHits; member this.IsFinished = (this.Ships |> List.tryFind (fun sh -> sh.IsFinished = false) ).IsNone member this.Print() = let shipHits = this.Ships |> List.collect (fun sh -> sh.Hits) printf "y\x" for x in 1 .. boardSize do printf " %d " x printfn "" for y in 1 .. boardSize do for x in 0 .. boardSize do let hit = shipHits |> List.tryFind (fun pt -> pt.X = x && pt.Y = y) if x = 0 && y > 0 then printf "%3d" y elif hit.IsSome then printf " X " else let pt = this.EnemyHits |> List.tryFind (fun pt -> pt.X = x && pt.Y = y) if pt.IsSome then printf " 0 " else printf " "; Console.WriteLine() static member Copy(player : Player, hittedShip : Ship option, hit : Point) = if hittedShip.IsSome then let shipList = Ship.Copy(hittedShip.Value, hit) :: player.Ships |> List.where (fun sh -> sh <> hittedShip.Value) Player(player.Name, shipList, player.EnemyHits) else Player(player.Name, player.Ships, hit :: player.EnemyHits) // The current state of the game type GameState(currentPlayer : Player, otherPlayer : Player) = member this.CurrentPlayer = currentPlayer member this.OtherPlayer = otherPlayer member this.IsFinished = currentPlayer.IsFinished || otherPlayer.IsFinished // Prompts for a point in the board coordinate system x = horz, y = vert let getUserTarget() = let rec gut() = Console.Write("Enter target [x,y]:") let input = Console.ReadLine().Split(',') if input.Length <> 2 then gut() else let mutable x = 0 let mutable y = 0 if Int32.TryParse(input., &x) && Int32.TryParse(input., &y) then if x < 1 || x > boardSize || y < 1 || y > boardSize then gut() else Point(x, y) else gut() gut() // Prints name of the current player and the board of the other player (which is the current players view of state) let printBoard (state : GameState) = Console.Clear() printfn "Current Player: %s:" state.CurrentPlayer.Name state.OtherPlayer.Print() // The "main" function which is repeatedly called by Seq.unfold below in main let getState (state : GameState) = if state.IsFinished then printBoard state None else printBoard state let target = getUserTarget() let hittest = state.OtherPlayer.Ships |> List.tryFind (fun ship -> ship.HitTest target) if hittest.IsSome then Some((state, GameState(state.CurrentPlayer, Player.Copy(state.OtherPlayer, hittest, target)))) else Some((state, GameState(Player.Copy(state.OtherPlayer, None, target), state.CurrentPlayer))) let initPlayer name = // TODO: change to user input loop including all ship types if name = "Hillary" then let ships = [ Ship(Point(1, 1), AircraftCarrier, Horz, ); Ship(Point(5, 3), BattleShip, Vert, ); Ship(Point(8, 9), Frigate, Horz, ) ] Player(name, ships, ) else let ships = [ Ship(Point(1, 1), AircraftCarrier, Horz, ); Ship(Point(5, 3), BattleShip, Vert, ); Ship(Point(8, 9), Frigate, Horz, ) ] Player(name, ships, ) [<EntryPoint>] let main argv = // Creates a sequence of game states of which the last holds the winner as the CurrentPlayer let result = Seq.unfold (fun state -> getState state) (GameState(initPlayer("Hillary"), initPlayer("Donald"))) result |> Seq.last |> fun state -> (printfn "\nWinner: %s" state.CurrentPlayer.Name) Console.WriteLine("END") Console.ReadLine() |> ignore 0