Minor Issues fixed and Commented most Files
Signed-off-by: Pablu23 <43807157+Pablu23@users.noreply.github.com>
This commit is contained in:
@@ -28,6 +28,7 @@ namespace TicTacToe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Helper Class to call GetDescription on Variables of Type FieldState, to get the Names from those Types
|
||||||
public static class FieldStateHelper
|
public static class FieldStateHelper
|
||||||
{
|
{
|
||||||
public static string GetDescription(this Enum value){
|
public static string GetDescription(this Enum value){
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using TicTacToe.Players;
|
|||||||
|
|
||||||
namespace TicTacToe
|
namespace TicTacToe
|
||||||
{
|
{
|
||||||
|
//Class on which every game bases on (Only TicTacToe atm)
|
||||||
abstract class Game
|
abstract class Game
|
||||||
{
|
{
|
||||||
protected readonly List<Player> Players = new List<Player>();
|
protected readonly List<Player> Players = new List<Player>();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace TicTacToe.Players
|
namespace TicTacToe.Players
|
||||||
{
|
{
|
||||||
|
// The advanced Player uses the MiniMax Algorithm to chose its best Placement option
|
||||||
public class AdvancedAiPlayer : Player
|
public class AdvancedAiPlayer : Player
|
||||||
{
|
{
|
||||||
private FieldState _enemyFieldState;
|
private FieldState _enemyFieldState;
|
||||||
@@ -20,17 +21,27 @@ namespace TicTacToe.Players
|
|||||||
|
|
||||||
public override int MakeMove(TicTacToeBoard b)
|
public override int MakeMove(TicTacToeBoard b)
|
||||||
{
|
{
|
||||||
|
// The best Value for the Player is decided if it wants to min or to max the Score
|
||||||
int bestVal = (int) Symbol * -1000;
|
int bestVal = (int) Symbol * -1000;
|
||||||
|
|
||||||
|
// The Move it makes on default | TODO: maybe this should be random 0-8, if the algorithm goes into a loop
|
||||||
int bestMove = -1;
|
int bestMove = -1;
|
||||||
|
|
||||||
|
// For every Field on the board Calculate a value of how good it would be to place there
|
||||||
for (int i = 0; i < 9; i++)
|
for (int i = 0; i < 9; i++)
|
||||||
{
|
{
|
||||||
if (b.Fields[i].State == FieldState.Empty)
|
if (b.Fields[i].State == FieldState.Empty)
|
||||||
{
|
{
|
||||||
|
// Place the Piece on the Field
|
||||||
b.Fields[i].State = Symbol;
|
b.Fields[i].State = Symbol;
|
||||||
|
|
||||||
|
// See how likely it is to win with this move
|
||||||
int val = MiniMax(b, -1, _enemyFieldState);
|
int val = MiniMax(b, -1, _enemyFieldState);
|
||||||
|
|
||||||
|
// Undo the move
|
||||||
b.Fields[i].State = FieldState.Empty;
|
b.Fields[i].State = FieldState.Empty;
|
||||||
|
|
||||||
|
// If it is the best move so far, mark it and continue on to the next field
|
||||||
if (Symbol == FieldState.PlayerX && val > bestVal)
|
if (Symbol == FieldState.PlayerX && val > bestVal)
|
||||||
{
|
{
|
||||||
bestMove = i;
|
bestMove = i;
|
||||||
@@ -43,16 +54,22 @@ namespace TicTacToe.Players
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestMove;
|
return bestMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MiniMax is a tree based Algorithm to find the best Outcome for the starting Player,
|
||||||
|
// if both Players play perfectly
|
||||||
|
// Reference: https://www.youtube.com/watch?v=l-hh51ncgDI
|
||||||
private int MiniMax(TicTacToeBoard b, int depth, FieldState player)
|
private int MiniMax(TicTacToeBoard b, int depth, FieldState player)
|
||||||
{
|
{
|
||||||
|
// If the game is Over return the winner value O = -1, No Winner = 0, X = 1
|
||||||
if (depth == 0 | b.IsGameFinished(out var winner))
|
if (depth == 0 | b.IsGameFinished(out var winner))
|
||||||
{
|
{
|
||||||
return (int) winner;
|
return (int) winner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The PlayerX wants to maximize the Value
|
||||||
if (player == FieldState.PlayerX)
|
if (player == FieldState.PlayerX)
|
||||||
{
|
{
|
||||||
int maxEval = int.MinValue;
|
int maxEval = int.MinValue;
|
||||||
@@ -60,14 +77,23 @@ namespace TicTacToe.Players
|
|||||||
{
|
{
|
||||||
if (b.Fields[i].State == FieldState.Empty)
|
if (b.Fields[i].State == FieldState.Empty)
|
||||||
{
|
{
|
||||||
|
// Place the Piece on the Field
|
||||||
b.Fields[i].State = FieldState.PlayerX;
|
b.Fields[i].State = FieldState.PlayerX;
|
||||||
|
|
||||||
|
// See how the perfect Enemy will respond to that placement
|
||||||
int eval = MiniMax(b, depth - 1, FieldState.PlayerO);
|
int eval = MiniMax(b, depth - 1, FieldState.PlayerO);
|
||||||
|
|
||||||
|
// Undo the placement
|
||||||
b.Fields[i].State = FieldState.Empty;
|
b.Fields[i].State = FieldState.Empty;
|
||||||
|
|
||||||
|
// Update the max evaluated Value
|
||||||
maxEval = Math.Max(maxEval, eval);
|
maxEval = Math.Max(maxEval, eval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxEval;
|
return maxEval;
|
||||||
}
|
}
|
||||||
|
// The PlayerO wants to minimize the Value
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int minEval = int.MaxValue;
|
int minEval = int.MaxValue;
|
||||||
@@ -75,9 +101,16 @@ namespace TicTacToe.Players
|
|||||||
{
|
{
|
||||||
if (b.Fields[i].State == FieldState.Empty)
|
if (b.Fields[i].State == FieldState.Empty)
|
||||||
{
|
{
|
||||||
|
// Place the Piece on the Field
|
||||||
b.Fields[i].State = FieldState.PlayerO;
|
b.Fields[i].State = FieldState.PlayerO;
|
||||||
|
|
||||||
|
// See how the perfect Enemy will respond to that placement
|
||||||
int eval = MiniMax(b, depth - 1, FieldState.PlayerX);
|
int eval = MiniMax(b, depth - 1, FieldState.PlayerX);
|
||||||
|
|
||||||
|
// Undo the placement
|
||||||
b.Fields[i].State = FieldState.Empty;
|
b.Fields[i].State = FieldState.Empty;
|
||||||
|
|
||||||
|
// Update the min evaluated Value
|
||||||
minEval = Math.Min(minEval, eval);
|
minEval = Math.Min(minEval, eval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,6 +120,7 @@ namespace TicTacToe.Players
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No Cleanup needed
|
||||||
public override void CleanUp()
|
public override void CleanUp()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ namespace TicTacToe.Players
|
|||||||
|
|
||||||
public override int MakeMove(TicTacToeBoard _)
|
public override int MakeMove(TicTacToeBoard _)
|
||||||
{
|
{
|
||||||
|
// Just make a random move
|
||||||
|
// It doesnt need to get checked because the TicTacToe Class handles the input by itself
|
||||||
return _random.Next(9);
|
return _random.Next(9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No Cleanup is needed
|
||||||
public override void CleanUp() { }
|
public override void CleanUp() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,10 +15,14 @@ namespace TicTacToe.Players
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Where do you want to put your {Symbol} ?");
|
Console.WriteLine($"Where do you want to put your {Symbol} ?");
|
||||||
|
|
||||||
|
// This is because the highest number is 9 so we always only need one Key to play
|
||||||
var key = Console.ReadKey(true);
|
var key = Console.ReadKey(true);
|
||||||
loop = int.TryParse(key.KeyChar.ToString(), out result);
|
loop = int.TryParse(key.KeyChar.ToString(), out result);
|
||||||
|
|
||||||
} while (!loop);
|
} while (!loop);
|
||||||
|
// Moves are Zero based, but are Shown One based
|
||||||
|
// So we subtract one to get to the Zero based Move
|
||||||
return result-1;
|
return result-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
// TODO: Comment this, ooh god ohh fuck ooh god pls no
|
||||||
|
|
||||||
namespace TicTacToe.Players
|
namespace TicTacToe.Players
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ namespace TicTacToe
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"Player {player.Name}: {Scores[player]}");
|
Console.WriteLine($"Player {player.Name}: {Scores[player]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Do CleanUp for every Player (Mostly needed for LearningAi to update the brain)
|
||||||
private void CleanUp()
|
private void CleanUp()
|
||||||
{
|
{
|
||||||
foreach (var player in Players)
|
foreach (var player in Players)
|
||||||
@@ -40,18 +40,24 @@ namespace TicTacToe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the game is finished, and if so add Scores and more for LearningAi
|
||||||
public override void CheckGameState()
|
public override void CheckGameState()
|
||||||
{
|
{
|
||||||
if (!_board.IsGameFinished(out var winnerState)) return;
|
if (!_board.IsGameFinished(out var winnerState)) return;
|
||||||
|
|
||||||
_gameFinished = true;
|
_gameFinished = true;
|
||||||
|
|
||||||
|
// Get the winner (Player? <- ? because the player could be null)
|
||||||
var winner = Players.FirstOrDefault(x => x.Symbol == winnerState);
|
var winner = Players.FirstOrDefault(x => x.Symbol == winnerState);
|
||||||
if (winner is not null)
|
if (winner is not null)
|
||||||
{
|
{
|
||||||
AddScore(winner);
|
AddScore(winner);
|
||||||
|
|
||||||
|
//If one of the Players was a LearningAiPlayer add the Match to the Ai Brain
|
||||||
var player = Players.FirstOrDefault(x => x.GetType() == typeof(LearningAiPlayer));
|
var player = Players.FirstOrDefault(x => x.GetType() == typeof(LearningAiPlayer));
|
||||||
var learningAiPlayer = player as LearningAiPlayer;
|
var learningAiPlayer = player as LearningAiPlayer;
|
||||||
learningAiPlayer?.SaveToJson(_board);
|
if (learningAiPlayer is not null)
|
||||||
|
learningAiPlayer.SaveToJson(_board);
|
||||||
}
|
}
|
||||||
CleanUp();
|
CleanUp();
|
||||||
}
|
}
|
||||||
@@ -60,14 +66,19 @@ namespace TicTacToe
|
|||||||
{
|
{
|
||||||
if (Players.Count != 2) throw new Exception("Not enough, or too many Players");
|
if (Players.Count != 2) throw new Exception("Not enough, or too many Players");
|
||||||
_board = new TicTacToeBoard();
|
_board = new TicTacToeBoard();
|
||||||
|
|
||||||
|
// Draw the Board so the first Player can see it
|
||||||
_board.DrawBoard();
|
_board.DrawBoard();
|
||||||
_gameFinished = false;
|
_gameFinished = false;
|
||||||
int round = 0;
|
int round = 0;
|
||||||
|
|
||||||
while (_gameFinished is not true)
|
while (_gameFinished is not true)
|
||||||
{
|
{
|
||||||
|
// For both Players
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
|
// As long as the Player has not played an allowed move,
|
||||||
|
// he is asked to repeat his move
|
||||||
bool placed;
|
bool placed;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -77,6 +88,7 @@ namespace TicTacToe
|
|||||||
round++;
|
round++;
|
||||||
_board.DrawBoard();
|
_board.DrawBoard();
|
||||||
CheckGameState();
|
CheckGameState();
|
||||||
|
//Dont let the second Player make his turn if the game is already finished
|
||||||
if (_gameFinished) break;
|
if (_gameFinished) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using TicTacToe.Players;
|
using TicTacToe.Players;
|
||||||
|
|
||||||
namespace TicTacToe
|
namespace TicTacToe
|
||||||
@@ -12,6 +11,7 @@ namespace TicTacToe
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < Fields.Length; i++)
|
for (int i = 0; i < Fields.Length; i++)
|
||||||
{
|
{
|
||||||
|
//Initialise all Fields and give them the Symbol on which Position they are
|
||||||
Fields[i] = new Field(Convert.ToChar((i+1).ToString()));
|
Fields[i] = new Field(Convert.ToChar((i+1).ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ namespace TicTacToe
|
|||||||
$"{Fields[8].Symbol}");
|
$"{Fields[8].Symbol}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Tries to mark a position, if the position is not free it returns a false
|
||||||
public bool TryPlace(int pos, Player player, int round)
|
public bool TryPlace(int pos, Player player, int round)
|
||||||
{
|
{
|
||||||
if (pos < 0 || pos > 9) return false;
|
if (pos < 0 || pos > 9) return false;
|
||||||
@@ -78,7 +79,6 @@ namespace TicTacToe
|
|||||||
|
|
||||||
private bool IsBoardFull()
|
private bool IsBoardFull()
|
||||||
{
|
{
|
||||||
//return !_fields.Any(x => x.State == FieldState.Empty);
|
|
||||||
bool isBoardFull = true;
|
bool isBoardFull = true;
|
||||||
foreach (var field in Fields)
|
foreach (var field in Fields)
|
||||||
{
|
{
|
||||||
@@ -174,7 +174,6 @@ namespace TicTacToe
|
|||||||
return IsBoardFull();
|
return IsBoardFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public FieldState[] GetFieldStates()
|
public FieldState[] GetFieldStates()
|
||||||
{
|
{
|
||||||
var output = new FieldState[9];
|
var output = new FieldState[9];
|
||||||
@@ -187,22 +186,5 @@ namespace TicTacToe
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
/*public IList<Field> GetBoardCopy()
|
|
||||||
{
|
|
||||||
return Array.AsReadOnly(_fields);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public object GetBoardCopy()
|
|
||||||
{
|
|
||||||
return this.MemberwiseClone();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetBoard(IList<Field> fields)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 9; i++)
|
|
||||||
{
|
|
||||||
Fields[i] = fields[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user