using System.Text; using MinMaxAB; namespace Connect4; /// /// Доска для игры в 4 в ряд /// public class GameBoard { public Player[][] cells; public readonly GameDimensions dimensions; public GameBoard(GameDimensions dim) { cells = Extensions.Fill(dim.Columns, dim.Rows, Player.None); dimensions = dim; } public GameBoard(GameDimensions dim, Player[][] data) { dimensions = dim; cells = data; } private bool cacheInvalidated = false; private Player winnerCache = Player.None; /// /// Закинуть монетку сверху /// /// Игрок, закинувший монетку /// Столбец, в который закинуть монетку /// Получилось ли поместить монетку public bool PlaceCoin(Player player, int column) { int row = 0; while (row < dimensions.Rows && cells[column][row] == Player.None) { row++; } if (row == 0) return false; cells[column][row - 1] = player; cacheInvalidated = true; return true; } /// /// Убрать верхнюю монетку /// /// Столбец, из которого убрать монетку /// Удалось ли убрать монетку public bool RemoveFromTop(int column) { int row = 0; while (row < dimensions.Rows && cells[column][row] == Player.None) { row++; } if (row == dimensions.Rows) return false; cells[column][row] = Player.None; cacheInvalidated = true; return true; } /// /// Найти победители /// /// Игрок-победитель public Player? GetWinner() { if (!cacheInvalidated) return winnerCache; cacheInvalidated = false; for (int i = 0; i < dimensions.Columns; i++) { for (int j = 0; j < dimensions.Rows; j++) { if (cells[i][j] == Player.None) continue; // Горизонталь bool horizontal = i + 3 < dimensions.Columns; // Вертикаль bool vertical = j + 3 < dimensions.Rows; if (!horizontal && !vertical) continue; // Диагональ bool diagonal = horizontal && vertical; // Вторая диагональ bool reverseDiagonal = vertical && i - 3 >= 0; // Проходим по всем возможным длинам последовательности for (int k = 1; k < 4; k++) { horizontal = horizontal && cells[i][j] == cells[i + k][j]; vertical = vertical && cells[i][j] == cells[i][j + k]; diagonal = diagonal && cells[i][j] == cells[i + k][j + k]; reverseDiagonal = reverseDiagonal && cells[i][j] == cells[i - k][j + k]; if (!horizontal && !vertical && !diagonal && !reverseDiagonal) break; } if (horizontal || vertical || diagonal || reverseDiagonal) { winnerCache = cells[i][j]; return cells[i][j]; } } } winnerCache = Player.None; return Player.None; } /// /// Проверить, есть ли пустые столбцы /// /// true если есть пустые столбцы public bool HasEmptyColumns() { for (int x = 0; x < dimensions.Columns; x++) { if (cells[x][0] == Player.None) return true; } return false; } /// /// Найти символ, обозначающий игрока /// /// Игрок /// Символ, обозначающий данного игрока protected char PlayerChar(Player player) => player switch { Player.Human => 'O', Player.Computer => 'X', _ => ' ', }; /// /// Сериализовать доску /// /// Строка, содержащая отформатированное представление доски public override string ToString() { string sep = $"\n{new('-', dimensions.Columns * 4)}"; var builder = new StringBuilder(sep); builder.Append("\n| "); for (int column = 0; column < dimensions.Columns; column++) { builder.Append(column + 1).Append(" | "); } builder.AppendLine(sep); for (int row = 0; row < dimensions.Rows; row++) { builder.Append("| "); for (int column = 0; column < dimensions.Columns; column++) builder.Append(PlayerChar(cells[column][row])).Append(" | "); builder.AppendLine(sep); } builder.Append("| "); for (int column = 0; column < dimensions.Columns; column++) { builder.Append(column + 1).Append(" | "); } builder.AppendLine(sep); return builder.ToString(); } /// /// Скопировать доску вместе с ее содержимым /// /// Копия доски с таким же содержимым public GameBoard Clone() { var newBoard = new GameBoard(dimensions, cells.DeepCopy()); return newBoard; } }