317 lines
10 KiB
Go
317 lines
10 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|