Пример #1
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            // Change state to PendingLogOn so if the client sends any messages
            // in the meantime, that state will take over.
            context.SetState(NetworkStateId.PendingLogOn);

            var attempt = (LogOnMessage)message;

            if (attempt.Version != BspConstants.Version)
            {
                _sender.Send(new RejectLogOnMessage(BspConstants.Version));
                return;
            }

            if (!_userRepo.TryLogIn(attempt.Username, attempt.Password, _sender))
            {
                _sender.Send(new RejectLogOnMessage(BspConstants.Version));
                return;
            }

            // Client has successfully logged in, so we update our record.
            _state.Username = attempt.Username;

            // Let the user know they were successful.
            _sender.Send(new BasicMessage(MessageTypeId.AcceptLogOn));

            // Send them the game types.
            foreach (var gameType in _gameTypeRepo.GetAll())
            {
                _sender.Send(new GameTypeMessage(gameType));
            }
        }
Пример #2
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.YouWin)
            {
                context.SetState(NetworkStateId.WaitingForBoard);
                _prompter.PromptYouWin();
                _prompter.PromptWaitingForBoard();
                return;
            }

            context.SetState(NetworkStateId.TheirTurn);

            switch (message.TypeId)
            {
            case MessageTypeId.Hit:
                _prompter.PromptYouHit();
                break;

            case MessageTypeId.Sunk:
                _prompter.PromptYouSunk();
                break;

            case MessageTypeId.Miss:
                _prompter.PromptYouMissed();
                break;
            }

            _prompter.PromptTheirTurn();
        }
Пример #3
0
        public static async Task Main(string[] args)
        {
            // Get the IP address to bind to as a command line arguement or default
            // to 127.0.0.1.
            var userSuppliedIp = args.Length < 1 ? "127.0.0.1" : args[0];

            // Require the IP address be supplied as a command line argument.
            var localIp       = IPAddress.Parse(userSuppliedIp);
            var localEndPoint = new IPEndPoint(localIp, BspConstants.DefaultPort);

            // Begin listening for clients attempting to discover the server IP
            _ = StartUdpListing(localEndPoint);

            // Create objects used for every connection.
            var generalLogger = new Logger(Console.Out);
            var unparser      = new MessageUnparser();
            var listener      = new BspListener(generalLogger);
            var gameTypeRepo  = CreateGameTypeRepository();
            var userRepo      = CreateUserRepository();
            var matchMaker    = new MatchMaker();

            // The body of this foreach loop is a callback. This body will run for
            // connection our listener accepts. Here we create objects specific for
            // each connection.
            await foreach (var socket in listener.StartListeningAsync(localEndPoint))
            {
                // Create a logger that will log with the client's EndPoint.
                var logger = new EndPointLogger(Console.Out, socket.RemoteEndPoint);

                // Create a disconnecter that can clean up a connection.
                var disconnecter = new ServerDisconnecter(logger, socket, userRepo, matchMaker);

                // Create a sender for sending messages to the client.
                var senderHandler = new MultiMessageHandler();
                var sender        = new BspSender(socket, logger, unparser, senderHandler);

                // Create a container for this connections state.
                var container = new ServerNetworkStateContainer(sender, disconnecter, gameTypeRepo,
                                                                userRepo, matchMaker);

                // Create a context for our state machine.
                var context = new NetworkStateContext(container, disconnecter);

                // Register incoming messages with this context.
                senderHandler.AddHandler(LoggingMessageHandler.ForSending(logger));
                senderHandler.AddHandler(new SentMessageHandler(context));

                // Register outgoing messages with this context.
                var receiverHandler = new MultiMessageHandler();
                receiverHandler.AddHandler(LoggingMessageHandler.ForReceiving(logger));
                receiverHandler.AddHandler(new ReceiveMessageHandler(context));

                // Create a parser for this connection
                var parser = new MessageParser(receiverHandler, gameTypeRepo);

                // Begin asynchronously receiving messages
                var receiver = new BspReceiver(socket, disconnecter, parser, logger);
                _ = receiver.StartReceivingAsync();
            }
        }
