Last active
March 5, 2020 22:37
-
-
Save tomaszbartoszewski/09e02706b79d0c66874d23574a3c6c9e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace TicTacToe | |
{ | |
class Program | |
{ | |
static void Main() | |
{ | |
while (true) | |
{ | |
int gameType = 0; | |
while (!new [] {1,2,3}.Contains(gameType)) | |
{ | |
Console.WriteLine("Select game type: 1 - H vs H, 2 - H vs C, 3 - C vs C"); | |
var typeString = Console.ReadLine(); | |
int.TryParse(typeString, out gameType); | |
} | |
IPlayer firstPlayer; | |
IPlayer secondPlayer; | |
switch (gameType) | |
{ | |
case 1: | |
{ | |
firstPlayer = new HumanConsolePlayer(FieldValue.X); | |
secondPlayer = new HumanConsolePlayer(FieldValue.O); | |
break; | |
} | |
case 2: | |
{ | |
string selection = string.Empty; | |
while (selection != "X" && selection != "O") | |
{ | |
Console.WriteLine("Select your sign: X, O"); | |
selection = Console.ReadLine(); | |
} | |
if (selection == "X") | |
{ | |
firstPlayer = new HumanConsolePlayer(FieldValue.X); | |
secondPlayer = new PerfectPlayer(FieldValue.O); | |
} | |
else | |
{ | |
secondPlayer = new HumanConsolePlayer(FieldValue.O); | |
firstPlayer = new PerfectPlayer(FieldValue.X); | |
} | |
break; | |
} | |
default: | |
{ | |
firstPlayer = new PerfectPlayer(FieldValue.X); | |
secondPlayer = new PerfectPlayer(FieldValue.O); | |
break; | |
} | |
} | |
var game = new Game(firstPlayer, secondPlayer); | |
game.StartGame(); | |
Console.WriteLine("Press any key for new game"); | |
Console.ReadLine(); | |
} | |
} | |
} | |
public class Game | |
{ | |
private readonly IPlayer firstPlayer; | |
private readonly IPlayer secondPlayer; | |
private readonly FieldValue[,] board; | |
public Game(IPlayer firstPlayer, IPlayer secondPlayer) | |
{ | |
this.firstPlayer = firstPlayer; | |
this.secondPlayer = secondPlayer; | |
board = new FieldValue[3, 3]; | |
} | |
public void StartGame() | |
{ | |
var gameStatus = Result.InProgress; | |
FieldValue lastPlayer = FieldValue.X; | |
PrintBoard(GetBoard()); | |
while (gameStatus == Result.InProgress) | |
{ | |
var firstPlayerMove = firstPlayer.GetNextMove(GetBoard()); | |
board[firstPlayerMove.Column, firstPlayerMove.Row] = firstPlayerMove.FieldValue; | |
gameStatus = BoardStatusChecker.GetCurrentState(GetBoard(), firstPlayerMove.FieldValue); | |
lastPlayer = firstPlayerMove.FieldValue; | |
PrintBoard(GetBoard()); | |
if (gameStatus != Result.InProgress) | |
break; | |
var secondPlayerMove = secondPlayer.GetNextMove(GetBoard()); | |
board[secondPlayerMove.Column, secondPlayerMove.Row] = secondPlayerMove.FieldValue; | |
PrintBoard(GetBoard()); | |
gameStatus = BoardStatusChecker.GetCurrentState(GetBoard(), secondPlayerMove.FieldValue); | |
lastPlayer = secondPlayerMove.FieldValue; | |
} | |
switch (gameStatus) | |
{ | |
case Result.Win: | |
Console.WriteLine("{0} won!", lastPlayer); | |
break; | |
case Result.Draw: | |
Console.WriteLine("Draw!"); | |
break; | |
} | |
} | |
private FieldValue[,] GetBoard() | |
{ | |
return board.CopyTable(); | |
} | |
private static void PrintBoard(FieldValue[,] board) | |
{ | |
Console.Write(" "); | |
for (var column = 1; column <= 3; column++) | |
Console.Write(column); | |
Console.WriteLine(); | |
for (var row = 0; row < 3; row++) | |
{ | |
Console.Write(row + 1); | |
for (var column = 0; column < 3; column++) | |
{ | |
Console.Write(board[column, row] == FieldValue.Hidden ? " " : board[column, row].ToString()); | |
} | |
Console.WriteLine(); | |
} | |
} | |
} | |
public static class ArrayHelper | |
{ | |
public static T[,] CopyTable<T>(this T[,] tableToCopy) | |
{ | |
var newTable = new T[tableToCopy.GetLength(0), tableToCopy.GetLength(1)]; | |
for (var i = 0; i < tableToCopy.GetLength(0); i++) | |
{ | |
for (var j = 0; j < tableToCopy.GetLength(1); j++) | |
{ | |
newTable[i, j] = tableToCopy[i, j]; | |
} | |
} | |
return newTable; | |
} | |
} | |
public interface IPlayer | |
{ | |
Move GetNextMove(FieldValue[,] board); | |
} | |
public class HumanConsolePlayer : IPlayer | |
{ | |
private readonly FieldValue fieldValue; | |
public HumanConsolePlayer(FieldValue fieldValue) | |
{ | |
this.fieldValue = fieldValue; | |
} | |
public Move GetNextMove(FieldValue[,] board) | |
{ | |
while (true) | |
{ | |
int column = 0; | |
while (column == 0) | |
{ | |
Console.WriteLine("Select Column(1-3):"); | |
var columnText = Console.ReadLine(); | |
int.TryParse(columnText, out column); | |
if (column > 3 || column < 1) | |
column = 0; | |
} | |
int row = 0; | |
while (row == 0) | |
{ | |
Console.WriteLine("Select Row(1-3):"); | |
var rowText = Console.ReadLine(); | |
int.TryParse(rowText, out row); | |
if (row > 3 || row < 1) | |
row = 0; | |
} | |
if (board[column - 1, row - 1] == FieldValue.Hidden) | |
{ | |
return new Move(column - 1, row - 1, fieldValue); | |
} | |
Console.WriteLine("Field is already selected, pick empty field"); | |
} | |
} | |
} | |
public class PerfectPlayer : IPlayer | |
{ | |
private readonly FieldValue playerFieldValue; | |
public PerfectPlayer(FieldValue fieldValue) | |
{ | |
this.playerFieldValue = fieldValue; | |
} | |
public Move GetNextMove(FieldValue[,] board) | |
{ | |
return CheckAllPossibleMoves(board, playerFieldValue).Move; | |
} | |
private int CalculatePointsForMove(FieldValue[,] board, Move move) | |
{ | |
board[move.Column, move.Row] = move.FieldValue; | |
var status = BoardStatusChecker.GetCurrentState(board, move.FieldValue); | |
if (status == Result.Win && move.FieldValue == playerFieldValue) | |
return 10; | |
if (status == Result.Win && move.FieldValue != playerFieldValue) | |
return -10; | |
if (status == Result.Lose && move.FieldValue == playerFieldValue) | |
return -10; | |
if (status == Result.Lose && move.FieldValue != playerFieldValue) | |
return 10; | |
if (status == Result.Draw) | |
return 0; | |
if (status == Result.InProgress) | |
{ | |
return CheckAllPossibleMoves(board, GetOppositePlayerSign(move.FieldValue)).Points; | |
} | |
return 0; | |
} | |
private FieldValue GetOppositePlayerSign(FieldValue fieldValue) | |
{ | |
if (fieldValue == FieldValue.X) | |
return FieldValue.O; | |
return FieldValue.X; | |
} | |
private PointsForMove CheckAllPossibleMoves(FieldValue[,] board, FieldValue fieldValue) | |
{ | |
var allResults = new List<PointsForMove>(); | |
for (var column = 0; column < 3; column++) | |
for (var row = 0; row < 3; row++) | |
if (board[column, row] == FieldValue.Hidden) | |
{ | |
var move = new Move(column, row, fieldValue); | |
var points = CalculatePointsForMove(board.CopyTable(), move); | |
allResults.Add(new PointsForMove(points, move)); | |
} | |
if (fieldValue == playerFieldValue) | |
return allResults.OrderByDescending(r => r.Points).First(); | |
return allResults.OrderBy(r => r.Points).First(); | |
} | |
private class PointsForMove | |
{ | |
public PointsForMove(int points, Move move) | |
{ | |
Points = points; | |
Move = move; | |
} | |
public int Points { get; } | |
public Move Move { get; } | |
} | |
} | |
public static class BoardStatusChecker | |
{ | |
public static Result GetCurrentState(FieldValue[,] board, FieldValue playerSign) | |
{ | |
if (GetAllLineResults(board, playerSign).Any(r => r == Result.Win)) | |
return Result.Win; | |
if (GetAllLineResults(board, playerSign).Any(r => r == Result.Lose)) | |
return Result.Lose; | |
var areHiddenFields = false; | |
for (var column = 0; column < 3; column++) | |
for (var row = 0; row < 3; row++) | |
if (board[column, row] == FieldValue.Hidden) | |
areHiddenFields = true; | |
if (!areHiddenFields) | |
return Result.Draw; | |
return Result.InProgress; | |
} | |
private static IEnumerable<Result> GetAllLineResults(FieldValue[,] board, FieldValue fieldValue) | |
{ | |
foreach (var lineResult in CheckSimpleLines(board, fieldValue, true)) | |
yield return lineResult; | |
foreach (var lineResult in CheckSimpleLines(board, fieldValue, false)) | |
yield return lineResult; | |
yield return CheckDiagonal(board, fieldValue, true); | |
yield return CheckDiagonal(board, fieldValue, false); | |
} | |
private static IEnumerable<Result> CheckSimpleLines(FieldValue[,] board, FieldValue fieldValue, bool isVertical) | |
{ | |
for (var i = 0; i < 3; i++) | |
{ | |
var checkList = new List<FieldValue>(); | |
for (var j = 0; j < 3; j++) | |
{ | |
checkList.Add(isVertical ? board[i, j] : board[j, i]); | |
} | |
yield return CheckCollection(checkList, fieldValue); | |
} | |
} | |
private static Result CheckDiagonal(FieldValue[,] board, FieldValue fieldValue, bool isReverse) | |
{ | |
var checkList = new List<FieldValue>(); | |
for (var i = 0; i < 3; i++) | |
{ | |
checkList.Add(isReverse ? board[2 - i, 2 - i] : board[i, 2 - i]); | |
} | |
return CheckCollection(checkList, fieldValue); | |
} | |
private static Result CheckCollection(List<FieldValue> checkList, FieldValue fieldValue) | |
{ | |
var oppositePlayerSign = GetOppositePlayerSign(fieldValue); | |
if (checkList.All(v => v == fieldValue)) | |
return Result.Win; | |
if (checkList.All(v => v == oppositePlayerSign)) | |
return Result.Lose; | |
return Result.InProgress; | |
} | |
private static FieldValue GetOppositePlayerSign(FieldValue fieldValue) | |
{ | |
if (fieldValue == FieldValue.X) | |
return FieldValue.O; | |
return FieldValue.X; | |
} | |
} | |
public struct Move | |
{ | |
public FieldValue FieldValue; | |
public int Column; | |
public int Row; | |
public Move(int column, int row, FieldValue fieldValue) | |
{ | |
FieldValue = fieldValue; | |
Column = column; | |
Row = row; | |
} | |
} | |
public enum FieldValue | |
{ | |
Hidden, | |
X, | |
O | |
} | |
public enum Result | |
{ | |
Win, | |
Draw, | |
Lose, | |
InProgress | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment