GameBoard.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. using System.Text;
  2. using MinMaxAB;
  3. namespace Connect4;
  4. /// <summary>
  5. /// Доска для игры в 4 в ряд
  6. /// </summary>
  7. public class GameBoard
  8. {
  9. public Player[][] cells;
  10. public readonly GameDimensions dimensions;
  11. public GameBoard(GameDimensions dim)
  12. {
  13. cells = Extensions.Fill(dim.Columns, dim.Rows, Player.None);
  14. dimensions = dim;
  15. }
  16. public GameBoard(GameDimensions dim, Player[][] data)
  17. {
  18. dimensions = dim;
  19. cells = data;
  20. }
  21. private bool cacheInvalidated = false;
  22. private Player winnerCache = Player.None;
  23. /// <summary>
  24. /// Закинуть монетку сверху
  25. /// </summary>
  26. /// <param name="player">Игрок, закинувший монетку</param>
  27. /// <param name="column">Столбец, в который закинуть монетку</param>
  28. /// <returns>Получилось ли поместить монетку</returns>
  29. public bool PlaceCoin(Player player, int column)
  30. {
  31. int row = 0;
  32. while (row < dimensions.Rows && cells[column][row] == Player.None)
  33. {
  34. row++;
  35. }
  36. if (row == 0)
  37. return false;
  38. cells[column][row - 1] = player;
  39. cacheInvalidated = true;
  40. return true;
  41. }
  42. /// <summary>
  43. /// Убрать верхнюю монетку
  44. /// </summary>
  45. /// <param name="column">Столбец, из которого убрать монетку</param>
  46. /// <returns>Удалось ли убрать монетку</returns>
  47. public bool RemoveFromTop(int column)
  48. {
  49. int row = 0;
  50. while (row < dimensions.Rows && cells[column][row] == Player.None)
  51. {
  52. row++;
  53. }
  54. if (row == dimensions.Rows)
  55. return false;
  56. cells[column][row] = Player.None;
  57. cacheInvalidated = true;
  58. return true;
  59. }
  60. /// <summary>
  61. /// Найти победители
  62. /// </summary>
  63. /// <returns>Игрок-победитель</returns>
  64. public Player? GetWinner()
  65. {
  66. if (!cacheInvalidated) return winnerCache;
  67. cacheInvalidated = false;
  68. for (int i = 0; i < dimensions.Columns; i++)
  69. {
  70. for (int j = 0; j < dimensions.Rows; j++)
  71. {
  72. if (cells[i][j] == Player.None)
  73. continue;
  74. // Горизонталь
  75. bool horizontal = i + 3 < dimensions.Columns;
  76. // Вертикаль
  77. bool vertical = j + 3 < dimensions.Rows;
  78. if (!horizontal && !vertical)
  79. continue;
  80. // Диагональ
  81. bool diagonal = horizontal && vertical;
  82. // Вторая диагональ
  83. bool reverseDiagonal = vertical && i - 3 >= 0;
  84. // Проходим по всем возможным длинам последовательности
  85. for (int k = 1; k < 4; k++)
  86. {
  87. horizontal = horizontal && cells[i][j] == cells[i + k][j];
  88. vertical = vertical && cells[i][j] == cells[i][j + k];
  89. diagonal = diagonal && cells[i][j] == cells[i + k][j + k];
  90. reverseDiagonal = reverseDiagonal && cells[i][j] == cells[i - k][j + k];
  91. if (!horizontal && !vertical && !diagonal && !reverseDiagonal)
  92. break;
  93. }
  94. if (horizontal || vertical || diagonal || reverseDiagonal)
  95. {
  96. winnerCache = cells[i][j];
  97. return cells[i][j];
  98. }
  99. }
  100. }
  101. winnerCache = Player.None;
  102. return Player.None;
  103. }
  104. /// <summary>
  105. /// Проверить, есть ли пустые столбцы
  106. /// </summary>
  107. /// <returns>true если есть пустые столбцы</returns>
  108. public bool HasEmptyColumns()
  109. {
  110. for (int x = 0; x < dimensions.Columns; x++)
  111. {
  112. if (cells[x][0] == Player.None) return true;
  113. }
  114. return false;
  115. }
  116. /// <summary>
  117. /// Найти символ, обозначающий игрока
  118. /// </summary>
  119. /// <param name="player">Игрок</param>
  120. /// <returns>Символ, обозначающий данного игрока</returns>
  121. protected char PlayerChar(Player player) => player switch
  122. {
  123. Player.Human => 'O',
  124. Player.Computer => 'X',
  125. _ => ' ',
  126. };
  127. /// <summary>
  128. /// Сериализовать доску
  129. /// </summary>
  130. /// <returns>Строка, содержащая отформатированное представление доски</returns>
  131. public override string ToString()
  132. {
  133. string sep = $"\n{new('-', dimensions.Columns * 4)}";
  134. var builder = new StringBuilder(sep);
  135. builder.Append("\n| ");
  136. for (int column = 0; column < dimensions.Columns; column++)
  137. {
  138. builder.Append(column + 1).Append(" | ");
  139. }
  140. builder.AppendLine(sep);
  141. for (int row = 0; row < dimensions.Rows; row++)
  142. {
  143. builder.Append("| ");
  144. for (int column = 0; column < dimensions.Columns; column++) builder.Append(PlayerChar(cells[column][row])).Append(" | ");
  145. builder.AppendLine(sep);
  146. }
  147. builder.Append("| ");
  148. for (int column = 0; column < dimensions.Columns; column++)
  149. {
  150. builder.Append(column + 1).Append(" | ");
  151. }
  152. builder.AppendLine(sep);
  153. return builder.ToString();
  154. }
  155. /// <summary>
  156. /// Скопировать доску вместе с ее содержимым
  157. /// </summary>
  158. /// <returns>Копия доски с таким же содержимым</returns>
  159. public GameBoard Clone()
  160. {
  161. var newBoard = new GameBoard(dimensions, cells.DeepCopy());
  162. return newBoard;
  163. }
  164. }