diff --git a/GameData/ApiClient.cs b/GameData/ApiClient.cs index bdb1c0a..ed03793 100644 --- a/GameData/ApiClient.cs +++ b/GameData/ApiClient.cs @@ -1,9 +1,11 @@ -using GameData.Repository; +using GameData.Model; +using GameData.Repository; using GameModel.Settings; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http.Headers; using System.Reflection; @@ -134,17 +136,26 @@ namespace GameData return res; } - public async Task Post(object payload, string serviceUrl, bool suppressExceptions = true) + /// + /// Create an item + /// https://docs.directus.io/reference/items.html#create-an-item + /// + /// + /// + /// + /// + /// + public T Post(object payload, string serviceUrl, bool suppressExceptions = false) { T res = default; try { - var response = await client.SendAsync(GetHttpRequestMessage(HttpMethod.Post, serviceUrl, payload)); + var response = client.Send(GetHttpRequestMessage(HttpMethod.Post, serviceUrl, payload)); if (response.IsSuccessStatusCode) { - using (var responseStream = await response.Content.ReadAsStreamAsync()) + using (var responseStream = response.Content.ReadAsStream()) { using (var streamReader = new StreamReader(responseStream)) { @@ -158,7 +169,7 @@ namespace GameData } else { - var errorResult = await response.Content.ReadAsStringAsync(); + var errorResult = response.Content.ReadAsStringAsync().Result; _logger.LogError("error posting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload)); } } @@ -248,7 +259,7 @@ namespace GameData } else { - var errorResult = response.Content.ReadAsStringAsync(); + var errorResult = response.Content.ReadAsStringAsync().Result; _logger.LogError("error getting from service '{service}' with code '{statuscode}' - Result: '{result}'", serviceUrl, response.StatusCode, errorResult); } } @@ -263,5 +274,45 @@ namespace GameData return res.Data; } + + internal void Put(object payload, string serviceUrl, bool suppressExceptions = false) + { + T res = default; + + try + { + var response = client.Send(GetHttpRequestMessage(HttpMethod.Post, serviceUrl, payload)); + + if (response.IsSuccessStatusCode) + { + using (var responseStream = response.Content.ReadAsStream()) + { + using (var streamReader = new StreamReader(responseStream)) + { + using (var jsonTextReader = new JsonTextReader(streamReader)) + { + var serializer = new JsonSerializer(); + res = serializer.Deserialize(jsonTextReader); + } + } + } + } + else + { + var errorResult = response.Content.ReadAsStringAsync().Result; + _logger.LogError("error posting to service '{service}' with code '{statuscode}' - Result: '{result}', Payload: '{payload}'", serviceUrl, response.StatusCode, errorResult, JsonConvert.SerializeObject(payload)); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "error at post"); + if (!suppressExceptions) + { + throw ex; + } + + + } + } } } diff --git a/GameData/Dummy/DummyExpenseRepository.cs b/GameData/Dummy/DummyExpenseRepository.cs index 2954827..c92fc9f 100644 --- a/GameData/Dummy/DummyExpenseRepository.cs +++ b/GameData/Dummy/DummyExpenseRepository.cs @@ -5,9 +5,19 @@ namespace GameData.Dummy { public class DummyExpenseRepository : IExpenseRepository { + public DummyExpenseRepository() + { + + } + public IEnumerable GetAll() { return IExpenseRepository.TestData; } + + public void Save(MemberExpense data) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/GameData/Model/GameStateDo.cs b/GameData/Model/GameStateDo.cs new file mode 100644 index 0000000..b08b01a --- /dev/null +++ b/GameData/Model/GameStateDo.cs @@ -0,0 +1,13 @@ +using GameModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameData.Model +{ + public record GameStateDo(Guid Id, Guid GameId, string GameName, GameState GameState) + { + } +} diff --git a/GameData/Repository/ExpenseRepository.cs b/GameData/Repository/ExpenseRepository.cs index 9b03134..3b05699 100644 --- a/GameData/Repository/ExpenseRepository.cs +++ b/GameData/Repository/ExpenseRepository.cs @@ -16,6 +16,7 @@ namespace GameData.Repository private ApiClient _client; string Url => "items/expense"; + List _memberExpenses = new List(); public ExpenseRepository(ILogger log, ApiClient apiClient) { @@ -29,5 +30,10 @@ namespace GameData.Repository { return _client.Get(Url); } + + public void Save(MemberExpense memberExpense) + { + _memberExpenses.Add(memberExpense); + } } } diff --git a/GameData/Repository/GameRepository.cs b/GameData/Repository/GameRepository.cs new file mode 100644 index 0000000..80b4e5b --- /dev/null +++ b/GameData/Repository/GameRepository.cs @@ -0,0 +1,36 @@ +using GameData.Model; +using GameModel; +using GameModel.Contracts; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameData.Repository +{ + public class GameRepository : IGameRepository + { + readonly ILogger _log; + private ApiClient _client; + + string Url => "items/gamestate"; + public GameRepository(ILogger log, ApiClient apiClient) + { + _log = log; + _client = apiClient; + } + + public GameState Load(Guid gameId) + { + throw new NotImplementedException(); + } + + public void Save(GameState gameState) + { + var gameStateDo = new GameStateDo(gameState.Id, gameState.GameId, gameState.GameName, gameState); + _client.Post(gameStateDo,Url); + } + } +} diff --git a/GameHandler/GameService.cs b/GameHandler/GameService.cs index e250126..f7b1d0c 100644 --- a/GameHandler/GameService.cs +++ b/GameHandler/GameService.cs @@ -27,10 +27,9 @@ namespace GameHandler private IGameHandler _gh; private ThrowHandler _th; private ExpenseHandler _eh; - private GameState _lastState; - public GameState GameModel { get => _lastState; } - - private IExpenseRepository _expenseRepository { get => _scope.Resolve(); } + private GameStateHandler _gameStateHandler; + + public GameState GameModel { get => _gameStateHandler.GameState; } public GameService(IContainer rootContainer = null) { @@ -41,9 +40,9 @@ namespace GameHandler { get { - if (_lastState != null) + if (_gameStateHandler.GameState != null) { - return _lastState.ThrowState.BoardState.ThrowMode == ThrowMode.Decrease ? "Abräumen" : "Volle"; + return _gameStateHandler.GameState.ThrowState.BoardState.ThrowMode == ThrowMode.Decrease ? "Abräumen" : "Volle"; } return string.Empty; } @@ -65,16 +64,18 @@ namespace GameHandler _isStarted = true; InitGameServiceBehaviour(gameName); - _lastState = InitGameState(playerIds, gameSettings, gameName); + var state = InitGameState(playerIds, gameSettings, gameName); + _gameStateHandler.Add(state); + _gameStateHandler.Add(ExpenseModel.Create()); - return _lastState; + return state; } private GameState InitGameState(int[] playerIds, IGameSettings gameSettings, string gameName) { var gm = _gh.InitGameModel(playerIds, gameSettings); var throwState = ThrowState.Create(_gh.ThrowMode(), _gh.ThrowsPerRount()); - return GameState.Create(gameName, throwState, _gh.GetCurrentPlayerId(gm), gm, ExpenseModel.Create()); + return GameState.Create(Guid.NewGuid(), gameName, throwState, _gh.GetCurrentPlayerId(gm), gm); } private void InitGameServiceBehaviour(string gameName) @@ -83,13 +84,15 @@ namespace GameHandler _gh = this.GetGameHandler(gameName); _gh.GameExpenseOccured += _gh_GameExpenseOccured; _th = new ThrowHandler(); - _eh = new ExpenseHandler(_expenseRepository); + _eh = new ExpenseHandler(_scope.Resolve()); + _gameStateHandler = new GameStateHandler(_scope.Resolve(), _scope.Resolve()); } private void _gh_GameExpenseOccured(object? sender, EventArgs e) { - var expenseModel = _eh.HandleGameExenseEventArgs(e as GameExenseEventArgs, _lastState.ExpenseModel); - _lastState = _lastState with { ExpenseModel = expenseModel }; + var expenseModel = _eh.HandleGameExenseEventArgs(e as GameExenseEventArgs, _gameStateHandler.ExpenseModel); + _gameStateHandler.Add(expenseModel); + //_lastState = _lastState with { ExpenseModel = expenseModel }; } public GameState HandleThrow(PinThrow pinThrow) @@ -99,16 +102,22 @@ namespace GameHandler throw new InvalidGameStateExcpetion("Game not started"); } - var boardStateBeforeUpdate = _lastState.ThrowState.BoardState; + var lastGameState = _gameStateHandler.GameState; + + var boardStateBeforeUpdate = lastGameState.ThrowState.BoardState; pinThrow = AutoCompletePlayerId(pinThrow); - var throwStateAfterUpdate = _th.Update(_lastState.ThrowState, pinThrow); - var gameModel = _gh.Update(pinThrow, _lastState.GameModel, boardStateBeforeUpdate, throwStateAfterUpdate); - var expenseModel = _eh.CheckThrow(throwStateAfterUpdate.BoardState, pinThrow, gameModel.PlayerIds, _lastState.ExpenseModel); + var throwStateAfterUpdate = _th.Update(lastGameState.ThrowState, pinThrow); + var gameModel = _gh.Update(pinThrow, lastGameState.GameModel, boardStateBeforeUpdate, throwStateAfterUpdate); + var expenseModel = _eh.CheckThrow(throwStateAfterUpdate.BoardState, pinThrow, gameModel.PlayerIds, _gameStateHandler.ExpenseModel); - _lastState = _lastState with { ThrowState = throwStateAfterUpdate, GameModel = gameModel, NextPlayerId = _gh.GetCurrentPlayerId(gameModel), ExpenseModel = expenseModel }; - return _lastState ; + var newGameState = lastGameState with { ThrowState = throwStateAfterUpdate, GameModel = gameModel, NextPlayerId = _gh.GetCurrentPlayerId(gameModel) }; + + _gameStateHandler.Add(expenseModel); + _gameStateHandler.Add(newGameState); + + return newGameState; } private PinThrow AutoCompletePlayerId(PinThrow pinThrow) @@ -123,7 +132,7 @@ namespace GameHandler return pinThrow; } - return pinThrow with { PlayerId = _gh.GetCurrentPlayerId(_lastState.GameModel) }; + return pinThrow with { PlayerId = _gh.GetCurrentPlayerId(_gameStateHandler.GameState.GameModel) }; } public void Stop() diff --git a/GameHandler/GameStateHandler.cs b/GameHandler/GameStateHandler.cs new file mode 100644 index 0000000..8ac8dd5 --- /dev/null +++ b/GameHandler/GameStateHandler.cs @@ -0,0 +1,120 @@ +using GameModel; +using GameModel.Contract; +using GameModel.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GameHandler +{ + class GameStateHandlerItem + { + internal Guid GameStateId; + internal bool Saved; + internal T Data; + public GameStateHandlerItem(Guid gameStateId, T data, bool saved) + { + GameStateId = gameStateId; + Saved = saved; + Data = data; + } + } + + public class GameStateHandler + { + private Guid _gameStateId; + private List> gameStates = new List>(); + private List> expenseModels = new List>(); + private List> memberExpenses = new List>(); + + private IGameRepository _gameRepository; + private IExpenseRepository _expenseRepository; + + public GameStateHandler(IGameRepository gameRepository, IExpenseRepository expenseRepository) + { + _gameRepository = gameRepository; + _expenseRepository = expenseRepository; + } + + public ExpenseModel ExpenseModel + { + get + { + var res = expenseModels.LastOrDefault(); + if (res != null) + { + return res.Data; + } + return null; + } + } + + public GameState GameState + { + get + { + var res = gameStates.LastOrDefault(); + if (res != null) + { + return res.Data; + } + return null; + } + } + + internal void Add(ExpenseModel expenseModel) + { + if (ExpenseModel != null) + { + if (!expenseModel.Equals(ExpenseModel)) + { + expenseModels.Add(new GameStateHandlerItem(_gameStateId, expenseModel, false)); + Save(); + } + } + else + { + expenseModels.Add(new GameStateHandlerItem(_gameStateId, expenseModel, false)); + Save(); + } + + + foreach(var memberExpense in expenseModel.MemberExpenses.Where(_ => memberExpenses.Any(me => me.Data.Id.Equals(_.Id)))) + { + memberExpenses.Add(new GameStateHandlerItem(_gameStateId,memberExpense,false)); + } + } + + internal void Add(GameState newState) + { + if (!newState.Equals(GameState)) + { + _gameStateId = Guid.NewGuid(); + gameStates.Add(new GameStateHandlerItem(_gameStateId, newState, false)); + Save(); + } + } + + private void Save() + { + foreach (var item in gameStates.Where(_ => !_.Saved)) + { + _gameRepository.Save(item.Data); + item.Saved = true; + } + + foreach (var item in memberExpenses) + { + _expenseRepository.Save(item.Data); + item.Saved = true; + } + } + + public void Undo() + { + + } + } +} diff --git a/GameModel/Contracts/IExpenseRepository.cs b/GameModel/Contracts/IExpenseRepository.cs index aaacb4d..c3e9faa 100644 --- a/GameModel/Contracts/IExpenseRepository.cs +++ b/GameModel/Contracts/IExpenseRepository.cs @@ -18,5 +18,6 @@ namespace GameModel.Contract }; IEnumerable GetAll(); + void Save(MemberExpense data); } } \ No newline at end of file diff --git a/GameModel/ExpenseModel.cs b/GameModel/ExpenseModel.cs index 376e8d1..5f9b631 100644 --- a/GameModel/ExpenseModel.cs +++ b/GameModel/ExpenseModel.cs @@ -8,6 +8,15 @@ namespace GameModel { public record ExpenseModel(MemberExpense[] MemberExpenses) { + public virtual bool Equals(ExpenseModel other) + { + if (ReferenceEquals(null, other)) return false; + + return MemberExpenses.SequenceEqual(other.MemberExpenses); + } + + public override int GetHashCode() => MemberExpenses.GetHashCode(); + public static ExpenseModel Create() { return new ExpenseModel(Array.Empty()); diff --git a/GameModel/GameState.cs b/GameModel/GameState.cs index 15cb4c0..bad0ea9 100644 --- a/GameModel/GameState.cs +++ b/GameModel/GameState.cs @@ -7,11 +7,11 @@ using System.Threading.Tasks; namespace GameModel { - public record GameState(Guid GameId, string GameName, ThrowState ThrowState, int NextPlayerId, IGameModel GameModel, ExpenseModel ExpenseModel) + public record GameState(Guid Id, Guid GameId, string GameName, ThrowState ThrowState, int NextPlayerId, IGameModel GameModel) { - public static GameState Create(string gameName, ThrowState throwState, int nextPlayerId, IGameModel gameModel, ExpenseModel expenseModel) + public static GameState Create(Guid gameId, string gameName, ThrowState throwState, int nextPlayerId, IGameModel gameModel) { - return new GameState(Guid.NewGuid(), gameName, throwState, nextPlayerId, gameModel, expenseModel); + return new GameState(Guid.NewGuid(), gameId, gameName, throwState, nextPlayerId, gameModel); } } } diff --git a/KoogleCli/Program.cs b/KoogleCli/Program.cs index 2c9f4c9..0ec8964 100644 --- a/KoogleCli/Program.cs +++ b/KoogleCli/Program.cs @@ -4,12 +4,14 @@ using Autofac.Core; using AutofacSerilogIntegration; using CommandLine; using GameData; +using GameData.Dummy; using GameData.Repository; using GameHandler; using GameHandler.DeathGame; using GameHandler.Extensions; using GameModel; using GameModel.Contract; +using GameModel.Contracts; using GameModel.DeathGame; using GameModel.Settings; using KoogleCli.Model; @@ -53,7 +55,8 @@ Autofac.IContainer Register() RegisterLogging(builder); builder.RegisterType(); - builder.RegisterType().As(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); return builder.Build(); }