go_games_collection/go_game_test.go

317 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import "testing"
func gp(r, c int) GoPos { return GoPos{Row: r, Col: c} }
func TestNewGoGameEmpty(t *testing.T) {
g := NewGoGame(9)
if g.Size != 9 {
t.Fatalf("ожидался размер доски 9, получено %d", g.Size)
}
if g.Turn != GoBlack {
t.Errorf("первый ход должен быть за чёрными")
}
for r := 0; r < 9; r++ {
for c := 0; c < 9; c++ {
if g.Board[r][c] != GoEmpty {
t.Fatalf("новая доска должна быть полностью пустой")
}
}
}
}
func TestPlaySwitchesTurn(t *testing.T) {
g := NewGoGame(9)
if err := g.Play(gp(4, 4)); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.Board[4][4] != GoBlack {
t.Errorf("камень должен был встать в (4,4) как чёрный")
}
if g.Turn != GoWhite {
t.Errorf("ход должен был перейти к белым")
}
}
func TestCannotPlayOnOccupiedPoint(t *testing.T) {
g := NewGoGame(9)
g.Play(gp(4, 4))
if err := g.Play(gp(4, 4)); err != ErrGoOccupied {
t.Errorf("ожидалась ошибка занятой точки, получено: %v", err)
}
}
func TestCannotPlayOutOfBounds(t *testing.T) {
g := NewGoGame(9)
if err := g.Play(gp(-1, 0)); err != ErrGoOutOfBounds {
t.Errorf("ожидалась ошибка выхода за границы, получено: %v", err)
}
if err := g.Play(gp(9, 0)); err != ErrGoOutOfBounds {
t.Errorf("ожидалась ошибка выхода за границы, получено: %v", err)
}
}
func TestSingleStoneCapture(t *testing.T) {
g := NewGoGame(9)
g.Board[3][4] = GoBlack
g.Board[5][4] = GoBlack
g.Board[4][3] = GoBlack
g.Board[4][4] = GoWhite
g.Turn = GoBlack
if err := g.Play(gp(4, 5)); err != nil {
t.Fatalf("неожиданная ошибка взятия: %v", err)
}
if g.Board[4][4] != GoEmpty {
t.Errorf("белый камень должен был быть взят")
}
if g.CapturedByBlack != 1 {
t.Errorf("ожидалось 1 взятие у чёрных, получено %d", g.CapturedByBlack)
}
}
func TestGroupCaptureRemovesWholeGroup(t *testing.T) {
g := NewGoGame(9)
g.Board[4][4] = GoWhite
g.Board[4][5] = GoWhite
g.Board[3][4] = GoBlack
g.Board[3][5] = GoBlack
g.Board[5][4] = GoBlack
g.Board[5][5] = GoBlack
g.Board[4][3] = GoBlack
g.Turn = GoBlack
if err := g.Play(gp(4, 6)); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.Board[4][4] != GoEmpty || g.Board[4][5] != GoEmpty {
t.Errorf("вся группа должна была быть взята целиком")
}
if g.CapturedByBlack != 2 {
t.Errorf("ожидалось 2 взятия, получено %d", g.CapturedByBlack)
}
}
func TestSuicideMoveIsIllegal(t *testing.T) {
g := NewGoGame(9)
g.Board[3][4] = GoWhite
g.Board[5][4] = GoWhite
g.Board[4][3] = GoWhite
g.Board[4][5] = GoWhite
g.Turn = GoBlack
if err := g.Play(gp(4, 4)); err != ErrGoSuicide {
t.Errorf("ожидалась ошибка самоубийственного хода, получено: %v", err)
}
if g.Board[4][4] != GoEmpty {
t.Errorf("доска не должна была измениться после отклонённого хода")
}
}
func TestSuicideAllowedIfItCaptures(t *testing.T) {
g := NewGoGame(9)
// белый камень (4,4) окружён чёрными с трёх сторон, единственная
// свобода — (4,5); чёрные играют туда — это взятие, а не
// самоубийство их собственного камня
g.Board[3][4] = GoBlack
g.Board[5][4] = GoBlack
g.Board[4][3] = GoBlack
g.Board[4][4] = GoWhite
g.Turn = GoBlack
if err := g.Play(gp(4, 5)); err != nil {
t.Fatalf("ход должен был пройти как взятие, а не самоубийство: %v", err)
}
if g.Board[4][4] != GoEmpty {
t.Errorf("белый камень должен был быть взят")
}
if g.Board[4][5] != GoBlack {
t.Errorf("чёрный камень должен был остаться на месте после взятия")
}
}
func TestKoRuleForbidsImmediateRecapture(t *testing.T) {
g := NewGoGame(9)
// классическая форма ко (координаты row,col):
// (0,1)=W (0,2)=B
// (1,0)=W (1,1)=. (1,2)=W (1,3)=B <- белые только что взяли
// одинокого чёрного в (1,1),
// сыграв в (1,2)
// (2,1)=W (2,2)=B
// белый камень (1,2) сам стоит всего с одной свободой — (1,1) —
// и чёрные не могут немедленно отыграть его обратно
g.Board[0][1] = GoWhite
g.Board[0][2] = GoBlack
g.Board[1][0] = GoWhite
g.Board[1][2] = GoWhite
g.Board[1][3] = GoBlack
g.Board[2][1] = GoWhite
g.Board[2][2] = GoBlack
// позиция ДО хода белых: на месте (1,2) было пусто, а на (1,1)
// стоял тот самый одинокий чёрный камень, которого взяли
before := cloneGoBoard(g.Board)
before[1][1] = GoBlack
before[1][2] = GoEmpty
g.prevBoardBeforeOpponentMove = before
g.Turn = GoBlack
if err := g.Play(gp(1, 1)); err != ErrGoKoViolation {
t.Errorf("ожидалась ошибка нарушения ко, получено: %v", err)
}
}
func TestKoAllowedAfterPlayingElsewhere(t *testing.T) {
g := NewGoGame(9)
g.Board[0][1] = GoWhite
g.Board[0][2] = GoBlack
g.Board[1][0] = GoWhite
g.Board[1][2] = GoWhite
g.Board[1][3] = GoBlack
g.Board[2][1] = GoWhite
g.Board[2][2] = GoBlack
before := cloneGoBoard(g.Board)
before[1][1] = GoBlack
before[1][2] = GoEmpty
g.prevBoardBeforeOpponentMove = before
g.Turn = GoBlack
// чёрные играют "ходом ко" в другое место
if err := g.Play(gp(7, 7)); err != nil {
t.Fatalf("неожиданная ошибка хода в сторону: %v", err)
}
// теперь белые тоже играют куда-то ещё
if err := g.Play(gp(0, 7)); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
// а теперь чёрные вновь могут взять в (1,1), т.к. позиция уже не та
if err := g.Play(gp(1, 1)); err != nil {
t.Fatalf("после ходов в сторону взятие должно быть разрешено: %v", err)
}
}
func TestPassTwiceEndsGame(t *testing.T) {
g := NewGoGame(9)
if err := g.Pass(); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.Result != nil {
t.Fatalf("после одного паса партия не должна завершаться")
}
if err := g.Pass(); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.Result == nil {
t.Fatalf("после двух пасов подряд партия должна была завершиться")
}
}
func TestPassResetByPlay(t *testing.T) {
g := NewGoGame(9)
g.Pass()
if err := g.Play(gp(4, 4)); err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if g.Passes != 0 {
t.Errorf("счётчик пасов должен был сброситься после реального хода")
}
}
func TestScoringEmptyBoardKomiDecides(t *testing.T) {
g := NewGoGame(9)
g.Pass()
g.Pass()
if g.Result == nil {
t.Fatalf("партия должна была завершиться")
}
if g.Result.BlackScore != 0 {
t.Errorf("на пустой доске у чёрных должно быть 0 очков, получено %v", g.Result.BlackScore)
}
if g.Result.WhiteScore != GoKomi {
t.Errorf("у белых должно быть ровно коми (%v), получено %v", GoKomi, g.Result.WhiteScore)
}
if g.Result.Winner != GoWhite {
t.Errorf("на пустой доске белые должны побеждать за счёт коми")
}
}
func TestScoringCountsStonesAndTerritory(t *testing.T) {
g := NewGoGame(9)
// угловая точка (0,0) на доске имеет всего два соседа — (0,1) и
// (1,0); если оба чёрные, то сама точка (0,0), оставшись пустой,
// полностью замкнута исключительно чёрным цветом
g.Board[0][1] = GoBlack
g.Board[1][0] = GoBlack
g.Board[8][8] = GoWhite
g.Pass()
g.Pass()
if g.Result == nil {
t.Fatalf("партия должна была завершиться")
}
// у чёрных: 2 камня + 1 замкнутая пустая точка (0,0) = 3
if g.Result.BlackScore != 3 {
t.Errorf("ожидалось 3 очка у чёрных, получено %v", g.Result.BlackScore)
}
}
func TestNeutralTerritoryNotScoredToEither(t *testing.T) {
g := NewGoGame(9)
g.Board[4][4] = GoBlack
g.Board[4][5] = GoWhite
g.Pass()
g.Pass()
if g.Result == nil {
t.Fatalf("партия должна была завершиться")
}
if g.Result.BlackScore != 1 {
t.Errorf("ожидалось 1 очко у чёрных (только свой камень), получено %v", g.Result.BlackScore)
}
if g.Result.WhiteScore != 1+GoKomi {
t.Errorf("ожидалось 1+коми у белых, получено %v", g.Result.WhiteScore)
}
}
func TestIsLegalDoesNotMutateState(t *testing.T) {
g := NewGoGame(9)
g.Board[3][4] = GoWhite
g.Board[5][4] = GoWhite
g.Board[4][3] = GoWhite
g.Board[4][5] = GoWhite
g.Turn = GoBlack
if g.IsLegal(gp(4, 4)) {
t.Errorf("самоубийственный ход не должен считаться легальным")
}
if g.Board[4][4] != GoEmpty {
t.Errorf("IsLegal не должен был изменить реальную доску")
}
if g.Turn != GoBlack {
t.Errorf("IsLegal не должен был менять очередь хода")
}
}
func TestPlayAfterGameOverReturnsError(t *testing.T) {
g := NewGoGame(9)
g.Pass()
g.Pass()
if err := g.Play(gp(0, 0)); err != ErrGoGameOver {
t.Errorf("ожидалась ErrGoGameOver, получено: %v", err)
}
if err := g.Pass(); err != ErrGoGameOver {
t.Errorf("ожидалась ErrGoGameOver при пасе после конца игры, получено: %v", err)
}
}
func TestBoardSizesSupported(t *testing.T) {
for _, size := range []int{9, 13, 19} {
g := NewGoGame(size)
if g.Size != size || len(g.Board) != size || len(g.Board[0]) != size {
t.Errorf("некорректная доска для размера %d", size)
}
}
}