patterncsharpMinor
Evaluate a game of tic-tac-toe to determine winner
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
The board looks like this
Winning lines are:
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.
Structure
Notes
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
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 | 9Winning 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 2Structure
- 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
CheckWinfunction 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.NewLinebecause I couldn't determine how to get something likevbLFto 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
Start by defining three helper-methods,
You should, ideally, create a
The same principle applies to
Then, you should make one more method:
I think you can see where this is going, simply use LINQ
In the following snippet:
If performance is not a concern, it's trivial to use:
In VB
This whole method can be cleaned quite a bit:
First: you only really need
This whole state can be extracted to a different class, so we'll start there.
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
Next, we'll use this in our
```
Sub Main()
Const INPUT_PATH As String = "C:\Temp\tictactoe.txt"
Const OUTPUT_PATH As String = "C:\Temp\tictactoeResults.txt"
Dim rawGameData As St
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 FunctionYou 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 FunctionI 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
NextIf performance is not a concern, it's trivial to use:
If move Mod 2 = 1 Then
' Player 1 move
Else
' Player 2 move
End IfIn 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 SubFirst: 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 EnumNow, 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 FunctionPublic 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 FunctionFor 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
NextIf move Mod 2 = 1 Then
' Player 1 move
Else
' Player 2 move
End IfSub 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 SubContext
StackExchange Code Review Q#146130, answer score: 2
Revisions (0)
No revisions yet.