The Kernal Of Tic-Tac-Toe (Part 2)
I added a new game state ReadyToPlay because I had a issue with the kernel outputting the blank board.
I then updated the TicTacToe class to utilize the new game state.
Then corrected a few bugs
and re-factored the code.
This had the effect of slightly increasing the line of code. (But hey it still tiny.)
Simple Console Game Implementation
Let's implement a tic-tac-toe game that user can interact with, a utilize the kernel we previously wrote.
It's a two human player play model.
Really simple loops construct here.
Add some event handlers to handle the events the Kernel raises.
Slightly fancy code here to handle both an in game board and a winning board.
If it is a winning board, it render the background of the winning squares yellow.
Then a couple of helper extension methods.
Statistics
Future Tasks
If any of the tasks take your fancy, have a code. See what you produce.
I added a new game state ReadyToPlay because I had a issue with the kernel outputting the blank board.
Public Enum GameStates ReadyToPlay Playing Win_Player1 Win_Player2 Drawn Invalid_State Invalid_Move End Enum
I then updated the TicTacToe class to utilize the new game state.
Then corrected a few bugs
Spoiler
and re-factored the code.
Public Class TicTacToe
Dim _TicTacToeBoard(8) As Markers, _CurrentPlayer As Markers = Markers.Player_1
Dim _GameState As GameStates = GameStates.ReadyToPlay
Dim _FatalInvalidMoves As Boolean
Public Sub New(Optional FatalInvalidMoves As Boolean = False)
_FatalInvalidMoves = FatalInvalidMoves
End Sub
Public ReadOnly Property CurrentPlayer As Markers
Get
Return _CurrentPlayer
End Get
End Property
Public Property GameState As GameStates
Get
Return _GameState
End Get
Private Set(value As GameStates)
If value = _GameState Then Exit Property
_GameState = value
RaiseEvent GameStateChanged(Me, New GameStateChangedArgs())
End Set
End Property
Private Event GameStateChanged(ticTacToe As TicTacToe, e As GameStateChangedArgs)
Public Event BoardChanged(sender As TicTacToe, e As BoardChangedEventArgs)
Public Event PlayerWin(sender As TicTacToe, e As PlayerWinEventArgs)
Public Event GameDrawn(sender As TicTacToe, e As EventArgs)
Private Sub TicTacToe_GameStateChanged(ticTacToe As TicTacToe, e As GameStateChangedArgs) Handles Me.GameStateChanged
If CType(ticTacToe, TicTacToe).GameState = GameStates.Drawn Then RaiseEvent GameDrawn(Me, EventArgs.Empty)
End Sub
Private Sub TicTacToe_PlayerWin(sender As Object, e As PlayerWinEventArgs) Handles Me.PlayerWin
If e.Player = Markers.Player_1 Then Me.GameState = GameStates.Win_Player1
If e.Player = Markers.Player_2 Then Me.GameState = GameStates.Win_Player2
End Sub
Default Public ReadOnly Property Square(ByVal i As Integer) As Markers
Get
If (0 <= i) AndAlso (i < 9) Then Return _TicTacToeBoard(i)
Return Markers.Empty
End Get
End Property
Public Sub Start()
If _GameState <> GameStates.ReadyToPlay Then Return
RaiseEvent BoardChanged(Me, New BoardChangedEventArgs(_TicTacToeBoard.AsEnumerable))
GameState = GameStates.Playing
End Sub
Public Function PlayMove(PlayedBy As Markers, PlayingSquare As Integer) As Boolean
If (_GameState <> GameStates.Playing) OrElse (_CurrentPlayer <> PlayedBy) Then Me.GameState = GameStates.Invalid_State : Return False
If Not ((0 <= PlayingSquare) AndAlso (PlayingSquare <= 8)) OrElse (_TicTacToeBoard(PlayingSquare) <> Markers.Empty) Then
If _FatalInvalidMoves Then Me.GameState = GameStates.Invalid_Move
Return False
Else
PlaceMarkerOnToGameBoard(PlayedBy, PlayingSquare) : Return True
End If
End Function
Private Sub PlaceMarkerOnToGameBoard(PlayedBy As Markers, PlayingSquare As Integer)
_TicTacToeBoard(PlayingSquare) = PlayedBy
RaiseEvent BoardChanged(Me, New BoardChangedEventArgs(_TicTacToeBoard.AsEnumerable))
CheckForPlayerWin(PlayedBy)
If GameIsDrawn() Then Me.GameState = GameStates.Drawn : Exit Sub
_CurrentPlayer = WhichPlayerIsNext(_CurrentPlayer)
End Sub
Private Function WhichPlayerIsNext(player As Markers) As Markers
Return DoWhatNext(player, Markers.Player_2, Markers.Player_1, player)
End Function
Private Function DoWhatNext(Of T)(player As Markers, p1 As T, p2 As T, defaultTo As T) As T
Return If(player = Markers.Player_1, p1, If(player = Markers.Player_2, p2, defaultTo))
End Function
Private Function GameIsDrawn() As Boolean
Return Not _TicTacToeBoard.Any(Function(square) square = Markers.Empty)
End Function
Private Sub CheckForPlayerWin(player As Markers)
Dim winLines = {({0, 1, 2}), ({3, 4, 5}), ({6, 7, 8}), ({0, 3, 6}), ({1, 4, 7}), ({2, 5, 8}), ({0, 4, 8}), ({2, 4, 6})}
Dim winningLines As New List(Of Integer())(winLines.Where(Function(WinningLine) CheckLine(player, WinningLine)))
If winningLines.Any Then
Me.GameState = DoWhatNext(player, GameStates.Win_Player1, GameStates.Win_Player2, GameStates.Invalid_State)
If (GameState = GameStates.Win_Player1) OrElse (GameState = GameStates.Win_Player2) Then RaiseEvent PlayerWin(Me, New PlayerWinEventArgs(player, winningLines))
End If
End Sub
Private Function CheckLine(player As Markers, ParamArray squares() As Integer) As Boolean
Return squares.All(Function(sqr) _TicTacToeBoard(sqr) = player)
End Function
End Class
This had the effect of slightly increasing the line of code. (But hey it still tiny.)
Simple Console Game Implementation
Let's implement a tic-tac-toe game that user can interact with, a utilize the kernel we previously wrote.
It's a two human player play model.
Module Module1
Dim WithEvents Game As TicTacToe.TicTacToe
Sub Main()
Game = New TicTacToe.TicTacToe()
Game.Start()
While Game.GameState = TicTacToe.GameStates.Playing
Dim k As ConsoleKeyInfo
Do
Do
k = Console.ReadKey(False)
Loop Until ("0"c <= k.KeyChar) AndAlso (k.KeyChar < "9"c)
Loop Until Game.PlayMove(Game.CurrentPlayer, Integer.Parse(k.KeyChar))
End While
Console.ReadKey()
End Sub
Really simple loops construct here.
Private Sub Game_BoardChanged(sender As TicTacToe.TicTacToe, e As TicTacToe.BoardChangedEventArgs) Handles Game.BoardChanged
DrawBoard(sender)
End Sub
Private Sub Game_GameDrawn(sender As TicTacToe.TicTacToe, e As EventArgs) Handles Game.GameDrawn
Console.WriteLine("Drawn")
End Sub
Private Sub Game_PlayerWin(sender As TicTacToe.TicTacToe, e As TicTacToe.PlayerWinEventArgs) Handles Game.PlayerWin
DrawBoard(sender, e.WinningLines.SelectMany(Function(x) x).Distinct.ToArray)
End Sub
Add some event handlers to handle the events the Kernel raises.
Private Sub DrawBoard(board As TicTacToe.TicTacToe, Optional winsqr As Integer() = Nothing)
Console.WriteLine()
winsqr = If(winsqr, {})
For i = 0 To 8
Console.BackgroundColor = If(winsqr.Contains(i), ConsoleColor.Yellow, ConsoleColor.Black)
Console.Write(board(i).MarkerToChar._If(Function(c) c = " ", i.ToString()(0))) ' <-- Remove the _If section to leave spaces instead
Console.BackgroundColor = ConsoleColor.Black
If (i Mod 3) < 2 Then
Console.Write("|")
Else
Console.WriteLine()
Console.WriteLine(If(i < 8, "-+-+-", ""))
End If
Next
Console.WriteLine()
End Sub
Slightly fancy code here to handle both an in game board and a winning board.
If it is a winning board, it render the background of the winning squares yellow.
Then a couple of helper extension methods.
<Runtime.CompilerServices.Extension>
Public Function _If(Of T)(feed As T, Pred As Func(Of T, Boolean), successful As T) As T
Return If(Pred(feed), successful, feed)
End Function
<Runtime.CompilerServices.Extension()>
Private Function MarkerToChar(m As TicTacToe.Markers) As Char
If m = TicTacToe.Markers.Empty Then Return " "c
If m = TicTacToe.Markers.Player_1 Then Return "X"c
If m = TicTacToe.Markers.Player_2 Then Return "O"c
Return Nothing
End Function
End Module
Statistics
IDE LoC
The Console Game :- 50 LoC
The Kernel :- 81 LoC
---
131 LoC
---
The EventArgs :- 50 LoC
---
181 LoC
Compiled
--------
The Kernel :- 102 IL ( CheckForPlayerWin :- 42 IL (Biggest))
The Console Game :- 37 IL
---
139 IL
Future Tasks
- A Control based Implementation
- A Computer / Robot Player.
- Computer vs Robot.
If any of the tasks take your fancy, have a code. See what you produce.
0 Comments On This Entry
|
|



Leave Comment









|