Last active
October 6, 2019 11:44
-
-
Save MaximBazarov/53bb0ce09b5a30d4ef3cb94c0c51b117 to your computer and use it in GitHub Desktop.
Draft for PGN parsing task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// MARK: - Portable Game Notation (PGN). | |
/// Portable Game Notation (PGN). | |
/// https://en.wikipedia.org/wiki/Portable_Game_Notation | |
struct PGN { | |
typealias TurnNumber = UInt | |
let counterparts: [Counterpart] | |
enum Counterpart { | |
case turn(TurnNumber, [Counterpart]) | |
case whiteMove(SAN.Move) | |
case blackMove(SAN.Move) | |
case moveResult(NAG) | |
case annotation([Counterpart]) | |
case text(String) | |
case arrow(from:SAN.Square, to: SAN.Square, color: HighlightColor) | |
case squareHighlight(SAN.Square, HighlightColor) | |
case tagPair(Tag, String) | |
} | |
/// Tags for tag pairs | |
enum Tag: String { | |
case event = "Event" | |
case site = "Site" | |
case date = "Date" | |
case white = "White" | |
case black = "Black" | |
case result = "Result" | |
} | |
enum HighlightColor: String { | |
case red = "R" | |
case green = "G" | |
case blue = "B" | |
case yellow = "Y" // R + G | |
case magenta = "M" // R + B | |
case cyan = "C" // G + B | |
} | |
} | |
// MARK: - Numeric Annotation Glyphs (NAG) | |
/// Numeric Annotation Glyphs (NAG) | |
/// https://en.wikipedia.org/wiki/Numeric_Annotation_Glyphs | |
// TODO: Implement | |
public enum NAG: String { | |
case veryGoodMove = "!!" | |
case goodMove = "!" | |
case interestingMove = "!?" | |
case questionableMove = "?!" | |
case badMove = "?" | |
case veryBadMove = "??" | |
case whiteDecisiveAdvantage = "+\\-" // looks: +\- | |
case whiteModerateAdvantage = "±" | |
case whiteSlightAdvantage = "⩲" | |
case equalPosition = "=" | |
case blackSlightAdvantage = "⩱" | |
case blackModerateAdvantage = "∓" | |
case blackDecisiveAdvantage = "-+" | |
case onlyMove = "□" | |
case unclearPosition = "∞" | |
case zugzwang = "⨀" | |
case developmentAdvantage = "⟳" | |
case initiative = "↑" | |
case attack = "→" | |
case counterplay = "⇆" | |
case zeitnot = "⊕" | |
case withIdea = "∆" | |
case betterIs = "⌓" | |
case whiteWon = "1-0" | |
case blackWon = "0-1" | |
case draft = "1/2-1/2" | |
case gameOngoing = "*" | |
} | |
// MARK: - Standard Algebraic Notation (SAN). | |
/// Standard Algebraic Notation (SAN). | |
/// https://en.wikipedia.org/wiki/Algebraic_notation_(chess) | |
enum SAN { | |
/// Move e.g. Nxe4 | |
enum Move { | |
case piece(piece: Piece, from: Square, to: Square) | |
case castlingQueen(Piece.Color) | |
case castlingKing(Piece.Color) | |
init?(_ string: String) { | |
return nil | |
} | |
} | |
/// Cell in Standard Algebraic Notation (SAN). e.g. e4 or a7 | |
struct Square { | |
// TODO: Implement | |
let value: String | |
init?(_ string: String) { | |
return nil | |
} | |
} | |
struct Piece { | |
// TODO: Implement | |
let value: String | |
enum Color { | |
case white | |
case black | |
} | |
init?(_ string: String) { | |
return nil | |
} | |
} | |
} | |
// MARK: - Syntax Sugar - | |
func Turn(_ num: PGN.TurnNumber, _ counterparts: [PGN.Counterpart]) -> PGN.Counterpart { | |
return PGN.Counterpart.turn(num, counterparts) | |
} | |
func WhiteMove(_ string: String) -> PGN.Counterpart? { | |
guard let san = SAN.Move(string) else { return nil } | |
return PGN.Counterpart.whiteMove(san) | |
} | |
func BlackMove(_ string: String) -> PGN.Counterpart? { | |
guard let san = SAN.Move(string) else { return nil } | |
return PGN.Counterpart.blackMove(san) | |
} | |
func Annotation(_ counterparts: [PGN.Counterpart]) -> PGN.Counterpart { | |
return PGN.Counterpart.annotation(counterparts) | |
} | |
func Text(_ text: String) -> PGN.Counterpart { | |
return PGN.Counterpart.text(text) | |
} | |
func MoveResult(_ string: String) -> PGN.Counterpart? { | |
guard let nag = NAG(rawValue: string) else { return nil } | |
return PGN.Counterpart.moveResult(nag) | |
} | |
func ArrowHL(_ from: String, _ to: String, _ color: PGN.HighlightColor) -> PGN.Counterpart { | |
return PGN.Counterpart.arrow(from: SAN.Square(from)!, to: SAN.Square(to)!, color: color) | |
} | |
func SquareHL(_ at: String, _ color: PGN.HighlightColor) -> PGN.Counterpart { | |
return PGN.Counterpart.squareHighlight(SAN.Square(at)!, color) | |
} | |
// MARK: - Examples/Tests - | |
/* | |
20.Rd1 ({Not} 20.Rg1 Rxg1+ 21.Kxg1 Re1+ -+) | |
20...Bg2+ 21.Kg1 Bxf3+ 22.Kf1 Bg2+ | |
(22...Rg2 ! {would have won more quickly. For instance:} 23.Qd3 Rxf2+ | |
24.Kg1 Rg2+ 25.Kh1 Rg1#) | |
23.Kg1 Bh3+ 24.Kh1 Bxf2 25.Qf1 {Absolutely forced.} 25...Bxf1 26.Rxf1 Re2 | |
27.Ra1 Rh6 28.d4 Be3 0-1 | |
""" | |
*/ | |
let parsed = PGN(counterparts:[ | |
Turn(20, [ | |
WhiteMove("Rd1")!, | |
// ---- ({Not} 20.Rg1 Rxg1+ 21.Kxg1 Re1+ -+) ----- | |
Annotation( | |
[Text("Not"), | |
Turn(20, [WhiteMove("Rg1")!, | |
BlackMove("Rxg1+")!,]), | |
Turn(21, [WhiteMove("Kxg1")!, | |
BlackMove("Re1+")!, | |
MoveResult("+-")!,]) | |
] | |
), | |
// ------------------------------------------------ | |
BlackMove("Bg2+")! | |
] | |
), | |
// 21.Kg1 Bxf3+ | |
Turn(21,[ | |
WhiteMove("Kg1")!, | |
BlackMove("Bxf3+")! | |
] | |
), | |
// 22.Kf1 Bg2+ | |
// (22...Rg2 ! {would have won more quickly. For instance:} 23.Qd3 Rxf2+ | |
// 24.Kg1 Rg2+ 25.Kh1 Rg1#) | |
Turn(22, [ | |
WhiteMove("Kf1")!, | |
BlackMove("Bg2+")!, | |
Annotation([ | |
Turn(22, [ | |
BlackMove("Rg2")!, | |
Text("would have won more quickly. For instance:"), | |
Turn(23, [ | |
WhiteMove("Qd3")!, | |
BlackMove("Rxf2+")! | |
]), | |
Turn(24, [ | |
WhiteMove("Kg1")!, | |
BlackMove("Rg2+")! | |
]), | |
Turn(25, [ | |
WhiteMove("Kh1")!, | |
BlackMove("Rg1#")! | |
]) | |
]) | |
]) | |
] | |
), | |
// 23.Kg1 Bh3+ 24.Kh1 Bxf2 | |
Turn(23, [ | |
WhiteMove("Kg1")!, | |
BlackMove("Bh3+")! | |
]), | |
Turn(24, [ | |
WhiteMove("Kh1")!, | |
BlackMove("Bxf2")! | |
]), | |
//25.Qf1 {Absolutely forced.} 25...Bxf1 26.Rxf1 Re2 | |
Turn(25, [ | |
WhiteMove("Qf1")!, | |
Text("Absolutely forced."), | |
BlackMove("Bxf1")! | |
]), | |
Turn(26, [ | |
WhiteMove("Rxf1")!, | |
BlackMove("Re2")! | |
]), | |
//27.Ra1 Rh6 28.d4 Be3 0-1 | |
Turn(27, [ | |
WhiteMove("Ra1")!, | |
BlackMove("Rh6")! | |
]), | |
Turn(28, [ | |
WhiteMove("d4")!, | |
BlackMove("Be3 ")!, | |
MoveResult("0-1")! | |
]), | |
] | |
) | |
/* | |
16.Ra2 Rae8 17.Qa6 | |
{[#][%csl Yf3][%cal Rd3f3,Re6g6,Gg6g1] Morphy took twelve minutes | |
over his next move, probably to assure himself that the combination was sound | |
and that he had a forced win in every variation.} | |
17...Qxf3 !! 18.gxf3 Rg6+ | |
*/ | |
let parsed2 = PGN(counterparts: [ | |
Turn(16, [WhiteMove("Ra2")!, BlackMove("Rae8")!]), | |
Turn(17, [ | |
WhiteMove("Qa6")!, | |
Annotation([ | |
SquareHL("f3", .yellow), | |
ArrowHL("d3", "f3", .red), | |
ArrowHL("e6", "g6", .red), | |
ArrowHL("g6", "g1", .green), | |
Text(""" | |
Morphy took twelve minutes | |
over his next move, probably to assure himself that the combination was sound | |
and that he had a forced win in every variation. | |
"""), | |
BlackMove("Qxf3")!, | |
MoveResult("!!")! | |
]) | |
]), | |
Turn(18, [ | |
WhiteMove("gxf3")!, | |
BlackMove("Rg6+")! | |
]) | |
]) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment