patternMinor
Grand Chess domain model and helper functions
Viewed 0 times
helpergrandchessmodelandfunctionsdomain
Problem
So I am trying to write, essentially from a blank slate, a program that plays Grand Chess. In short, it is a chess variant that is played with two extra pieces, on a 10x10 board, no castling, and optional promotion on the 8th and 9th ranks, mandatory on the last. Finally, you may not promote to a piece unless it has been captured (no 2 queens, for example.) Rest of the rules are the same as chess.
These functions will, hopefully, be the basis for both a UI to play the game and a simple engine to play against.
I have somewhat implemented the kind-of-complicated promotion rules, but I am not too sure about the implementation. It should only generate legal moves. The only legality check is checking whether the move ends with the King in check. I am maintaining that you can only promote to a captured piece by maintaining a piece list in the Position Type. this will probably generate tons of garbage, so any performance improvements are welcome.
Edit: The code below is a restructuring / renaming / refactoring of the original code posted here. I did so since there were no answers nor comments and I kept working on the code anyway. The algorithms used and the main logic are the same, however.
Without further ado:
Types.fs
```
namespace Stamma
type Color =
| White
| Black
member x.Opp =
match x with
| White -> Black
| Black -> White
(*
This type is replaced by simple int * int tuples throughout
type Vector =
{ Forward : int // changes per player
East : int } // to the Right of the white player.
*)
type Piece =
| King
| Queen
| Marshal
| Cardinal
| Bishop
| Knight
| Rook
| Pawn
type MoveType =
| Move
| Capture
| Promotion of Piece
| CapAndPromotion of Piece
type Coordinates =
{ Rank : int
File : int }
[]
type Field =
| Empty
| Piece of Piece * Color
type Position =
{ Board : Map
EnPassant : Coordinates
Turn : Color
KingW
These functions will, hopefully, be the basis for both a UI to play the game and a simple engine to play against.
I have somewhat implemented the kind-of-complicated promotion rules, but I am not too sure about the implementation. It should only generate legal moves. The only legality check is checking whether the move ends with the King in check. I am maintaining that you can only promote to a captured piece by maintaining a piece list in the Position Type. this will probably generate tons of garbage, so any performance improvements are welcome.
Edit: The code below is a restructuring / renaming / refactoring of the original code posted here. I did so since there were no answers nor comments and I kept working on the code anyway. The algorithms used and the main logic are the same, however.
Without further ado:
Types.fs
```
namespace Stamma
type Color =
| White
| Black
member x.Opp =
match x with
| White -> Black
| Black -> White
(*
This type is replaced by simple int * int tuples throughout
type Vector =
{ Forward : int // changes per player
East : int } // to the Right of the white player.
*)
type Piece =
| King
| Queen
| Marshal
| Cardinal
| Bishop
| Knight
| Rook
| Pawn
type MoveType =
| Move
| Capture
| Promotion of Piece
| CapAndPromotion of Piece
type Coordinates =
{ Rank : int
File : int }
[]
type Field =
| Empty
| Piece of Piece * Color
type Position =
{ Board : Map
EnPassant : Coordinates
Turn : Color
KingW
Solution
In
The same thing applies to
Generally, in F# it's preferred to
Just as well:
One more:
Other than that, there's not much I can say. I'm not an expert in F# by any means. You used tail-call recursion everywhere you could, which is good. You use
Rewriting
Note: I haven't yet tested this, you provided no test data, but it should provide the same result.
Basically, we use recursive loops like the functional paradigms expect.
All-in-all this was a very well-written programme, I actually learned a lot from it and it forced me to think functionally for a moment, and helped me remove that
toFen, you don't need sb to be mutable. You're not reassigning it, but are instead calling methods on it, of which mutable does not affect.The same thing applies to
baseArray in ofFenRank. This doesn't need to be mutable, because you're not directly assigning to the array, but are assigning to elements in the array.Generally, in F# it's preferred to
match instead of if, especially with constants:let private applyVector turn start (x, y) =
{ Rank =
start.Rank + ((match turn with
| White -> 1
| _ -> -1)
* x)
File = start.File + y }Just as well:
let private wayfindCore start pos piece reach =
let rec loop start acc reach vector =
match reach with
| 0 -> acc
| _ -> let dist = applyVector pos.Turn start vector
match Map.tryFind dist pos.Board with
| Some(Piece(p, c)) when c <> pos.Turn -> (dist, Capture) :: acc
| Some(Empty) -> loop dist ((dist, Move) :: acc) (reach - 1) vector
| _ -> acc
Piece.vectors piece
|> List.collect (loop start [] reach)
|> List.map (fun (d, m) -> start, d, m)One more:
{ pos with Board =
match (pos.EnPassant, movingPiece) with
| (ep, Pawn) when ep = dist ->
newBoard |> Map.add { Rank = start.Rank
File = dist.File } Empty
| _ -> newBoard
KingWhite =
match (movingPiece, pos.Turn) with
| (King, White) -> dist
| _ -> pos.KingWhite
KingBlack =
match (movingPiece, pos.Turn) with
| (King, Black) -> dist
| _ -> pos.KingBlack
CappedWhite = update pos.CappedWhite Black |> remove (fun x -> promo && x = endPiece)
CappedBlack = update pos.CappedBlack White |> remove (fun x -> promo && x = endPiece)
Turn = pos.Turn.Opp
EnPassant =
match (movingPiece, abs (start.Rank - dist.Rank)) with
| (Pawn, 2) ->
{ Rank = (start.Rank + dist.Rank) / 2
File = start.File }
| _ ->
{ Rank = 0
File = 0 } }Other than that, there's not much I can say. I'm not an expert in F# by any means. You used tail-call recursion everywhere you could, which is good. You use
Option and unions when appropriate, and you used functional paradigms everywhere. The only one I don't like is the mutable esc.Rewriting
toFen as follows should eliminate the need for this mutable variable, and follow functional paradigms as much as possible. (Reduced the number of if statements substantially.)Note: I haven't yet tested this, you provided no test data, but it should provide the same result.
let toFen board =
let sb = System.Text.StringBuilder()
let rec rLoop r acc =
let rec fLoop f acc =
let newAcc =
match Map.find { Rank = r
File = f } board with
| Empty -> acc + 1
| Piece(p, c) ->
match acc with
| v when v > 0 ->
sb.Append acc |> ignore
0
| _ ->
sb.Append(Piece.toChar c p) |> ignore
acc
match f with
| v when v fLoop (f + 1) newAcc
| _ -> newAcc
let newAcc = fLoop 1 acc
let finalAcc =
if newAcc > 0 then
sb.Append newAcc |> ignore
0
elif r > 1 then
sb.Append '/' |> ignore
newAcc
else
newAcc
if r > 1 then
rLoop (r - 1) newAcc
rLoop 10 0
sb.ToString()Basically, we use recursive loops like the functional paradigms expect.
All-in-all this was a very well-written programme, I actually learned a lot from it and it forced me to think functionally for a moment, and helped me remove that
esc mutable variable with a little effort. Very good job! :)Code Snippets
let private applyVector turn start (x, y) =
{ Rank =
start.Rank + ((match turn with
| White -> 1
| _ -> -1)
* x)
File = start.File + y }let private wayfindCore start pos piece reach =
let rec loop start acc reach vector =
match reach with
| 0 -> acc
| _ -> let dist = applyVector pos.Turn start vector
match Map.tryFind dist pos.Board with
| Some(Piece(p, c)) when c <> pos.Turn -> (dist, Capture) :: acc
| Some(Empty) -> loop dist ((dist, Move) :: acc) (reach - 1) vector
| _ -> acc
Piece.vectors piece
|> List.collect (loop start [] reach)
|> List.map (fun (d, m) -> start, d, m){ pos with Board =
match (pos.EnPassant, movingPiece) with
| (ep, Pawn) when ep = dist ->
newBoard |> Map.add { Rank = start.Rank
File = dist.File } Empty
| _ -> newBoard
KingWhite =
match (movingPiece, pos.Turn) with
| (King, White) -> dist
| _ -> pos.KingWhite
KingBlack =
match (movingPiece, pos.Turn) with
| (King, Black) -> dist
| _ -> pos.KingBlack
CappedWhite = update pos.CappedWhite Black |> remove (fun x -> promo && x = endPiece)
CappedBlack = update pos.CappedBlack White |> remove (fun x -> promo && x = endPiece)
Turn = pos.Turn.Opp
EnPassant =
match (movingPiece, abs (start.Rank - dist.Rank)) with
| (Pawn, 2) ->
{ Rank = (start.Rank + dist.Rank) / 2
File = start.File }
| _ ->
{ Rank = 0
File = 0 } }let toFen board =
let sb = System.Text.StringBuilder()
let rec rLoop r acc =
let rec fLoop f acc =
let newAcc =
match Map.find { Rank = r
File = f } board with
| Empty -> acc + 1
| Piece(p, c) ->
match acc with
| v when v > 0 ->
sb.Append acc |> ignore
0
| _ ->
sb.Append(Piece.toChar c p) |> ignore
acc
match f with
| v when v < 10 -> fLoop (f + 1) newAcc
| _ -> newAcc
let newAcc = fLoop 1 acc
let finalAcc =
if newAcc > 0 then
sb.Append newAcc |> ignore
0
elif r > 1 then
sb.Append '/' |> ignore
newAcc
else
newAcc
if r > 1 then
rLoop (r - 1) newAcc
rLoop 10 0
sb.ToString()Context
StackExchange Code Review Q#141425, answer score: 2
Revisions (0)
No revisions yet.