Skip to content

Commit

Permalink
Refactored TicTacToe API, Business Logic and NUnitTests to support Co…
Browse files Browse the repository at this point in the history
…mputer vs Computer games
  • Loading branch information
JosephWee committed Apr 25, 2023
1 parent f611190 commit 0f67c97
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 206 deletions.
224 changes: 138 additions & 86 deletions NUnitTest/TicTacToeTests.cs

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions Tic-Tac-Toe.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TicTacToeML", "TicTacToeML\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TicTacToeBL", "TicTacToeBL\TicTacToeBL.csproj", "{D722A0C3-1CD7-47ED-BEAF-9428BC2F3AC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnitTest", "NUnitTest\NUnitTest.csproj", "{E3DDE56F-39E3-4D7C-9C97-039F5E77BF19}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "WebApi\WebApi.csproj", "{4033DA58-FFC6-4D29-A367-EDBD5E442F69}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServerApp", "BlazorServerApp\BlazorServerApp.csproj", "{CCFDDB0C-6D7A-48EA-B098-3CA209031878}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TicTacToe.Distribution", "TicTacToe.Distribution\TicTacToe.Distribution.csproj", "{DA2FFA93-46F8-43C1-8CF6-B9958FFB9D91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnitTest", "NUnitTest\NUnitTest.csproj", "{5E66F398-F3F4-4AB7-BC7F-7A2187CD73FB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -29,10 +29,6 @@ Global
{D722A0C3-1CD7-47ED-BEAF-9428BC2F3AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D722A0C3-1CD7-47ED-BEAF-9428BC2F3AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D722A0C3-1CD7-47ED-BEAF-9428BC2F3AC5}.Release|Any CPU.Build.0 = Release|Any CPU
{E3DDE56F-39E3-4D7C-9C97-039F5E77BF19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3DDE56F-39E3-4D7C-9C97-039F5E77BF19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3DDE56F-39E3-4D7C-9C97-039F5E77BF19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3DDE56F-39E3-4D7C-9C97-039F5E77BF19}.Release|Any CPU.Build.0 = Release|Any CPU
{4033DA58-FFC6-4D29-A367-EDBD5E442F69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4033DA58-FFC6-4D29-A367-EDBD5E442F69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4033DA58-FFC6-4D29-A367-EDBD5E442F69}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -45,6 +41,10 @@ Global
{DA2FFA93-46F8-43C1-8CF6-B9958FFB9D91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA2FFA93-46F8-43C1-8CF6-B9958FFB9D91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA2FFA93-46F8-43C1-8CF6-B9958FFB9D91}.Release|Any CPU.Build.0 = Release|Any CPU
{5E66F398-F3F4-4AB7-BC7F-7A2187CD73FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E66F398-F3F4-4AB7-BC7F-7A2187CD73FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E66F398-F3F4-4AB7-BC7F-7A2187CD73FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E66F398-F3F4-4AB7-BC7F-7A2187CD73FB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
3 changes: 3 additions & 0 deletions TicTacToeBL/BusinessLogic/ComputerPlayerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public virtual int GetMove(string InstanceId)
{
var ds = TicTacToe.GetAndValidatePreviousMove(InstanceId);

if (!ds.Any())
return -1;

int GridSize = ds.First().GridSize;
List<int> CellStates = ds.Select(x => x.CellContent).ToList();
int BlankCellCount = int.MinValue;
Expand Down
3 changes: 3 additions & 0 deletions TicTacToeBL/BusinessLogic/ComputerPlayerV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public override int GetMove(string InstanceId)
{
var ds = TicTacToe.GetAndValidatePreviousMove(InstanceId);

if (!ds.Any())
return -1;

int GridSize = ds.First().GridSize;
List<int> CellStates = ds.Select(x => x.CellContent).ToList();
int BlankCellCount = int.MinValue;
Expand Down
3 changes: 3 additions & 0 deletions TicTacToeBL/BusinessLogic/ComputerPlayerV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public override int GetMove(string InstanceId)
{
var ds = TicTacToe.GetAndValidatePreviousMove(InstanceId);

if (!ds.Any())
return -1;

int GridSize = ds.First().GridSize;
List<int> CellStates = ds.Select(x => x.CellContent).ToList();
int BlankCellCount = int.MinValue;
Expand Down
124 changes: 121 additions & 3 deletions TicTacToeBL/BusinessLogic/TicTacToe.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System;
using Microsoft.ML;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TicTacToe.Entity;
using T3Ent = TicTacToe.Entity;
using T3ML = TicTacToe.ML;
using T3Mod = TicTacToe.Models;

namespace TicTacToe.BusinessLogic
{
Expand All @@ -18,10 +22,124 @@ public static IReadOnlyList<int> ValidCellStateValues
}
}

public static T3Mod.TicTacToeUpdateResponse ProcessRequest(T3Mod.TicTacToeUpdateRequest request, ComputerPlayerBase computerPlayer, string MLNetModelPath)
{
T3Mod.TicTacToeUpdateResponse response = null;

var latestMove = new List<T3Ent.TicTacToeDataEntry>();
TicTacToe.ValidateTicTacToeUpdateRequest(request, computerPlayer, out latestMove);

int moveNumber = latestMove.Any() ? latestMove.Max(x => x.MoveNumber) : 0;
int cellsChanged = 0;
latestMove.ForEach(
x =>
{
if (x.CellContent != request.CellStates[x.CellIndex])
cellsChanged++;
});

if (!latestMove.Any() || cellsChanged == 1)
{
// Save: New Game or New Move
moveNumber++;
TicTacToe.SaveToDatabase(request.InstanceId, request.GridSize, moveNumber, request.CellStates);
}

// Evaluate Current Game Outcome
var response1 =
TicTacToe.EvaluateResult(request, computerPlayer);
response = response1;

if (request.NumberOfPlayers == 1
&& (!latestMove.Any() || cellsChanged == 1)
&& response1.Status == T3Mod.TicTacToeGameStatus.InProgress)
{
// Computer Player Moves
int? ComputerMove =
computerPlayer.GetMove(request.InstanceId);

if (ComputerMove.HasValue && request.CellStates[ComputerMove.Value] == 0)
{
var CellStates = request.CellStates.ToList();
CellStates[ComputerMove.Value] = computerPlayer.PlayerSymbolSelf;

//Save Computer Player's move
moveNumber++;
TicTacToe.SaveToDatabase(request.InstanceId, request.GridSize, moveNumber, CellStates);

int BlankCellCount2 = int.MinValue;
List<int> WinningCells2 = new List<int>();

var response2 =
new T3Mod.TicTacToeUpdateResponse()
{
Status = TicTacToe.EvaluateResult(computerPlayer, request.GridSize, CellStates, out BlankCellCount2, out WinningCells2),
ComputerMove = ComputerMove,
WinningCells = WinningCells2
};

response = response2;
}
}

var cellStates = request.CellStates.ToList();

if (!cellStates.Any(x => !TicTacToe.ValidCellStateValues.Contains(x)))
{
if (response.ComputerMove.HasValue)
{
cellStates[response.ComputerMove.Value] = computerPlayer.PlayerSymbolSelf;
}

var mlContext = new MLContext();
ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _);
var predEngine = mlContext.Model.CreatePredictionEngine<T3ML.MLModel1.ModelInput, T3ML.MLModel1.ModelOutput>(mlModel);

var inputModel1 =
new T3ML.MLModel1.ModelInput()
{
MoveNumber = moveNumber,
Cell0 = request.CellStates[0],
Cell1 = request.CellStates[1],
Cell2 = request.CellStates[2],
Cell3 = request.CellStates[3],
Cell4 = request.CellStates[4],
Cell5 = request.CellStates[5],
Cell6 = request.CellStates[6],
Cell7 = request.CellStates[7],
Cell8 = request.CellStates[8],
GameResultCode = 0
};

//var inputModel2 =
// new MLModel2.ModelInput()
// {
// Cell0 = value.CellStates[0],
// Cell1 = value.CellStates[1],
// Cell2 = value.CellStates[2],
// Cell3 = value.CellStates[3],
// Cell4 = value.CellStates[4],
// Cell5 = value.CellStates[5],
// Cell6 = value.CellStates[6],
// Cell7 = value.CellStates[7],
// Cell8 = value.CellStates[8],
// GameResultCode = 0
// };

// Get Prediction
var prediction1 = predEngine.Predict(inputModel1);

response.Prediction = prediction1.PredictedLabel;
response.PredictionScore = prediction1.Score;
}

return response;
}

public static Models.TicTacToeUpdateResponse EvaluateResult(Models.TicTacToeUpdateRequest request, ComputerPlayerBase computerPlayer)
{
//Validate TicTacToeUpdateRequest
var latestMove = new List<TicTacToeDataEntry>();
var latestMove = new List<T3Ent.TicTacToeDataEntry>();
TicTacToe.ValidateTicTacToeUpdateRequest(request, computerPlayer, out latestMove);

List<int> WinningCells = null;
Expand Down
114 changes: 3 additions & 111 deletions WebApi/Controllers/TicTacToeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using T3ML = TicTacToe.ML;
using T3Mod = TicTacToe.Models;
using System.Reflection.Metadata.Ecma335;
using TicTacToe.BusinessLogic;

namespace WebApi.Controllers
{
Expand Down Expand Up @@ -59,118 +60,9 @@ public ActionResult Post([FromBody] TicTacToe.Models.TicTacToeUpdateRequest valu
if (value == null)
return BadRequest();

T3Mod.TicTacToeUpdateResponse retVal = null;

//var computerPlayer = new T3BL.ComputerPlayerV2();
var computerPlayer = new T3BL.ComputerPlayerV3(_MLNetModelPath);

var latestMove = new List<T3Ent.TicTacToeDataEntry>();
T3BL.TicTacToe.ValidateTicTacToeUpdateRequest(value, computerPlayer, out latestMove);

int moveNumber = latestMove.Any() ? latestMove.Max(x => x.MoveNumber) : 0;
int cellsChanged = 0;
latestMove.ForEach(
x =>
{
if (x.CellContent != value.CellStates[x.CellIndex])
cellsChanged++;
});

if (!latestMove.Any() || cellsChanged == 1)
{
// Save: New Game or New Move
moveNumber++;
T3BL.TicTacToe.SaveToDatabase(value.InstanceId, value.GridSize, moveNumber, value.CellStates);
}

// Evaluate Current Game Outcome
var response1 =
T3BL.TicTacToe.EvaluateResult(value, computerPlayer);

retVal = response1;

if (value.NumberOfPlayers == 1
&& (!latestMove.Any() || cellsChanged == 1)
&& response1.Status == T3Mod.TicTacToeGameStatus.InProgress)
{
// Computer Player Moves
int? ComputerMove =
computerPlayer.GetMove(value.InstanceId);

if (ComputerMove.HasValue && value.CellStates[ComputerMove.Value] == 0)
{
var CellStates = value.CellStates.ToList();
CellStates[ComputerMove.Value] = computerPlayer.PlayerSymbolSelf;

//Save Computer Player's move
moveNumber++;
T3BL.TicTacToe.SaveToDatabase(value.InstanceId, value.GridSize, moveNumber, CellStates);

int BlankCellCount2 = int.MinValue;
List<int> WinningCells2 = new List<int>();

var response2 =
new T3Mod.TicTacToeUpdateResponse()
{
Status = T3BL.TicTacToe.EvaluateResult(computerPlayer, value.GridSize, CellStates, out BlankCellCount2, out WinningCells2),
ComputerMove = ComputerMove,
WinningCells = WinningCells2
};

retVal = response2;
}
}

var cellStates = value.CellStates.ToList();

if (!cellStates.Any(x => !T3BL.TicTacToe.ValidCellStateValues.Contains(x)))
{
if (retVal.ComputerMove.HasValue)
{
cellStates[retVal.ComputerMove.Value] = computerPlayer.PlayerSymbolSelf;
}

var mlContext = new MLContext();
ITransformer mlModel = mlContext.Model.Load(_MLNetModelPath, out var _);
var predEngine = mlContext.Model.CreatePredictionEngine<T3ML.MLModel1.ModelInput, T3ML.MLModel1.ModelOutput>(mlModel);

var inputModel1 =
new T3ML.MLModel1.ModelInput()
{
MoveNumber = moveNumber,
Cell0 = value.CellStates[0],
Cell1 = value.CellStates[1],
Cell2 = value.CellStates[2],
Cell3 = value.CellStates[3],
Cell4 = value.CellStates[4],
Cell5 = value.CellStates[5],
Cell6 = value.CellStates[6],
Cell7 = value.CellStates[7],
Cell8 = value.CellStates[8],
GameResultCode = 0
};

//var inputModel2 =
// new MLModel2.ModelInput()
// {
// Cell0 = value.CellStates[0],
// Cell1 = value.CellStates[1],
// Cell2 = value.CellStates[2],
// Cell3 = value.CellStates[3],
// Cell4 = value.CellStates[4],
// Cell5 = value.CellStates[5],
// Cell6 = value.CellStates[6],
// Cell7 = value.CellStates[7],
// Cell8 = value.CellStates[8],
// GameResultCode = 0
// };

// Get Prediction
var prediction1 = predEngine.Predict(inputModel1);

retVal.Prediction = prediction1.PredictedLabel;
retVal.PredictionScore = prediction1.Score;
}
var computerPlayer = new ComputerPlayerV3(_MLNetModelPath);
var retVal = T3BL.TicTacToe.ProcessRequest(value, computerPlayer, _MLNetModelPath);

return Ok(retVal);
}
Expand Down

0 comments on commit 0f67c97

Please sign in to comment.