public void Event(IMatchEvent matchEvent, int minute) { if (_matchEnded) { throw new UpdatedAFinishedMatchException(); } matchEvent.AffectMatch(this, minute); }
public Match Apply(Match match, IMatchEvent matchEvent) { Match res = null; if (matchEvent is MatchStarted) { if (match.GameStarted) { throw new Exception("The game has already begun"); } var matchStarted = matchEvent as MatchStarted; res = match.StartMatch(matchStarted.Player1, matchStarted.Player2); } else if (matchEvent is MatchPoint) { if (!match.GameStarted) { throw new Exception("The game has not started"); } if (match.GameEnded) { throw new Exception("The game has ended"); } var matchPoint = matchEvent as MatchPoint; if (!match.Players.Any(i => i == matchPoint.Player)) { throw new Exception(string.Format("{0} is not in the match", matchPoint.Player)); } res = match.PlayPoint(matchPoint.Player); } else { throw new Exception("Invalid event"); } return(res); }
private void MatchEvent(IMatchEvent matchEvent) { INotification <IMatchEvent> notification = new Notification <IMatchEvent>(matchEvent); this.Notify(notification); }
private static MatchEventDto ToDto(this IMatchEvent @event) { switch (@event) { case GoalEvent goalEvent: return(new MatchEventDto { Id = goalEvent.Id, MatchEventType = (int)MatchEventType.Goal, MatchPeriod = (int)goalEvent.MatchPeriod, Minute = goalEvent.Minute, Home = goalEvent.Home, PlayerId = goalEvent.PlayerId }); case RedCardEvent redCardEvent: return(new MatchEventDto { Id = @event.Id, MatchEventType = (int)MatchEventType.RedCard, MatchPeriod = (int)redCardEvent.MatchPeriod, Minute = redCardEvent.Minute, Home = redCardEvent.Home, PlayerId = redCardEvent.PlayerId }); case SubstitutionEvent substitutionEvent: return(new MatchEventDto() { Id = @event.Id, MatchEventType = (int)MatchEventType.Substitution, MatchPeriod = (int)substitutionEvent.MatchPeriod, Minute = substitutionEvent.Minute, Home = substitutionEvent.Home, PlayerId = substitutionEvent.PlayerId, SubstitutionPlayerId = substitutionEvent.SubstitutionPlayerId }); case TimeEvent timeEvent: return(new MatchEventDto { Id = @event.Id, MatchEventType = (int)MatchEventType.Time, Minute = timeEvent.Minute, MatchPeriod = (int)timeEvent.MatchPeriod }); case YellowCardEvent yellowCardEvent: return(new MatchEventDto { Id = @event.Id, MatchEventType = (int)MatchEventType.YellowCard, MatchPeriod = (int)yellowCardEvent.MatchPeriod, Minute = yellowCardEvent.Minute, Home = yellowCardEvent.Home, PlayerId = yellowCardEvent.PlayerId }); default: throw new NotSupportedException(); } }
public async UniTask <NextScreen> Run(ClientState client, WebSocket socket) { _client = client; _socket = socket; // Request that the server start a new match, and load our tile prefabs in // the background. Wait for both of these operations to complete before // attempting to instantiate any tiles. await UniTask.WhenAll( RequestStartMatch(_cancellation.Token), LoadTilePrefabs(_cancellation.Token)); // TODO: Have the server data specify which player is controlled by this // client, rather than hard coding it to always control the east seat. _seat = Wind.East; // Register input events from the player's hand. var playerHand = _hands[(int)_seat]; // Once we have the match data, instantiate the tiles for each player's // starting hand. // // TODO: Move tile placement logic into `PlayerHand`. The match controller // should only need to add and remove tiles from the hands as the match's // state advances, and the `PlayerHand` script should handle layout and // positioning. foreach (var seat in EnumUtils.GetValues <Wind>()) { var hand = _hands[(int)seat]; var tiles = _localState.PlayerHand(seat); foreach (var tile in tiles) { hand.AddToHand(InstantiateTile(tile)); } if (_localState.PlayerHasCurrentDraw(seat)) { var currentDraw = _localState.CurrentDraw(seat); hand.DrawTile(InstantiateTile(currentDraw)); } } // If the local player has the first turn, have them discard a tile now. if (_localState.CurrentTurn() == _seat) { await DiscardTile(); } // Process incoming updates from the server. var matchEnded = false; while (!matchEnded) { // Wait to receive the next update from the server. var eventJson = await _socket.RecvStringAsync(_cancellation.Token); // Feed the incoming event into the server state. IMatchEvent update = _serverState.HandleEvent(eventJson); // Apply the received update to the local state, updating both the game // state tracking and the visual state. switch (update) { case MatchEvent.TileDrawn draw: { // Update the game state tracking for the client. if (!_localState.TryDrawTile(draw.Seat)) { // TODO: Handle the client being out of sync with the server. throw new NotImplementedException("Client out of sync with server"); } var localDraw = _localState.CurrentDraw(draw.Seat); Debug.Assert( draw.Tile.Element0 == localDraw.Id.Element0, "Drew incorrect tile when simulating locally", this); // Update the visuals based on the draw event. var hand = _hands[(int)draw.Seat]; var tileObject = InstantiateTile(localDraw); hand.DrawTile(tileObject); // TODO: Do some kind of actual animation for the draw. // If the local player was the one that drew the tile, have them discard a // tile now. if (draw.Seat == _seat) { Debug.Assert( _localState.CurrentTurn() == _seat, "Player drew a tile but it's not their turn???"); await DiscardTile(); } else { // TODO: Remove the explicit delay once we have an animation for the // draw. await UniTask.Delay((int)(_delayAfterDraw * 1000)); } } break; case MatchEvent.TileDiscarded discard: { // If we performed a discard event locally, the next discard event from // the server should match the one we performed. Verify that's the case // and reconcile our local state with the server state. // // Otherwise, perform the action on the local state and then verify that // the local state is still in sync with the server state. if (_lastDiscard is TileId lastDiscard) { if (discard.Seat != _seat || discard.Tile.Element0 != lastDiscard.Element0) { throw new OutOfSyncException( $"Previously discarded tile {lastDiscard}, but received" + $"discard event {discard}"); } // Clear local tracking for discarded tile now that the server has // caught up. _lastDiscard = null; } else if (_localState.TryDiscardTile(discard.Seat, discard.Tile)) { // Perform the discard action locally. var hand = _hands[(int)discard.Seat]; hand.MoveToDiscard(discard.Tile); // TODO: Remove the explicit delay once we have a proper animation. await UniTask.Delay((int)(_delayAfterDiscard * 1000)); } else { throw new OutOfSyncException($"Could not apply discard event locally: {discard}"); } // TODO: Reconcile our local state with the updated server state to // verify that the two are in sync. } break; case MatchEvent.MatchEnded _: { matchEnded = true; } break; } } // Display that the match ended UI and wait for the player to hit the exit button. _matchEndedDisplayRoot.SetActive(true); await _exitButton.OnClickAsync(_cancellation.Token); // Exit the match, indicating that we should return to the home screen. return(NextScreen.Home); // Helper method to handle requesting match creation from the server. async UniTask RequestStartMatch(CancellationToken cancellation = default)