patterncsharpMinor
Clean and well-designed Tic-Tac-Toe game
Viewed 0 times
toedesignedtictacgamewellandclean
Problem
My design goal was to try and make a "clean" and well-designed Tic-Tac-Toe game but also to try and use concepts and programming constructs that I'm new to.
I'm planning to implement an "AI Player" class later on. I know that Tic-Tac-Toe is a solved game so there are more effective approaches for AI logic but I've never programmed an AI before so my idea is to at runtime generate a "database" of all possible gamestates, and then have the AI rank each "intermediate gamestate" by the amount of positive outcomes (that is where the AI wins) - negative outcomes when it decides where to place its marker. Would this be a good idea and would it work at all?
Both positive and negative feedback is welcome.
Game.cs
```
public partial class Game : Form
{
#region Fields & Properties
internal static VisualCell[,] vGrid;
internal static Player[] players;
#endregion
#region Constructors
public Game()
{
InitializeComponent();
}
#endregion
#region Instanced Methods
private void Game_Load(object sender, EventArgs e)
{
players = new Player[2];
players[0] = new Player(); // Set to human player or AI player
players[1] = new Player(); // Always human
GameRound.Complete += ShowCompletionDialog;
GenerateGridBtns();
Task roundTask = Task.Factory.StartNew(() => new GameRound().Start());
}
private void ShowCompletionDialog(OutcomeType gameOutcome)
{
long pID = (long)gameOutcome + 1;
string WonText = "Player " + pID + " won!";
if (gameOutcome == OutcomeType.Draw)
{ //HACK: If the marker is empty it means that the passed player was a dummy and game was a draw
WonText = "The game ended in a draw!";
}
DialogResult dialogResult = MessageBox.Show("Play again?", WonText, MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
RestartGame();
// TODO: Add a bunc
I'm planning to implement an "AI Player" class later on. I know that Tic-Tac-Toe is a solved game so there are more effective approaches for AI logic but I've never programmed an AI before so my idea is to at runtime generate a "database" of all possible gamestates, and then have the AI rank each "intermediate gamestate" by the amount of positive outcomes (that is where the AI wins) - negative outcomes when it decides where to place its marker. Would this be a good idea and would it work at all?
Both positive and negative feedback is welcome.
Game.cs
```
public partial class Game : Form
{
#region Fields & Properties
internal static VisualCell[,] vGrid;
internal static Player[] players;
#endregion
#region Constructors
public Game()
{
InitializeComponent();
}
#endregion
#region Instanced Methods
private void Game_Load(object sender, EventArgs e)
{
players = new Player[2];
players[0] = new Player(); // Set to human player or AI player
players[1] = new Player(); // Always human
GameRound.Complete += ShowCompletionDialog;
GenerateGridBtns();
Task roundTask = Task.Factory.StartNew(() => new GameRound().Start());
}
private void ShowCompletionDialog(OutcomeType gameOutcome)
{
long pID = (long)gameOutcome + 1;
string WonText = "Player " + pID + " won!";
if (gameOutcome == OutcomeType.Draw)
{ //HACK: If the marker is empty it means that the passed player was a dummy and game was a draw
WonText = "The game ended in a draw!";
}
DialogResult dialogResult = MessageBox.Show("Play again?", WonText, MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
RestartGame();
// TODO: Add a bunc
Solution
To answer the "bonus question" that may not be the right algorithm for the AI: it assumes that the human player is playing randomly.
As a human player, I play to a) not-lose b) win. That's why most games (between experienced humans) end in a draw.
With the algorithm you suggested, there might be a move which results in 10 possible wins (if I play stupidly/randomly) and 1 loss (if I play cleverly). Because the win/loss ratio is high (10-to-1) you might pick it and (because I'm not stupid) I then play the correct move and win.
IMO the algorithm you want is to choose the move which allows you to prevent my winning no matter what I do, i.e. a move which allows you to guarantee you can't lose.
Mandatory code review:
I'm surprised that plays and vGrid are static inside Game. What if there's more than one Game instance?
It's difficult to assess the lifetime of a Game: why doesn't a Game include a Grid? Instead Game instantiates a GameRound as a local/temporary, which it passes to a local/temporary Task?
The Cell extension methods like
What happens if the player plays on an already-marked cell? It looks like nothing happens, except that they lose their turn?
I suspect this could be a lot simpler/clearer (but I'm not tempted to rewrite it at the moment).
As a human player, I play to a) not-lose b) win. That's why most games (between experienced humans) end in a draw.
With the algorithm you suggested, there might be a move which results in 10 possible wins (if I play stupidly/randomly) and 1 loss (if I play cleverly). Because the win/loss ratio is high (10-to-1) you might pick it and (because I'm not stupid) I then play the correct move and win.
IMO the algorithm you want is to choose the move which allows you to prevent my winning no matter what I do, i.e. a move which allows you to guarantee you can't lose.
Mandatory code review:
I'm surprised that plays and vGrid are static inside Game. What if there's more than one Game instance?
It's difficult to assess the lifetime of a Game: why doesn't a Game include a Grid? Instead Game instantiates a GameRound as a local/temporary, which it passes to a local/temporary Task?
The Cell extension methods like
int DiagonalRelatives(this Cell cell, Grid grid) might be more naturally ordinary methods of the Grid class.What happens if the player plays on an already-marked cell? It looks like nothing happens, except that they lose their turn?
I suspect this could be a lot simpler/clearer (but I'm not tempted to rewrite it at the moment).
Context
StackExchange Code Review Q#45091, answer score: 5
Revisions (0)
No revisions yet.