Example #1
0
        public void Event(IMatchEvent matchEvent, int minute)
        {
            if (_matchEnded)
            {
                throw new UpdatedAFinishedMatchException();
            }

            matchEvent.AffectMatch(this, minute);
        }
Example #2
0
        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);
        }
Example #3
0
        private void MatchEvent(IMatchEvent matchEvent)
        {
            INotification <IMatchEvent> notification = new Notification <IMatchEvent>(matchEvent);

            this.Notify(notification);
        }
Example #4
0
        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();
            }
        }
Example #5
0
        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)