diff --git a/Solution/CubeService/Controllers/CubeController.cs b/Solution/CubeService/Controllers/CubeController.cs index 75927db..ae5a32e 100644 --- a/Solution/CubeService/Controllers/CubeController.cs +++ b/Solution/CubeService/Controllers/CubeController.cs @@ -1,4 +1,5 @@ -using LibNetCube; +using CubeService.Models; +using LibNetCube; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; @@ -9,16 +10,18 @@ namespace CubeService.Controllers [Route("api/[controller]")] public class CubeController : Controller { - readonly CubePuzzle _cubePuzzle; - public CubeController(CubePuzzle cubePuzzle) : base() + private CubeGameEngine _cubeEngine; + + public CubeController(CubeGameEngine cubeEngine) : base() { - _cubePuzzle = cubePuzzle; + _cubeEngine = cubeEngine; } [HttpPost("[action]")] public IActionResult Reset() { - _cubePuzzle.Reset(); + _cubeEngine.Reset(); + return Ok(); } @@ -45,11 +48,8 @@ public IActionResult ApplyShuffle([FromQuery] string? shuffle = null) moves = ScrambleAlgorithm.GenerateScramble(); } - _cubePuzzle.Reset(); - foreach(CubeMove move in moves) - { - _cubePuzzle.PerformMove(move); - } + _cubeEngine.Reset(); + _cubeEngine.PerformMoveSeries(moves); return Ok(); } @@ -64,7 +64,7 @@ public IActionResult PerformMove([FromQuery] string? move = null) { //attempt to parse move as Enum CubeMove parsedMove = MoveParser.ParseMove(move)!; - _cubePuzzle.PerformMove(parsedMove); + _cubeEngine.PerformMove(parsedMove); return Ok(); } catch @@ -82,16 +82,18 @@ public IActionResult PerformMove([FromQuery] string? move = null) [Route("[action]")] public IActionResult State() { - CubeState state = _cubePuzzle.GetState(); + CubeState state = _cubeEngine.GetCubeState(); Dictionary faces = new Dictionary(); foreach (CubeFace face in CubeState.GetFaceNames()) { - int[,] ints = _cubePuzzle.ReadFace(face); + int[,] ints = _cubeEngine.GetCubeFace(face); int[] formattedInts = ints.Cast().ToArray(); faces.Add(face.ToString(), formattedInts); } return Ok(faces); } + + [HttpGet("[action]")] public IActionResult Face([FromQuery] string? face = null) { @@ -108,9 +110,9 @@ public IActionResult Face([FromQuery] string? face = null) //Not a valid face return BadRequest(); } + //Is a valid face - CubeState state = _cubePuzzle.GetState(); - int[,] ints = _cubePuzzle.ReadFace(parsedFace); + int[,] ints = _cubeEngine.GetCubeFace(parsedFace); int[] formattedInts = ints.Cast().ToArray(); return Ok(formattedInts); } diff --git a/Solution/CubeService/CubeGameEngine.cs b/Solution/CubeService/CubeGameEngine.cs new file mode 100644 index 0000000..b2a5155 --- /dev/null +++ b/Solution/CubeService/CubeGameEngine.cs @@ -0,0 +1,87 @@ +using CubeService.Models; +using LibNetCube; + +namespace CubeService +{ + public class CubeGameEngine + { + private readonly CubePuzzle _cubePuzzle; + private readonly MoveTransactionRepository _repo; + private readonly object _lock = new(); + + public CubeGameEngine() + { + _cubePuzzle = new CubePuzzle(); + _repo = new MoveTransactionRepository(); + HydrateCubeState(); + } + + private void HydrateCubeState() + { + IEnumerable moves = _repo.GetAllMoves(); + + lock (_lock) + { + foreach (MoveTransaction move in moves) + { + if (Enum.TryParse(move.MovePerformed, out var parsed)) + { + _cubePuzzle.PerformMove(parsed); + } + } + } + } + + public void PerformMove(CubeMove move) + { + lock (_lock) + { + _cubePuzzle.PerformMove(move); + _repo.InsertMove(move); + } + } + + public void PerformMoveSeries(IEnumerable moves) + { + lock (_lock) + { + foreach(CubeMove move in moves) + { + _cubePuzzle.PerformMove(move); + } + _repo.InsertMoves(moves); + } + } + + public void Reset() + { + lock (_lock) + { + _cubePuzzle.Reset(); + _repo.ClearAllMoves(); + } + } + + public CubeState GetCubeState() + { + CubeState result; + lock (_lock) + { + result = _cubePuzzle.GetState(); + } + + return result; + } + + public int[,] GetCubeFace(CubeFace face) + { + int[,] result; + lock (_lock) + { + result = _cubePuzzle.ReadFace(face); + } + + return result; + } + } +} diff --git a/Solution/CubeService/CubeService.csproj b/Solution/CubeService/CubeService.csproj index 4277bb9..f15b083 100644 --- a/Solution/CubeService/CubeService.csproj +++ b/Solution/CubeService/CubeService.csproj @@ -6,8 +6,19 @@ enable + + + + + + + + PreserveNewest + + + diff --git a/Solution/CubeService/Models/MoveTransaction.cs b/Solution/CubeService/Models/MoveTransaction.cs new file mode 100644 index 0000000..1982637 --- /dev/null +++ b/Solution/CubeService/Models/MoveTransaction.cs @@ -0,0 +1,9 @@ +namespace CubeService.Models +{ + public class MoveTransaction + { + public int MoveID { get; set; } + public string Timestamp { get; set; } = ""; + public string MovePerformed { get; set; } = ""; + } +} diff --git a/Solution/CubeService/MoveTransactionRepository.cs b/Solution/CubeService/MoveTransactionRepository.cs new file mode 100644 index 0000000..0bc636b --- /dev/null +++ b/Solution/CubeService/MoveTransactionRepository.cs @@ -0,0 +1,57 @@ +using CubeService.Models; +using Dapper; +using LibNetCube; +using Microsoft.Data.Sqlite; + + +namespace CubeService +{ + public class MoveTransactionRepository + { + private const string ConnectionString = "Data Source=cubedatabase.db"; + + public void InsertMove(CubeMove move) + { + using var conn = new SqliteConnection(ConnectionString); + var sql = "INSERT INTO MoveTransactions (Timestamp, MovePerformed) VALUES (@Timestamp, @MovePerformed)"; + conn.Execute(sql, new + { + Timestamp = DateTime.UtcNow.ToString("o"), + MovePerformed = move.ToString() + }); + } + + public void InsertMoves(IEnumerable moves) + { + using var conn = new SqliteConnection(ConnectionString); + conn.Open(); // required for transaction + + using var transaction = conn.BeginTransaction(); + var sql = "INSERT INTO MoveTransactions (Timestamp, MovePerformed) VALUES (@Timestamp, @MovePerformed)"; + + foreach (var move in moves) + { + conn.Execute(sql, new + { + Timestamp = DateTime.UtcNow.ToString("o"), + MovePerformed = move.ToString() + }, transaction); + } + + transaction.Commit(); + } + + + public void ClearAllMoves() + { + using var conn = new SqliteConnection(ConnectionString); + conn.Execute("DELETE FROM MoveTransactions"); + } + + public IEnumerable GetAllMoves() + { + using var conn = new SqliteConnection(ConnectionString); + return conn.Query("SELECT * FROM MoveTransactions ORDER BY Timestamp"); + } + } +} diff --git a/Solution/CubeService/Program.cs b/Solution/CubeService/Program.cs index 7975d6b..ca9fe79 100644 --- a/Solution/CubeService/Program.cs +++ b/Solution/CubeService/Program.cs @@ -1,7 +1,7 @@ var builder = WebApplication.CreateBuilder(args); //Adds a singleton for the cube puzzle, ensures only one is given -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddControllers(); diff --git a/Solution/CubeService/cubedatabase.db b/Solution/CubeService/cubedatabase.db new file mode 100644 index 0000000..293cc09 Binary files /dev/null and b/Solution/CubeService/cubedatabase.db differ diff --git a/misc/netcube-system-design.png b/misc/netcube-system-design.png index 04e9995..543f682 100644 Binary files a/misc/netcube-system-design.png and b/misc/netcube-system-design.png differ