Пример #4
0
        public void Sent(NetworkStateContext context, IMessage message)
        {
            // The only valid send is FoundGame
            context.SetState(NetworkStateId.FoundGame);

            // Get the opponents connection.
            if (!_userRepo.TryGetSender(_state.Match.Opponent.Username, out var opponent))
            {
                // Somehow the opponent is not logged in.
                _disconnecter.Disconnect();
                return;
            }

            _state.SetMatchTimeoutCallback((s, e) =>
            {
                // If the opponent has responded already, send a RejectGame to them.
                if (_state.Match.Opponent.AcceptedGame != MatchResponse.None)
                {
                    opponent.Send(new BasicMessage(MessageTypeId.RejectGame));
                }

                _sender.Send(new BasicMessage(MessageTypeId.GameExpired));
            });

            _state.StartMatchTimer();
        }
Пример #5
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            // Only valid receive is MyGuess
            context.SetState(NetworkStateId.Waiting);

            var guess       = ((MyGuessMessage)message).Position;
            var guessResult = _state.Match.Opponent.Board.Guess(guess);

            var id = guessResult switch
            {
                GuessResult.Miss => MessageTypeId.Miss,
                GuessResult.Hit => MessageTypeId.Hit,
                GuessResult.Sunk => MessageTypeId.Sunk,
                GuessResult.Win => MessageTypeId.YouWin,
                _ => throw new ArgumentOutOfRangeException()
            };

            _sender.Send(new BasicMessage(id));

            // Get the opponents connection.
            if (!_userRepo.TryGetSender(_state.Match.Opponent.Username, out var opponent))
            {
                // Somehow the opponent is not logged in.
                _disconnecter.Disconnect();
                return;
            }

            if (id == MessageTypeId.YouWin)
            {
                opponent.Send(new YouLoseMessage(guess));
                return;
            }

            opponent.Send(new TheirGuessMessage(guess));
        }
Пример #6
0
        public void Sent(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.AssignRed)
            {
                context.SetState(NetworkStateId.MyTurn);
                return;
            }

            context.SetState(NetworkStateId.TheirTurn);
        }
Пример #7
0
        public void Sent(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.TheirGuess)
            {
                context.SetState(NetworkStateId.MyTurn);
                return;
            }

            context.SetState(NetworkStateId.WaitingForBoard);
        }
Пример #8
0
 public void Sent(NetworkStateContext context, IMessage message)
 {
     if (message.TypeId == MessageTypeId.AcceptLogOn)
     {
         context.SetState(NetworkStateId.WaitingForBoard);
     }
     else
     {
         context.SetState(NetworkStateId.NotConnected);
     }
 }
Пример #9
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.AssignRed)
            {
                context.SetState(NetworkStateId.MyTurn);
                _prompter.PromptAssignRed();
                _prompter.PromptMyTurn();
                return;
            }

            context.SetState(NetworkStateId.TheirTurn);
            _prompter.PromptAssignBlue();
            _prompter.PromptTheirTurn();
        }
Пример #10
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            // Get the opponents connection.
            if (!_userRepo.TryGetSender(_state.Match.Opponent.Username, out var opponent))
            {
                // Somehow the opponent is not logged in.
                _disconnecter.Disconnect();
                return;
            }

            _state.CancelMatchTimer();

            // The player is rejecting the game.
            if (message.TypeId == MessageTypeId.RejectGame)
            {
                // Mark the player as accepting the game.
                _state.Match.Player.AcceptedGame = MatchResponse.Reject;

                // Update our state.
                context.SetState(NetworkStateId.WaitingForBoard);

                // Let the opponent know that the player has rejected the game.
                opponent.Send(new BasicMessage(MessageTypeId.RejectGame));
                return;
            }

            // The only remaining valid message is AcceptGame. So mark the player as accept.
            _state.Match.Player.AcceptedGame = MatchResponse.Accept;

            // If the opponent has also sent AcceptGame, we let both know to start the game.
            if (_state.Match.Opponent.AcceptedGame == MatchResponse.Accept)
            {
                // Let both know that the game has been accepted.
                _sender.Send(new BasicMessage(MessageTypeId.AcceptGame));
                opponent.Send(new BasicMessage(MessageTypeId.AcceptGame));

                // Notify who goes first
                if (_state.Match.PlayerGoesFirst)
                {
                    _sender.Send(new BasicMessage(MessageTypeId.AssignRed));
                    opponent.Send(new BasicMessage(MessageTypeId.AssignBlue));
                }
                else
                {
                    _sender.Send(new BasicMessage(MessageTypeId.AssignBlue));
                    opponent.Send(new BasicMessage(MessageTypeId.AssignRed));
                }
            }
        }
