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

Evaluate a game of tic-tac-toe to determine winner

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
toetictacgamedetermineevaluatewinner

Problem

So the program will take input of a series of 9 integers which indicate which square was played on each move. Player 1 is always X and player 2 is always O. Player 1 plays first. Move 1 is first and Move 9 is last, sequentially.
The board looks like this

1 | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9


Winning lines are:

  • 3 Horizontal (1-2-3, 4-5-6, 7-8-9)



  • 3 Vertical (1-4-7, 2-5-8, 3-6-9)



  • 2 Diagonal (1-5-9, 3-5-7)



Sample input

Because of the challenge I was completing, the input includes the first line as the number of games being played, but I've completely ignored it in this implementation.

13
3 5 2 8 6 9 7 1 4
8 3 1 6 2 5 7 4 9
6 4 1 5 8 7 3 2 9
3 1 7 5 9 2 6 4 8
3 2 6 1 4 7 8 5 9
2 7 8 5 3 9 4 1 6
3 6 9 1 5 2 7 4 8
9 2 6 3 5 8 1 7 4
5 8 2 9 1 6 4 7 3
9 7 2 3 4 8 5 1 6
7 1 9 8 4 2 6 5 3
7 3 5 2 6 4 9 1 8
5 1 4 3 6 9 8 7 2


Structure

  • Get input data from txt file (space-delimited)



  • For each game, determine which player played the winning move and at what location. If no winner, draw.



  • Write txt file of winning data



Notes

  • I initially wrote this in excel and it played the games, which is how I could double check this to ensure it's working properly.



  • I'm sure my CheckWin function can be improved dramatically, but I have a simple mind when it comes to this.



  • This is really the first "complex" thing I've written in VB.net, hence the beginner tag, though I did take input from my other questions on the language.



  • I used Environment.NewLine because I couldn't determine how to get something like vbLF to work and so I'm not sure if that's bad practice, using the environment.



The Magic

```
Option Explicit On
Option Strict On
Option Infer On
Option Compare Text

Imports System.IO
Module TicTacToeAnalyzer

Sub Main()
Const INPUT_PATH As String = "C:\Temp\tictactoe.txt"
Const OUTPUT_PATH As String = "C:\Temp\tictactoeResults.txt"
Dim rawGameData As String()
rawGa

Solution

You should consider representing the board as a class instead of an array.

This will allow you to make it more stateful, and extract board-specific logic into the class itself. You can also take your CheckWin method and make it a little more robust / non-repetitive.

Start by defining three helper-methods, RowContaining(ByVal position As Integer), ColumnContaining(ByVal position As Integer) and DiagonalContaining(ByVal position As Integer). What do we return? Simply return an Integer() array on each.

Public Function RowContaining(ByVal position As Integer) As Integer()
    Select Case position
        Case 1, 2, 3
            Return New Integer() { 1, 2, 3 }
        Case 4, 5, 6
            Return New Integer() { 4, 5, 6 }
        case 7, 8, 9
            Return New Integer() { 7, 8, 9 }
    End Select

    Return Nothing
End Function


You should, ideally, create a rows dictionary and put each row in there, then select the row from the dictionary using LINQ, but I'm writing this entire answer outside of an IDE and it's been some time since I've done VB.NET.

The same principle applies to ColumnContaining and DiagonalContaining.

Then, you should make one more method: DirectionsContaining(ByVal position As Integer) As List(Of Integer()) which is similar to the follows:

Public Function DirectionsContaining(ByVal position As Integer) As List(Of Integer())
    Dim result As New List(Of Integer())

    Dim rowContaining As Integer() = RowContaining(position)
    Dim columnContaining As Integer() = ColumnContaining(position)
    Dim diagonalContaining As Integer() = DiagonalContaining(position)

    If rowContaing IsNot Nothing Then
        result.Add(rowContaining)
    End If

    If columnContaining IsNot Nothing Then
        result.Add(columnContaining)
    End If

    If diagonalContaining IsNot Nothing Then
        result.Add(diagonalContaining)
    End If

    Return result
End Function


I think you can see where this is going, simply use LINQ .Any in your CheckWin method and bam, you've extracted the ugly switch statement and conditionals to a much cleaner (and more robust) alternative. Now if, for some reason, you want to add more rows/columns, it's trivial.

In the following snippet:

For iterator As Integer = 0 To boardArray.Length
        move = iterator + 1
        square = moveArray(iterator)
        Select Case move
            Case 1, 3, 5, 7, 9
                player = 1
                boardArray(square) = 1
            Case 2, 4, 6, 8
                player = 2
                boardArray(square) = 2
        End Select
        If move > 4 Then isWon = CheckWin(boardArray, square)
        If isWon Then
            gameWinner = "Game #" & gameNumber & " is won by Player " & player & " on move #" & move & " in square #" & square & "."
            Return gameWinner
        End If
    Next


If performance is not a concern, it's trivial to use:

If move Mod 2 = 1 Then
    ' Player 1 move
Else
    ' Player 2 move
End If


In VB n Mod x is equivalent to C#'s n % x.

This whole method can be cleaned quite a bit:

Sub Main()
    Const INPUT_PATH As String = "C:\Temp\tictactoe.txt"
    Const OUTPUT_PATH As String = "C:\Temp\tictactoeResults.txt"
    Dim rawGameData As String()
    rawGameData = GetInput(INPUT_PATH)
    Dim gameResult As String
    Dim resultData As String()
    ReDim resultData(rawGameData.Length - 1)
    Dim moveData As String
    For i As Integer = 1 To rawGameData.Length - 1
        moveData = rawGameData(i)
        gameResult = PlayGame(moveData, i)
        resultData(i) = gameResult
    Next
    gameResult = resultData(1)
    gameResult = String.Join(Environment.NewLine, resultData).Trim()
    WriteOutput(OUTPUT_PATH, gameResult)
End Sub


First: you only really need i for the "game winner" message, which should be extracted to a different location. In fact, you're PlayGame method only ever returns two variants on the string:

"won by Player " & player & " on move #" & move & " in square #" & square & "."
"a draw."


This whole state can be extracted to a different class, so we'll start there.

Public Class GameResult
    Public Property Winner As GamePlayer
    Public Property LastMoveSquare As Integer
    Public Property LastMoveNumber As Integer
End Class

Public Enum GamePlayer
    None,
    Player1,
    Player2,
End Enum


Now, we've extracted the entire game result to a new class to eliminate the need for string processing. (This is far better for multi-lingual setups, or setups where you just want to dump this result to a DB, etc.)

Notice that I left GameNumber out, that's irrelevant to the result of the game playing method. It doesn't care about the game number, only about it's result.

Next, we'll use this in our Main() method:

```
Sub Main()
Const INPUT_PATH As String = "C:\Temp\tictactoe.txt"
Const OUTPUT_PATH As String = "C:\Temp\tictactoeResults.txt"
Dim rawGameData As St

