@@ -20,6 +20,7 @@ module Position exposing
20
20
It also includes operations on other types that needs the Position, to avoid circular dependencies.
21
21
22
22
There's a lot going on in here:
23
+
23
24
- the whole game of chess
24
25
- representing moves as strings
25
26
- rudimentary "AI"
@@ -29,6 +30,7 @@ The API exposed by this module and its do-everything nature have a lot of room f
29
30
The state of the game is represented by the piece positions, the move history, and the current player.
30
31
Since we have to frequently iterate over the History to figure things out if things like castling are possible,
31
32
this format is pretty inefficient. So far, this only starts to appear when evaluating moves for the AI.
33
+
32
34
-}
33
35
34
36
import Array
@@ -74,12 +76,16 @@ initial =
74
76
}
75
77
76
78
79
+ {- | Get the piece at the given square
80
+ -}
77
81
get : Position -> Square -> Maybe Piece
78
82
get { board } { rank, file } =
79
83
Array2D . get rank file board
80
84
|> Maybe . withDefault Nothing
81
85
82
86
87
+ {- | Find the squares with pieces of the same kind belonging to the same player
88
+ -}
83
89
findPieces : Piece -> Position -> List Square
84
90
findPieces piece position =
85
91
let
@@ -97,6 +103,10 @@ findPieces piece position =
97
103
List . filter ( hasPiece position) occupiedSquares
98
104
99
105
106
+ {- | Used to test if a potential ply is can be applied to the current Position.
107
+ Intended to be used when parsing plies from text, as the parser is only looking
108
+ at syntactic correctness.
109
+ -}
100
110
isPlyValid : Ply -> Position -> Result String ()
101
111
isPlyValid ply position =
102
112
let
@@ -116,6 +126,9 @@ isPlyValid ply position =
116
126
Err " Illegal ply"
117
127
118
128
129
+ {- | If the current player has a pawn that can move to the provided square,
130
+ get the square that it is on.
131
+ -}
119
132
findPawnThatCanMoveToSquare : Square -> Position -> Maybe Square
120
133
findPawnThatCanMoveToSquare square position =
121
134
let
@@ -239,6 +252,8 @@ areSquaresUnoccupied position squares =
239
252
List . map ( get position) squares |> List . filterMap identity |> List . isEmpty
240
253
241
254
255
+ {- | Get every square that the current player has a piece on
256
+ -}
242
257
getSquaresOccupiedByCurrentPlayer : Position -> EverySet Square
243
258
getSquaresOccupiedByCurrentPlayer position =
244
259
getSquaresOccupiedByPlayer position. playerToMove position
@@ -355,13 +370,18 @@ isPlayerInCheck player position =
355
370
EverySet . member sq otherPlayerPossibleMoves
356
371
357
372
373
+ {- | Determine if the game has ended by checkmate.
374
+ -}
358
375
isCurrentPlayerInCheckMate : Position -> Bool
359
376
isCurrentPlayerInCheckMate position =
360
377
isPlayerInCheck position. playerToMove position
361
378
&& EverySet . empty
362
379
== generateAllMovesForCurrentPlayerWithoutCheck position
363
380
364
381
382
+ {- | Determine if the game has ended by stalemate. A stalemate occurs when the current player is not in check,
383
+ but they have no legal moves.
384
+ -}
365
385
isStalemate : Position -> Bool
366
386
isStalemate position =
367
387
not ( isPlayerInCheck position. playerToMove position)
@@ -409,6 +429,9 @@ wouldMoveLeavePlayerInCheck player position ply =
409
429
isPlayerInCheck player pos
410
430
411
431
432
+ {- | Get all of the plies that could be chosen by the current player that would not
433
+ leave them in check.
434
+ -}
412
435
getPossibleMovesForCurrentPlayerWithoutCheck : Position -> Square -> EverySet Ply
413
436
getPossibleMovesForCurrentPlayerWithoutCheck position square =
414
437
getPossibleMoves True position. playerToMove position square
@@ -750,13 +773,7 @@ getRows board =
750
773
{- TODO rename this -}
751
774
let
752
775
maybeToBool r =
753
- -- TODO i'm sure there's a smoother way to do this
754
- case r of
755
- Just _ ->
756
- True
757
-
758
- Nothing ->
759
- False
776
+ r /= Nothing
760
777
in
761
778
List . range 0 ( Array2D . rows board)
762
779
|> List . map ( \ i -> Array2D . getRow i board)
@@ -774,6 +791,50 @@ canPieceMoveBetweenSquares position start end =
774
791
|> List . member end
775
792
776
793
794
+ {- | Represent the move history as a string. The moves will be represented as they would be in a
795
+ Portable Game Notation (PGN) file.
796
+ -}
797
+ toPgn : Position -> String
798
+ toPgn position =
799
+ List . foldl toPgnHelp ( initial, [] ) ( History . toList position. history)
800
+ |> Tuple . second
801
+ |> List . reverse
802
+ |> String . join " "
803
+
804
+
805
+ toPgnHelp : Ply -> ( Position , List String ) -> ( Position , List String )
806
+ toPgnHelp ply ( position, strings ) =
807
+ let
808
+ plyText =
809
+ plyToString position ply
810
+
811
+ nextPosition =
812
+ makeMove position ply
813
+ |> Maybe . withDefault initial
814
+
815
+ moveNumber =
816
+ if position. playerToMove == Player . White then
817
+ History . moveNumber nextPosition. history
818
+ |> String . fromInt
819
+ |> ( \ i -> i ++ " . " )
820
+
821
+ else
822
+ " "
823
+ in
824
+ ( nextPosition, ( moveNumber ++ plyText) :: strings )
825
+
826
+
827
+ {- | Turn a ply into a string. This is surprisingly complicated because we want to display as little information as
828
+ possible to unambiguously refer to it.
829
+
830
+ Suppose we have a knight moving to the f3 square:
831
+
832
+ - if there's only one knight that can move there, we want to output "Nf3"
833
+ - if there's another Knight that can move to f3, we want to add either the file or the rank to disambiguate
834
+ For example, "Naf3" or "N1f3"
835
+ - if we've promoted some pawns to Knights, we might have to specify both the rank and the file: "Na1f3"
836
+
837
+ -}
777
838
plyToString : Position -> Ply -> String
778
839
plyToString position ply =
779
840
case ply of
@@ -801,7 +862,7 @@ plyToString position ply =
801
862
context =
802
863
case casesToDisambiguate of
803
864
[] ->
804
- " " |> Debug . log " shouldnt happen"
865
+ " " |> Debug . todo " shouldnt happen"
805
866
806
867
[ _ ] ->
807
868
" "
@@ -848,36 +909,15 @@ plyToString position ply =
848
909
" O-O"
849
910
850
911
851
- toPgnHelp : Ply -> ( Position , List String ) -> ( Position , List String )
852
- toPgnHelp ply ( position, strings ) =
853
- let
854
- plyText =
855
- plyToString position ply
856
-
857
- nextPosition =
858
- makeMove position ply
859
- |> Maybe . withDefault initial
860
-
861
- moveNumber =
862
- if position. playerToMove == Player . White then
863
- History . moveNumber nextPosition. history
864
- |> String . fromInt
865
- |> ( \ i -> i ++ " . " )
866
-
867
- else
868
- " "
869
- in
870
- ( nextPosition, ( moveNumber ++ plyText) :: strings )
871
912
872
-
873
- toPgn : Position -> String
874
- toPgn position =
875
- List . foldl toPgnHelp ( initial, [] ) ( History . toList position. history)
876
- |> Tuple . second
877
- |> List . reverse
878
- |> String . join " "
913
+ -- AI Functions
879
914
880
915
916
+ {- | Pick a ply for the current player and apply it. Plies are ranked based on a bunch of heuristics that assign
917
+ a score to each one, and one is picked by the seed.
918
+ One day, the seed may be used for randomly selecting moves, but for now it serves like an index.
919
+ If the seed is 0, the best move will always be picked.
920
+ -}
881
921
aiMove : Position -> Int -> Position
882
922
aiMove position seed =
883
923
let
0 commit comments