Пример #11
0
        public void Sent(NetworkStateContext context, IMessage message)
        {
            switch (message.TypeId)
            {
            case MessageTypeId.AcceptBoard:
                context.SetState(NetworkStateId.WaitingForGame);
                return;

            case MessageTypeId.RejectBoard:
                context.SetState(NetworkStateId.WaitingForBoard);
                return;

            default:
                throw new ArgumentException();
            }
        }
Пример #12
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.YouLose)
            {
                context.SetState(NetworkStateId.WaitingForBoard);
                _prompter.PromptYouLose();
                _prompter.PromptWaitingForBoard();
                return;
            }

            context.SetState(NetworkStateId.MyTurn);
            var guess = (TheirGuessMessage)message;

            _prompter.PromptTheirGuess(guess.Position);
            _prompter.PromptMyTurn();
        }
Пример #13
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.AcceptLogOn)
            {
                context.SetState(NetworkStateId.WaitingForBoard);
                _prompter.PromptSuccessfulLogOn();
                _prompter.PromptWaitingForBoard();
                return;
            }

            context.SetState(NetworkStateId.NotConnected);
            var version = ((RejectLogOnMessage)message).Version;

            _prompter.PromptFailedLogOn(version);
            _prompter.PromptLogOn();
        }
Пример #14
0
        public void Sent(NetworkStateContext context, IMessage message)
        {
            switch (message.TypeId)
            {
            case MessageTypeId.AcceptGame:
                context.SetState(NetworkStateId.InitialGame);
                return;

            case MessageTypeId.RejectGame:
                context.SetState(NetworkStateId.WaitingForBoard);
                return;

            case MessageTypeId.GameExpired:
                context.SetState(NetworkStateId.WaitingForBoard);
                return;
            }
        }
Пример #15
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.AcceptBoard)
            {
                context.SetState(NetworkStateId.WaitingForGame);
                _prompter.PromptValidBoard();
                return;
            }

            if (message.TypeId == MessageTypeId.RejectBoard)
            {
                context.SetState(NetworkStateId.WaitingForBoard);
                _prompter.PromptInvalidBoard();
                _prompter.PromptWaitingForBoard();
            }

            // Ignore GameType
        }
Пример #16
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            if (message.TypeId == MessageTypeId.AcceptGame)
            {
                context.SetState(NetworkStateId.InitialGame);
                _prompter.PromptOpponentAccepted();
                return;
            }

            if (message.TypeId == MessageTypeId.RejectGame)
            {
                context.SetState(NetworkStateId.WaitingForBoard);
                _prompter.PromptOpponentRejected();
                _prompter.PromptWaitingForBoard();
                return;
            }

            context.SetState(NetworkStateId.WaitingForBoard);
            _prompter.PromptMatchExpired();
            _prompter.PromptWaitingForBoard();
        }
Пример #17
0
 public void Received(NetworkStateContext context, IMessage message)
 {
     context.SetState(NetworkStateId.FoundGame);
     _prompter.PromptFoundGame();
 }
Пример #18
0
 public void Received(NetworkStateContext context, IMessage message)
 {
     // The only valid receive is RecallBoard
     context.SetState(NetworkStateId.WaitingForBoard);
 }
Пример #19
0
 public void Received(NetworkStateContext context, IMessage message)
 {
     // There are no valid messages for the server to receive in this state.
 }
Пример #20
0
        public void Received(NetworkStateContext context, IMessage message)
        {
            context.SetState(NetworkStateId.PendingBoard);

            var submission = (SubmitBoardMessage)message;
            var placements = submission.ShipPlacements;

            var isValidGameType = _gameTypeRepo.TryGet(submission.GameTypeId, out var gameType);

            if (!isValidGameType)
            {
                SendRejection(RejectBoardErrorId.UnsupportedGameType);
                return;
            }

            if (placements.Count != gameType.ShipLengths.Count)
            {
                SendRejection(RejectBoardErrorId.WrongShips);
                return;
            }

            var board = new Board(gameType);

            for (var i = 0; i < placements.Count; i++)
            {
                if (board.IsOutOfBounds(placements[i], i))
                {
                    SendRejection(RejectBoardErrorId.OutOfBounds);
                    return;
                }

                if (board.IsOverlapping(placements[i], i))
                {
                    SendRejection(RejectBoardErrorId.ShipOverlap);
                    return;
                }

                if (!board.TryPlace(placements[i], i))
                {
                    // This shouldn't happen because we just checked the error
                    // conditions. If this somehow happens, we want to crash the
                    // server.
                    throw new Exception();
                }
            }

            _sender.Send(new BasicMessage(MessageTypeId.AcceptBoard));

            var userBoard = new UserBoard(_state.Username, board);
            var match     = _matchMaker.FindMatchAsync(userBoard).GetAwaiter().GetResult();

            if (match == null)
            {
                // This could happen if the server somehow ends up in an
                // inconsistent state where it can't add the user to match making.
                throw new Exception();
            }

            _state.Match = match;
            _sender.Send(new BasicMessage(MessageTypeId.FoundGame));
        }
