HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Clean and well-designed Tic-Tac-Toe game

Submitted by: @import:stackexchange-codereview··
0
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

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 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.