Skip to content

Commit 20cf758

Browse files
authored
feat: Implement individual ignore options for automatic draws (#80)
1 parent 3a101d2 commit 20cf758

File tree

2 files changed

+114
-12
lines changed

2 files changed

+114
-12
lines changed

game.go

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,16 @@ type TagPairs map[string]string
8686

8787
// A Game represents a single chess game.
8888
type Game struct {
89-
pos *Position // Current position
90-
outcome Outcome // Game result
91-
tagPairs TagPairs // PGN tag pairs
92-
rootMove *Move // Root of move tree
93-
currentMove *Move // Current position in tree
94-
comments [][]string // Game comments
95-
method Method // How the game ended
96-
ignoreAutomaticDraws bool // Flag for automatic draw handling
89+
pos *Position // Current position
90+
outcome Outcome // Game result
91+
tagPairs TagPairs // PGN tag pairs
92+
rootMove *Move // Root of move tree
93+
currentMove *Move // Current position in tree
94+
comments [][]string // Game comments
95+
method Method // How the game ended
96+
ignoreFivefoldRepetitionDraw bool // Flag for automatic FivefoldRepetition draw handling
97+
ignoreSeventyFiveMoveRuleDraw bool // Flag for automatic SeventyFiveMoveRule draw handling
98+
ignoreInsufficientMaterialDraw bool // Flag for automatic InsufficientMaterial draw handling
9799
}
98100

99101
// PGN takes a reader and returns a function that updates
@@ -688,19 +690,19 @@ func (g *Game) evaluatePositionStatus() {
688690
}
689691

690692
// five fold rep creates automatic draw
691-
if !g.ignoreAutomaticDraws && g.numOfRepetitions() >= 5 {
693+
if !g.ignoreFivefoldRepetitionDraw && g.numOfRepetitions() >= 5 {
692694
g.outcome = Draw
693695
g.method = FivefoldRepetition
694696
}
695697

696698
// 75 move rule creates automatic draw
697-
if !g.ignoreAutomaticDraws && g.pos.halfMoveClock >= 150 && g.method != Checkmate {
699+
if !g.ignoreSeventyFiveMoveRuleDraw && g.pos.halfMoveClock >= 150 && g.method != Checkmate {
698700
g.outcome = Draw
699701
g.method = SeventyFiveMoveRule
700702
}
701703

702704
// insufficient material creates automatic draw
703-
if !g.ignoreAutomaticDraws && !g.pos.board.hasSufficientMaterial() {
705+
if !g.ignoreInsufficientMaterialDraw && !g.pos.board.hasSufficientMaterial() {
704706
g.outcome = Draw
705707
g.method = InsufficientMaterial
706708
}
@@ -718,7 +720,9 @@ func (g *Game) copy(game *Game) {
718720
g.outcome = game.outcome
719721
g.method = game.method
720722
g.comments = game.Comments()
721-
g.ignoreAutomaticDraws = game.ignoreAutomaticDraws
723+
g.ignoreFivefoldRepetitionDraw = game.ignoreFivefoldRepetitionDraw
724+
g.ignoreSeventyFiveMoveRuleDraw = game.ignoreSeventyFiveMoveRuleDraw
725+
g.ignoreInsufficientMaterialDraw = game.ignoreInsufficientMaterialDraw
722726
}
723727

724728
// Clone returns a deep copy of the game.
@@ -1043,3 +1047,30 @@ func ValidateSAN(s string) error {
10431047
_, err := algebraicNotationParts(s)
10441048
return err
10451049
}
1050+
1051+
// IgnoreFivefoldRepetitionDraw returns a Game option that disables automatic draws
1052+
// caused by the fivefold repetition rule. When applied, the game will not
1053+
// automatically end in a draw if the same position occurs five times.
1054+
func IgnoreFivefoldRepetitionDraw() func(*Game) {
1055+
return func(g *Game) {
1056+
g.ignoreFivefoldRepetitionDraw = true
1057+
}
1058+
}
1059+
1060+
// IgnoreSeventyFiveMoveRuleDraw returns a Game option that disables automatic draws
1061+
// triggered by the seventy-five move rule. When applied, the game will not
1062+
// automatically end in a draw if one hundred fifty half-moves pass without a pawn move or capture.
1063+
func IgnoreSeventyFiveMoveRuleDraw() func(*Game) {
1064+
return func(g *Game) {
1065+
g.ignoreSeventyFiveMoveRuleDraw = true
1066+
}
1067+
}
1068+
1069+
// IgnoreInsufficientMaterialDraw returns a Game option that disables automatic draws
1070+
// caused by insufficient material. When applied, the game will not automatically
1071+
// end in a draw even if checkmate is impossible with the remaining pieces.
1072+
func IgnoreInsufficientMaterialDraw() func(*Game) {
1073+
return func(g *Game) {
1074+
g.ignoreInsufficientMaterialDraw = true
1075+
}
1076+
}

game_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,24 @@ func TestFiveFoldRepetition(t *testing.T) {
147147
}
148148
}
149149

150+
func TestFiveFoldRepetitionIgnored(t *testing.T) {
151+
g := NewGame(IgnoreFivefoldRepetitionDraw())
152+
moves := []string{
153+
"Nf3", "Nf6", "Ng1", "Ng8",
154+
"Nf3", "Nf6", "Ng1", "Ng8",
155+
"Nf3", "Nf6", "Ng1", "Ng8",
156+
"Nf3", "Nf6", "Ng1", "Ng8",
157+
}
158+
for _, m := range moves {
159+
if err := g.PushMove(m, nil); err != nil {
160+
t.Fatal(err)
161+
}
162+
}
163+
if g.Outcome() == Draw && g.Method() == FivefoldRepetition {
164+
t.Fatal("automatically draw after five repetitions should be ignored")
165+
}
166+
}
167+
150168
func TestFiftyMoveRule(t *testing.T) {
151169
fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 100 60")
152170
g := NewGame(fen)
@@ -174,6 +192,17 @@ func TestSeventyFiveMoveRule(t *testing.T) {
174192
}
175193
}
176194

195+
func TestSeventyFiveMoveRuleIgnored(t *testing.T) {
196+
fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 149 80")
197+
g := NewGame(fen, IgnoreSeventyFiveMoveRuleDraw())
198+
if err := g.PushMove("Kf8", nil); err != nil {
199+
t.Fatal(err)
200+
}
201+
if g.Outcome() == Draw && g.Method() == SeventyFiveMoveRule {
202+
t.Fatal("automatically draw after seventy five moves w/ no pawn move or capture should be ignored")
203+
}
204+
}
205+
177206
func TestInsufficientMaterial(t *testing.T) {
178207
fens := []string{
179208
"8/2k5/8/8/8/3K4/8/8 w - - 1 1",
@@ -195,6 +224,27 @@ func TestInsufficientMaterial(t *testing.T) {
195224
}
196225
}
197226

227+
func TestInsufficientMaterialIgnored(t *testing.T) {
228+
fens := []string{
229+
"8/2k5/8/8/8/3K4/8/8 w - - 1 1",
230+
"8/2k5/8/8/8/3K1N2/8/8 w - - 1 1",
231+
"8/2k5/8/8/8/3K1B2/8/8 w - - 1 1",
232+
"8/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1",
233+
"4b3/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1",
234+
}
235+
for _, f := range fens {
236+
fen, err := FEN(f)
237+
if err != nil {
238+
t.Fatal(err)
239+
}
240+
g := NewGame(IgnoreInsufficientMaterialDraw(), fen)
241+
if g.Outcome() == Draw && g.Method() == InsufficientMaterial {
242+
log.Println(g.Position().Board().Draw())
243+
t.Fatalf("%s automatically draw by insufficient material should be ignored", f)
244+
}
245+
}
246+
}
247+
198248
func TestSufficientMaterial(t *testing.T) {
199249
fens := []string{
200250
"8/2k5/8/8/8/3K1B2/4N3/8 w - - 1 1",
@@ -2190,3 +2240,24 @@ func TestPushNotationMoveVsUnsafePushNotationMovePerformance(t *testing.T) {
21902240
t.Logf("Warning: UnsafePushNotationMove wasn't faster than PushNotationMove - this might be expected for simple positions")
21912241
}
21922242
}
2243+
2244+
func TestIgnoreFivefoldRepetitionDraw(t *testing.T) {
2245+
g := NewGame(IgnoreFivefoldRepetitionDraw())
2246+
if !g.ignoreFivefoldRepetitionDraw {
2247+
t.Fatal("ignoreFivefoldRepetitionDraw should be true after being ignored")
2248+
}
2249+
}
2250+
2251+
func TestIgnoreSeventyFiveMoveRuleDraw(t *testing.T) {
2252+
g := NewGame(IgnoreSeventyFiveMoveRuleDraw())
2253+
if !g.ignoreSeventyFiveMoveRuleDraw {
2254+
t.Fatal("ignoreSeventyFiveMoveRuleDraw should be true after being ignored")
2255+
}
2256+
}
2257+
2258+
func TestIgnoreInsufficientMaterialDraw(t *testing.T) {
2259+
g := NewGame(IgnoreInsufficientMaterialDraw())
2260+
if !g.ignoreInsufficientMaterialDraw {
2261+
t.Fatal("ignoreInsufficientMaterialDraw should be true after being ignored")
2262+
}
2263+
}

0 commit comments

Comments
 (0)