@@ -30,175 +30,6 @@ public struct Game {
3030 }
3131 }
3232
33- public enum Outcome : Hashable , CustomStringConvertible {
34- case win( PlayerColor )
35- case draw
36-
37- public var description : String {
38- if let color = winColor {
39- return color. isWhite ( ) ? " 1-0 " : " 0-1 "
40- } else {
41- return " 1/2-1/2 "
42- }
43- }
44-
45- public var winColor : PlayerColor ? {
46- guard case let . win( color) = self else { return nil }
47- return color
48- }
49-
50- public var isWin : Bool {
51- if case . win = self { return true } else { return false }
52- }
53-
54- public var isDraw : Bool {
55- return !isWin
56- }
57-
58- public init ? ( _ string: String ) {
59- let stripped = string. split ( separator: " " ) . map ( String . init) . joined ( separator: " " )
60- switch stripped {
61- case " 1-0 " :
62- self = . win( . white)
63- case " 0-1 " :
64- self = . win( . black)
65- case " 1/2-1/2 " :
66- self = . draw
67- default :
68- return nil
69- }
70- }
71-
72- public func value( for playerColor: PlayerColor ) -> Double {
73- return winColor. map ( { $0 == playerColor ? 1 : 0 } ) ?? 0.5
74- }
75-
76- }
77- public struct Position : Equatable , CustomStringConvertible {
78- public var board : Board
79- public var playerTurn : PlayerColor
80- public var castlingRights : CastlingRights
81- public var enPassantTarget : Square ?
82- public var halfmoves : UInt
83- public var fullmoves : UInt
84-
85- public var description : String {
86- return " Position( \( fen ( ) ) ) "
87- }
88-
89- public init ( board: Board = Board ( ) ,
90- playerTurn: PlayerColor = . white,
91- castlingRights: CastlingRights = . all,
92- enPassantTarget: Square ? = nil ,
93- halfmoves: UInt = 0 ,
94- fullmoves: UInt = 1 ) {
95- self . board = board
96- self . playerTurn = playerTurn
97- self . castlingRights = castlingRights
98- self . enPassantTarget = enPassantTarget
99- self . halfmoves = halfmoves
100- self . fullmoves = fullmoves
101- }
102-
103- public init ? ( fen: String ) {
104- let parts = fen. split ( separator: " " ) . map ( String . init)
105- guard
106- parts. count == 6 ,
107- let board = Board ( fen: parts [ 0 ] ) ,
108- parts [ 1 ] . count == 1 ,
109- let playerTurn = PlayerColor . init ( string: parts [ 1 ] ) ,
110- let rights = CastlingRights ( string: parts [ 2 ] ) ,
111- let halfmoves = UInt ( parts [ 4 ] ) ,
112- let fullmoves = UInt ( parts [ 5 ] ) ,
113- fullmoves > 0 else {
114- return nil
115- }
116- var target : Square ?
117- let targetStr = parts [ 3 ]
118- if targetStr. count == 2 {
119- guard let square = Square ( targetStr) else {
120- return nil
121- }
122- target = square
123- } else {
124- guard targetStr == " - " else {
125- return nil
126- }
127- }
128- self . init ( board: board,
129- playerTurn: playerTurn,
130- castlingRights: rights,
131- enPassantTarget: target,
132- halfmoves: halfmoves,
133- fullmoves: fullmoves)
134- }
135-
136- func fen( ) -> String {
137- return board. fen ( )
138- + " \( playerTurn. isWhite ( ) ? " w " : " b " ) \( castlingRights. description) "
139- + ( enPassantTarget. map { " \( $0 as Square ) " . lowercased ( ) } ?? " - " )
140- + " \( halfmoves) \( fullmoves) "
141- }
142-
143- public static func == ( lhs: Game . Position , rhs: Game . Position ) -> Bool {
144- return lhs. playerTurn == rhs. playerTurn
145- && lhs. castlingRights == rhs. castlingRights
146- && lhs. halfmoves == rhs. halfmoves
147- && lhs. fullmoves == rhs. fullmoves
148- && lhs. enPassantTarget == rhs. enPassantTarget
149- && lhs. board == rhs. board
150- }
151-
152- internal func validationError( ) -> PositionError ? {
153- for color in PlayerColor . allCases {
154- guard board. count ( of: Piece ( king: color) ) == 1 else {
155- return . wrongKingCount( color)
156- }
157- }
158- for right in castlingRights {
159- let color = right. color
160- let king = Piece ( king: color)
161- guard board. bitboard ( for: king) == Bitboard ( startFor: king) else {
162- return . missingKing( right) }
163- let rook = Piece ( rook: color)
164- let square = Square ( file: right. side. isKingside ? . h : . a,
165- rank: Rank ( startFor: color) )
166- guard board. bitboard ( for: rook) [ square] else {
167- return . missingRook( right)
168- }
169- }
170- if let target = enPassantTarget {
171- guard target. rank == ( playerTurn. isWhite ( ) ? 6 : 3 ) else {
172- return . wrongEnPassantTargetRank( target. rank)
173- }
174- if let piece = board [ target] {
175- return . nonEmptyEnPassantTarget( target, piece)
176- }
177- let pawnSquare = Square ( file: target. file, rank: playerTurn. isWhite ( ) ? 5 : 4 )
178- guard board [ pawnSquare] == Piece ( pawn: playerTurn. inverse ( ) ) else {
179- return . missingEnPassantPawn( pawnSquare)
180- }
181- let startSquare = Square ( file: target. file, rank: playerTurn. isWhite ( ) ? 7 : 2 )
182- if let piece = board [ startSquare] {
183- return . nonEmptyEnPassantSquare( startSquare, piece)
184-
185- }
186- }
187- return nil
188- }
189- }
190-
191- public enum PositionError : Error {
192- case wrongKingCount( PlayerColor )
193- case missingKing( CastlingRights . Right )
194- case missingRook( CastlingRights . Right )
195- case wrongEnPassantTargetRank( Rank )
196- case nonEmptyEnPassantTarget( Square , Piece )
197- case missingEnPassantPawn( Square )
198- case nonEmptyEnPassantSquare( Square , Piece )
199- case fenMalformed
200- }
201-
20233 public enum ExecutionError : Error {
20334
20435 case missingPiece( Square )
@@ -288,16 +119,15 @@ public struct Game {
288119 }
289120
290121 public init ( whitePlayer: String = " " ,
291- blackPlayer: String = " " ,
292- variant: Variant = . standard) {
122+ blackPlayer: String = " " ) {
293123 self . moveHistory = [ ]
294124 self . undoHistory = [ ]
295- self . board = Board ( variant : variant )
125+ self . board = Board ( )
296126 self . playerTurn = . white
297127 self . castlingRights = . all
298128 self . whitePlayer = whitePlayer
299129 self . blackPlayer = blackPlayer
300- self . variant = variant
130+ self . variant = . standard
301131 self . attackersToKing = 0
302132 self . halfmoves = 0
303133 self . fullmoves = 1
@@ -605,3 +435,84 @@ extension Game {
605435 variant: variant)
606436 }
607437}
438+
439+ public extension Game {
440+ func sanMove( from uci: String ) throws -> SANMove {
441+ guard uci. count >= 4 else { throw FischerCoreError . illegalMove }
442+
443+ let fromString = String ( uci. prefix ( 2 ) )
444+ let toString = String ( uci. dropFirst ( 2 ) . prefix ( 2 ) )
445+ let promotionChar = uci. count == 5 ? uci. last : nil
446+
447+ guard
448+ let from = Square ( fromString) ,
449+ let to = Square ( toString) ,
450+ let piece = board [ from]
451+ else {
452+ throw FischerCoreError . illegalMove
453+ }
454+
455+ guard isLegal ( move: from >>> to) else { throw FischerCoreError . illegalMove }
456+
457+ if piece. kind == . king && from. file == . e {
458+ if to. file == . g {
459+ return . kingsideCastling
460+ } else if to. file == . c {
461+ return . queensideCastling
462+ }
463+ }
464+
465+ let isCapture = self . board [ to] != nil || ( piece. kind == . pawn && to == enPassantTarget)
466+
467+ let promotionTo : SANMove . PromotionPiece ? = {
468+ guard let char = promotionChar else { return nil }
469+ return SANMove . PromotionPiece ( rawValue: String ( char) . uppercased ( ) )
470+ } ( )
471+
472+ let possibleDisambiguations = self . board. bitboard ( for: piece)
473+ . filter { $0 != from }
474+ . filter {
475+ self . isLegal ( move: $0 >>> to)
476+ }
477+
478+ let disambiguation : SANMove . FromPosition ? = {
479+ if isCapture && piece. kind == . pawn { return . file( from. file) }
480+ guard !possibleDisambiguations. isEmpty else { return nil }
481+ let fileUnique = !possibleDisambiguations. contains ( where: { $0. file == from. file && $0 != from } )
482+ let rankUnique = !possibleDisambiguations. contains ( where: { $0. rank == from. rank && $0 != from } )
483+
484+ if fileUnique {
485+ return . file( from. file)
486+ } else if rankUnique {
487+ return . rank( from. rank)
488+ } else {
489+ return . square( from)
490+ }
491+ } ( )
492+
493+ var gameAfterMove = self
494+ try gameAfterMove. execute ( move: Move ( start: from, end: to) )
495+
496+ let sanDefault = SANMove . SANDefaultMove (
497+ piece: piece. kind,
498+ from: disambiguation,
499+ isCapture: isCapture,
500+ toSquare: to,
501+ promotionTo: promotionTo,
502+ isCheck: gameAfterMove. kingIsChecked,
503+ isCheckmate: gameAfterMove. isFinished
504+ )
505+ return . san( sanDefault)
506+ }
507+
508+ func sanMoveList( from uciArray: [ String ] ) throws -> [ SANMove ] {
509+ var sanMoves : [ SANMove ] = [ ]
510+ var currentGame = self
511+ for uci in uciArray {
512+ let sanMove = try currentGame. sanMove ( from: uci)
513+ try currentGame. execute ( move: Move . init ( game: currentGame, sanMove: sanMove) )
514+ sanMoves. append ( sanMove)
515+ }
516+ return sanMoves
517+ }
518+ }
0 commit comments