package main import "testing" func TestGoModelPlayStoneViaKey(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) m.cursor = GoPos{Row: 4, Col: 4} next, _ := m.Update(key("enter")) m = next.(GoModel) if m.isError { t.Fatalf("неожиданная ошибка: %s", m.message) } if m.game.Board[4][4] != GoBlack { t.Fatalf("камень должен был встать в (4,4) как чёрный") } } func TestGoModelCursorMovement(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) m.cursor = GoPos{Row: 4, Col: 4} next, _ := m.Update(key("up")) m = next.(GoModel) if m.cursor.Row != 3 { t.Errorf("курсор должен был сдвинуться вверх") } next, _ = m.Update(key("right")) m = next.(GoModel) if m.cursor.Col != 5 { t.Errorf("курсор должен был сдвинуться вправо") } } func TestGoModelCursorClampsAtEdges(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) m.cursor = GoPos{Row: 0, Col: 0} next, _ := m.Update(key("up")) m = next.(GoModel) if m.cursor.Row != 0 { t.Errorf("курсор не должен был уйти за верхний край") } next, _ = m.Update(key("left")) m = next.(GoModel) if m.cursor.Col != 0 { t.Errorf("курсор не должен был уйти за левый край") } } func TestGoModelPassViaKey(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) next, _ := m.Update(key("p")) m = next.(GoModel) if m.isError { t.Fatalf("неожиданная ошибка паса: %s", m.message) } } func TestGoModelInvalidMoveShowsError(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) m.cursor = GoPos{Row: 0, Col: 0} m.game.Board[0][0] = GoBlack next, _ := m.Update(key("enter")) m = next.(GoModel) if !m.isError { t.Fatalf("ожидалась ошибка хода на занятую точку") } } // TestGoModelBotPassShowsDistinctMessage — по мотивам реального // наблюдения пользователя: "бот делал ход, но камень не появлялся" // — на самом деле бот пасовал, но интерфейс молча показывал то же // сообщение "Бот сходил.", что и при обычном ходе, из-за чего // казалось, будто ход просто не отрисовался. func TestGoModelBotPassShowsDistinctMessage(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) // заполняем всю доску без единой пустой клетки — тогда легальных // ходов гарантированно нет ни у кого, пас неизбежен for r := 0; r < 9; r++ { for c := 0; c < 9; c++ { if (r+c)%2 == 0 { m.game.Board[r][c] = GoBlack } else { m.game.Board[r][c] = GoWhite } } } m.game.Turn = GoWhite next, _ := m.Update(botMoveMsg{}) m = next.(GoModel) if m.message != T("go.msg.bot_passed") { t.Errorf("ожидалось отдельное сообщение о пасе бота, получено: %q", m.message) } if m.message == T("go.msg.bot_moved") { t.Errorf("сообщение о пасе не должно совпадать с сообщением об обычном ходе") } } func TestGoModelQReturnsToMenu(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) next, _ := m.Update(key("q")) m = next.(GoModel) if !m.backToMenu { t.Fatalf("ожидался флаг backToMenu после 'q'") } if m.quitting { t.Fatalf("'q' не должен закрывать всё приложение") } } func TestGoModelNewGameAfterResult(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) m.game.Result = &GoResult{Winner: GoBlack, BlackScore: 10, WhiteScore: 7.5} next, _ := m.Update(key("n")) m = next.(GoModel) if m.game.Result != nil { t.Errorf("новая партия не должна была начаться с результатом") } if m.game.Size != 9 { t.Errorf("новая партия должна была сохранить размер доски") } } func TestGoModelSupportsAllBoardSizes(t *testing.T) { for _, size := range []int{9, 13, 19} { m := NewGoModel(size, GoDifficultyMedium, ColorChoiceA) if m.game.Size != size { t.Errorf("ожидался размер доски %d, получено %d", size, m.game.Size) } } } // TestGoModelFullAutoPlaythrough прогоняет TUI-модель от начала до // конца на маленькой доске, где "человек" отыгрывает свои ходы // через бота, проверяя, что весь путь через Update не паникует и // партия завершается. func TestGoModelFullAutoPlaythrough(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) helperBot := GoBot{Difficulty: GoDifficultyEasy} rnd := m.rnd const maxSteps = 3000 for i := 0; i < maxSteps && m.game.Result == nil; i++ { if m.game.Turn != m.human { next, _ := m.Update(botMoveMsg{}) m = next.(GoModel) continue } pos, ok := helperBot.DecideMove(m.game, rnd) if !ok { next, _ := m.Update(key("p")) m = next.(GoModel) continue } m.cursor = pos next, _ := m.Update(key("enter")) m = next.(GoModel) if m.isError { t.Fatalf("неожиданная ошибка хода человеком: %s", m.message) } } if m.game.Result == nil { t.Fatalf("партия через TUI не завершилась за %d шагов", maxSteps) } } func TestNewGoModelRespectsColorChoiceBlack(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceA) if m.human != GoBlack { t.Errorf("при выборе ColorChoiceA человек должен был играть чёрными, получено %v", m.human) } } func TestNewGoModelRespectsColorChoiceWhite(t *testing.T) { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceB) if m.human != GoWhite { t.Errorf("при выборе ColorChoiceB человек должен был играть белыми, получено %v", m.human) } } func TestNewGoModelRandomColorGivesValidColor(t *testing.T) { seenBlack, seenWhite := false, false for i := 0; i < 50 && !(seenBlack && seenWhite); i++ { m := NewGoModel(9, GoDifficultyEasy, ColorChoiceRandom) if m.human == GoBlack { seenBlack = true } else { seenWhite = true } } if !seenBlack || !seenWhite { t.Errorf("случайный выбор цвета должен был давать оба варианта за 50 попыток: чёрные=%v белые=%v", seenBlack, seenWhite) } }