Пример #21
0
 public void Sent(NetworkStateContext context, IMessage message)
 {
     context.SetState(NetworkStateId.PendingBoard);
 }
Пример #22
0
 public void Received(NetworkStateContext context, IMessage message)
 {
     // This is left intentionally blank.
 }
Пример #23
0
 public void Sent(NetworkStateContext context, IMessage message)
 {
     // There are no valid messages for the server to send in this state.
 }
Пример #24
0
 public void Sent(NetworkStateContext context, IMessage message)
 {
     context.SetState(NetworkStateId.Waiting);
 }
Пример #25
0
 public ReceiveMessageHandler(NetworkStateContext context)
 {
     _context = context;
 }
Пример #26
0
        public static async Task Main(string[] args)
        {
            // Create a logger
            var logger = new Logger(Console.Out);

            // If the user did not supply an IP or hostname, attempt to
            // discover a server on the same subnet.
            IPEndPoint endPoint;

            if (args.Length > 0)
            {
                if (IPAddress.TryParse(args[0], out var ip))
                {
                    endPoint = new IPEndPoint(ip, BspConstants.DefaultPort);
                }
                else
                {
                    // Maybe the user supplied a hostname not an IP
                    var addresses = Dns.GetHostAddresses(args[0]);

                    if (addresses.Length < 1)
                    {
                        Console.WriteLine("Could not find hostname.");
                        return;
                    }

                    endPoint = new IPEndPoint(addresses.Last(), BspConstants.DefaultPort);
                }
            }
            else
            {
                logger.LogInfo("Attempting to discover server end point...");
                endPoint = await DiscoverServerEndPoint(BspConstants.DefaultPort);
            }

            // Attempt to connect to the server.
            var unparser = new MessageUnparser();
            var socket   = new Socket(SocketType.Stream, ProtocolType.Tcp);

            try
            {
                logger.LogInfo($"Attempting to connect to {endPoint}");
                socket.Connect(endPoint);
            }
            catch (SocketException)
            {
                logger.LogError($"Failed to connect to {endPoint}");
            }

            // Create a disconnecter that can clean up a connection.
            var disconnecter = new ClientDisconnecter(logger, socket);

            // Create a sender for sending to the server.
            var senderHandler = new MultiMessageHandler();
            var sender        = new BspSender(socket, logger, unparser, senderHandler);

            // Create a prompter to handle user interaction.
            var prompter = new Prompter(sender);

            // Create a state machine
            var container = new ClientNetworkStateContainer(prompter);
            var context   = new NetworkStateContext(container, disconnecter);

            // Register sending messages with our state machine.
            senderHandler.AddHandler(LoggingMessageHandler.ForSending(logger));
            senderHandler.AddHandler(new SentMessageHandler(context));

            // Register receiving messages with our state machine.
            var receiverHandler = new MultiMessageHandler();

            receiverHandler.AddHandler(LoggingMessageHandler.ForReceiving(logger));
            receiverHandler.AddHandler(new ReceiveMessageHandler(context));

            // Create a parser for our connection with the server
            var parser   = new MessageParser(receiverHandler, new GameTypeRepository());
            var receiver = new BspReceiver(socket, disconnecter, parser, logger);

            // Begin receive messages and start the prompt.
            var receivingTask = receiver.StartReceivingAsync();

            prompter.PromptLogOn();
            await receivingTask;
        }
Пример #27
0
 public SentMessageHandler(NetworkStateContext context)
 {
     _context = context;
 }