2

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

enter image description here

Figure 2

enter image description here

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?

Foxy
  • 980
  • 8
  • 17
  • It looks to me like you're not consistent with whether you're indexing by `x` or `y` first (e.g. you have an `Array2D.set` with `x` first, `Array2D.get` with `y` first, different `Array2D.get` with `(fst newPos)` first, which would be `x`). – kvb Jun 25 '19 at 14:52
  • Furthermore, your coordinate conversion is treating the letter as the x coordinate, but that's not how your map looks... – kvb Jun 25 '19 at 15:08
  • There is indeed a funny management of the coordinates. I occasionally exchanged the arguments x and y for reasons of ease instead of exchanging all the x and y of an expression. I'm fixing that, maybe I'll find some bugs. – Foxy Jun 25 '19 at 15:35

0 Answers0