Code Snippets

Public Function RowContaining(ByVal position As Integer) As Integer()
    Select Case position
        Case 1, 2, 3
            Return New Integer() { 1, 2, 3 }
        Case 4, 5, 6
            Return New Integer() { 4, 5, 6 }
        case 7, 8, 9
            Return New Integer() { 7, 8, 9 }
    End Select

    Return Nothing
End Function
Public Function DirectionsContaining(ByVal position As Integer) As List(Of Integer())
    Dim result As New List(Of Integer())

    Dim rowContaining As Integer() = RowContaining(position)
    Dim columnContaining As Integer() = ColumnContaining(position)
    Dim diagonalContaining As Integer() = DiagonalContaining(position)

    If rowContaing IsNot Nothing Then
        result.Add(rowContaining)
    End If

    If columnContaining IsNot Nothing Then
        result.Add(columnContaining)
    End If

    If diagonalContaining IsNot Nothing Then
        result.Add(diagonalContaining)
    End If

    Return result
End Function
For iterator As Integer = 0 To boardArray.Length
        move = iterator + 1
        square = moveArray(iterator)
        Select Case move
            Case 1, 3, 5, 7, 9
                player = 1
                boardArray(square) = 1
            Case 2, 4, 6, 8
                player = 2
                boardArray(square) = 2
        End Select
        If move > 4 Then isWon = CheckWin(boardArray, square)
        If isWon Then
            gameWinner = "Game #" & gameNumber & " is won by Player " & player & " on move #" & move & " in square #" & square & "."
            Return gameWinner
        End If
    Next
If move Mod 2 = 1 Then
    ' Player 1 move
Else
    ' Player 2 move
End If
Sub Main()
    Const INPUT_PATH As String = "C:\Temp\tictactoe.txt"
    Const OUTPUT_PATH As String = "C:\Temp\tictactoeResults.txt"
    Dim rawGameData As String()
    rawGameData = GetInput(INPUT_PATH)
    Dim gameResult As String
    Dim resultData As String()
    ReDim resultData(rawGameData.Length - 1)
    Dim moveData As String
    For i As Integer = 1 To rawGameData.Length - 1
        moveData = rawGameData(i)
        gameResult = PlayGame(moveData, i)
        resultData(i) = gameResult
    Next
    gameResult = resultData(1)
    gameResult = String.Join(Environment.NewLine, resultData).Trim()
    WriteOutput(OUTPUT_PATH, gameResult)
End Sub

Context

StackExchange Code Review Q#146130, answer score: 2

Revisions (0)

No revisions yet.