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;
}
}