I would like to create the game Stratego in an F# console application. The current display (see figure 1) does not show any wrong position of pawns, however they seem to be positioned differently (see figure 2, after some moves). The program detects obstacles and bad movements well, but I don't understand why the pawns are not correctly placed "in memory", which is quite annoying... I suspect the problem comes directly from this function, but I can't solve it:
let isWater ((x, y) : Coordinate) =
((x = 2 || x = 3) || (x = 6 || x = 7)) && (y = 4 || y = 5)
let defaultBoardGame =
Array2D.init 10 10
(fun y x ->
if y <= 3 then Occuped (unknownPawn (x, y))
elif isWater (x, y) then Water
elif y = 4 || y = 5 then Free
else (Occuped (pawn Allied Kind.Marshal (x, y) [])))
Figure 1
Figure 2
Indeed, after moving characters from the blue side to the grey area, they turned red on the display. However, the pawn has not changed a priori.
Here is the code:
module Game
open System
let gameTimeCounter = ref 0
type Camp = Allied | Ennemy
type Kind
= Bomb = 0
| Spy = 1
| Scout = 2
| Miner = 3
| Sergeant = 4
| Lieutenant = 5
| Captain = 6
| Major = 7
| Colonel = 8
| General = 9
| Marshal = 10
| Flag = 11
| Unknown = 12
type Coordinate = int * int
exception ThereIsAnObstacle of Coordinate
exception PawnCantMove of Coordinate
exception BadMove of Coordinate
[<StructuredFormatDisplay("{kind}")>]
type Pawn =
{ camp : Camp
; kind : Kind
; pos : Coordinate
; state : Coordinate list }
let unknownPawn pos =
{ camp = Ennemy
; kind = Kind.Unknown
; pos = pos
; state = [] }
let pawn camp kind pos state =
{ camp = camp
; kind = kind
; pos = pos
; state = state }
type Case
= Free
| Water
| Occuped of Pawn
type Board = Case [,]
let isWater ((x, y) : Coordinate) = ((x = 2 || x = 3) || (x = 6 || x = 7)) && (y = 4 || y = 5)
let defaultBoardGame =
Array2D.init 10 10
(fun y x ->
if y <= 3 then Occuped (unknownPawn (x, y))
elif isWater (x, y) then Water
elif y = 4 || y = 5 then Free
else (Occuped (pawn Allied Kind.Marshal (x, y) [])))
let boardInsert (board : Board) (pawn : Pawn) ((x, y) : Coordinate) =
let mutable board = board
Array2D.set board x y (Occuped {pawn with pos = (x, y); state = pawn.state @ [pawn.pos]})
Array2D.set board (fst pawn.pos) (snd pawn.pos) Free
board
let boardRemove (board : Board) ((x, y) : Coordinate) =
let mutable board = board
Array2D.set board x y Free
board
let move (board : Board) (pawn : Pawn) (newPos : Coordinate) : Board =
let envisagedCase = Array2D.get board (fst newPos) (snd newPos)
// Check the simple move
let isCorrectMove =
match pawn.pos, newPos with
(x, y), (x', y') ->
// Check the proximity
((x + 1 = x' || x - 1 = x' || y + 1 = y' || y - 1 = y')
&& (x = x' || y = y')) && (x <= 9 && y <= 9)
// Check the obstacles
let isObstacle =
match envisagedCase with
| Free -> false
| Occuped _ | Water -> true
// Check if the pawn can move
let pawnCanMove = not (pawn.kind = Kind.Bomb || pawn.kind = Kind.Flag)
if not isCorrectMove then raise (BadMove newPos)
if isObstacle then raise (ThereIsAnObstacle newPos)
if not pawnCanMove then raise (PawnCantMove newPos)
boardInsert board pawn newPos
let displayGridBox =
System.Console.Clear ()
Array2D.zeroCreate 11 11 |> Array2D.iteri
(fun y x _ ->
if x = 0 || y = 0
then
if x = 0 then System.Console.BackgroundColor <- System.ConsoleColor.DarkYellow; System.Console.ForegroundColor <- System.ConsoleColor.White; printf "%c" (let c = (char (63 + y + x + 1)) in if c = '@' then ' ' else c)
elif y = 0 then System.Console.BackgroundColor <- System.ConsoleColor.DarkYellow; System.Console.ForegroundColor <- System.ConsoleColor.White; (if x = 10 then printf " %d" x else printf " %d " x)
else System.Console.ResetColor ()
else System.Console.ResetColor ()
if x = 10 then printfn "")
let pawnAtPos (board : Board) ((x, y) : Coordinate) : Pawn =
let case = Array2D.get board y x
match case with
| Occuped pawn -> pawn
| _ -> failwith "Nobody is at this position!"
let displayBoard (board : Board) =
displayGridBox
System.Console.SetCursorPosition(0, 1)
board |> Array2D.iteri
(fun y x case ->
System.Console.SetCursorPosition(Console.CursorLeft + 1, Console.CursorTop)
if y >= 6 && x <= 9 then System.Console.BackgroundColor <- System.ConsoleColor.DarkBlue
elif isWater (x, y) then System.Console.BackgroundColor <- System.ConsoleColor.Cyan
elif y >= 4 && x <= 9 then System.Console.BackgroundColor <- System.ConsoleColor.DarkGray
elif y >= 0 && x <= 9 then System.Console.BackgroundColor <- System.ConsoleColor.DarkRed
else System.Console.ResetColor ()
match case with
| Free | Water -> printf " "
| Occuped pawn ->
match pawn.camp with
| Allied -> System.Console.ForegroundColor <- System.ConsoleColor.Blue; printf " X "
| Ennemy -> System.Console.ForegroundColor <- System.ConsoleColor.Red; printf " X "
System.Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop)
if x = 9 then printfn "")
System.Console.ResetColor ()
let numberOfFlag = 1
let numberOfMarshal = 1
let numberOfGeneral = 1
let numberOfColonel = 2
let numberOfMajor = 3
let numberOfCaptain = 4
let numberOfLieutenant = 4
let numberOfSergeant = 4
let numberOfMiner = 5
let numberOfScout = 8
let numberOfSpy = 1
let numberOfBomb = 6
let say s = fun (second : int option) ->
let second = if second.IsSome then second.Value else 0
System.Console.SetCursorPosition(47, 7 + second)
System.Console.ForegroundColor <- System.ConsoleColor.Yellow
printfn "%s" s
System.Console.ResetColor ()
System.Console.SetCursorPosition(0, 11)
let fight (board : Board) (pawn1 : Pawn) (pawn2 : Pawn) =
say (sprintf "%A vs %A" pawn1.kind pawn2.kind) (Some -1)
if pawn1.kind > pawn2.kind
then say (sprintf "The %A %A win!" pawn1.camp pawn1.kind) None
boardRemove board pawn2.pos
elif pawn1.kind < pawn2.kind
then say (sprintf "The %A %A win!" pawn2.camp pawn2.kind) None
boardRemove board pawn1.pos
else say "Fight equality!" None
boardRemove (boardRemove board pawn1.pos) pawn2.pos
let coordinateConvert ((x, y) : char * int) : Coordinate =
let alphabet = "ABCDEFGHIJ"
alphabet.IndexOf x, y - 1
let corrdinateConvertToString ((x, y) : Coordinate) =
sprintf "%c%d" (char (65 + x)) (y + 1)
let tryGuessEnnemyBomb (board : Board) =
0
module Command =
open FParsec
type Command
= Move of Coordinate * Coordinate
| Ask of Coordinate * Coordinate
let coordinate = anyChar .>>. pint32
let pmove =
pipe2
(spaces >>? coordinate)
(spaces1 >>? coordinate)
(fun p1 p2 -> (coordinateConvert p1, coordinateConvert p2) |> Move)
let pask =
pipe3
(spaces >>? pstring "ask")
(spaces1 >>? coordinate)
(spaces1 >>? coordinate)
(fun _ p1 p2 -> (coordinateConvert p1, coordinateConvert p2) |> Ask)
let commands =
choice [pmove; pask]
let parse str = run commands str
open Command
open FParsec.CharParsers
[<EntryPointAttribute>]
let main _ =
let mutable board = defaultBoardGame
while true do
displayBoard board
say (sprintf "Turn n°%d" (!gameTimeCounter + 1)) None
if !gameTimeCounter % 2 = 0
then say "Red to play " (Some 1)
else say "Blue to play" (Some 1)
printf "> "
let input = System.Console.ReadLine ()
if !gameTimeCounter % 2 = 0
then try match parse input with
| Success (result, _, _) ->
match result with
| Move (p1, p2) -> board <- move board (pawnAtPos board p1) p2
| Ask (p1, p2) -> () // Next step to do
| Failure (msg, _, _) -> say msg (Some 10)
with
| BadMove p -> say (sprintf "Can't move to %s" (corrdinateConvertToString p)) (Some 2)
| ThereIsAnObstacle p -> say (sprintf "There is an obstacle at %s" (corrdinateConvertToString p)) (Some 2)
| PawnCantMove p -> say (sprintf "The pawn at %s is not allowed to move" (corrdinateConvertToString p)) (Some 2)
gameTimeCounter := !gameTimeCounter + 1
0
Forgive the few uses of mutability or exception, I'm just trying to solve this problem for the moment, waiting for make the program better.
Could you help me?