X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=Project06%2FCS%203505%20Project%2006%2FCS%203505%20Project%2006%2FNetworkGame.cs;h=03b6e90481812e40680b720009379c554f43b089;hb=89af32ed79f55de76dd9b88eeb214ede7e72639d;hp=bacfb19074d77dfc8686f2395fc1c162d20dbcb1;hpb=41f16c6fe0674f5bc7bf25bfd3dcd237f68611d5;p=chaz%2Fcarfire diff --git a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs index bacfb19..03b6e90 100644 --- a/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs +++ b/Project06/CS 3505 Project 06/CS 3505 Project 06/NetworkGame.cs @@ -11,64 +11,56 @@ using Microsoft.Xna.Framework.Input; namespace CS_3505_Project_06 { + /// + /// A manager class to handle network interactions between peers and + /// lobby/game switching. + /// public class NetworkGame { - NetworkSession mNetworkSession; - - ILobby mLobby; - IDeterministicGame mGame; - - TimeSpan mTargetTimeSpan = new TimeSpan(166666); - public TimeSpan TargetTimeSpan - { - get - { - return mTargetTimeSpan; - } - } - - List lastPressedKeys; - bool lastButtonPressed; - - Object[] playerIdentifiers = { "One", "Two", "Three", "Four" }; // Any objects will do, strings are easy to debug. - - // For debugging - - Object activePlayer; - bool paused; - long lastAutoPause; - - public SpriteFont font; - - + /// + /// Called when a session has been created or joined using CreateSession() or JoinSession(). + /// + /// The new session that was created or joined. + /// The NetworkGame that joined the session. + public delegate void JoinedSessionDelegate(NetworkSession session, NetworkGame networkGame); + + /// + /// Called when sessions are found as a result of calling FindSessions(). + /// + /// A container of the available sessions. + /// The NetworkGame that searched for the sessions. + public delegate void FoundSessionsDelegate(AvailableNetworkSessionCollection sessions, NetworkGame networkGame); + + + /// + /// Construct a NetworkGame with a lobby and a game. + /// + /// Provides an associated lobby to update and draw. + /// Provides a game object to be played over the network. public NetworkGame(ILobby lobby, IDeterministicGame game) { Debug.Assert(lobby != null && game != null); mLobby = lobby; mGame = game; - - // Begin: Test harness stuff - lastPressedKeys = new List(); - activePlayer = playerIdentifiers[0]; - paused = false; - - // Reset the game - indicate that player #1 (player 0) owns this instance of the game. - - mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); } + /// + /// Get the Gamer object for the local player. + /// public LocalNetworkGamer LocalGamer { get { + // TODO: Is this the correct way to get the single local gamer? return mNetworkSession.LocalGamers[0]; } } - // I added this as I needed a way to display all gamers not just the first gamer - // -Brady + /// + /// Get all the gamers associated with the active network session. + /// public GamerCollection NetworkGamers { get @@ -78,53 +70,99 @@ namespace CS_3505_Project_06 } - public NetworkSession CreateSession() + /// + /// Begin a new network session with the local gamer as the host. You must not + /// call this method or use JoinSession without first using LeaveSession. + /// + /// The delegate/method to call when the session is created. + public void CreateSession(JoinedSessionDelegate callback) { - return CreateSession(mGame.MaximumSupportedPlayers); + CreateSession(mGame.MaximumSupportedPlayers, callback); } - public NetworkSession CreateSession(int maxGamers) + /// + /// Begin a new network session with the local gamer as the host. You must not + /// call this method or use JoinSession without first using LeaveSession. + /// + /// Provide the maximum number of players allowed to connect. + /// The delegate/method to call when the session is created. + public void CreateSession(int maxGamers, JoinedSessionDelegate callback) { - Debug.Assert(mNetworkSession == null); + Debug.Assert(mNetworkSession == null); + + mJoinedSessionDelegate = callback; + NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null); + } + private void CreateSessionEnd(IAsyncResult result) + { + Debug.Assert(mNetworkSession == null); - mNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers); + mNetworkSession = NetworkSession.EndCreate(result); mNetworkSession.AllowHostMigration = true; mNetworkSession.AllowJoinInProgress = false; - return mNetworkSession; + mJoinedSessionDelegate(mNetworkSession, this); } - // added so I can test if sessionExists and thus be able to call things on NetworkGame safely - // -Brady - public bool sessionExists() + /// + /// Determine whether or not the network game object is associated with any network session. + /// + /// True if there exists a NetworkSession; false otherwise. + public bool HasActiveSession { - return mNetworkSession != null; + get + { + return mNetworkSession != null; + } } - public AvailableNetworkSessionCollection FindSessions() + + /// + /// Find available sessions to join. You should not already be in a session when + /// calling this method; call LeaveSession first. + /// + /// The delegate/method to call when the search finishes. + public void FindSessions(FoundSessionsDelegate callback) + { + Debug.Assert(mNetworkSession == null); + + mFoundSessionsDelegate = callback; + NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null); + } + private void FindSessionsEnd(IAsyncResult result) { - return NetworkSession.Find(NetworkSessionType.SystemLink, 1, new NetworkSessionProperties()); + AvailableNetworkSessionCollection sessions = NetworkSession.EndFind(result); + mFoundSessionsDelegate(sessions, this); } - public NetworkSession JoinSession(AvailableNetworkSession availableSession) + /// + /// Join a network session found using FindSessions(). This is for joining a game that + /// somebody else has already started hosting. You must not already be in a session. + /// + /// Pass the session object to try to join. + /// The delegate/method to call when the search finishes. + public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback) { Debug.Assert(mNetworkSession == null); - mNetworkSession = NetworkSession.Join(availableSession); - - return mNetworkSession; + mJoinedSessionDelegate = callback; + NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null); } - - // added to begin the game. I made the LobbyGUI make sure that only the host will call it when everyone is ready. - // This is already taken care of in the update method below. But it may be nice to allow the host to signal the start - // rather then having it start automatically. Just a suggestion. - // -Brady - public void StartGame() + private void JoinSessionEnd(IAsyncResult result) { - mNetworkSession.StartGame(); - mNetworkSession.ResetReady(); + Debug.Assert(mNetworkSession == null); + + mNetworkSession = NetworkSession.EndJoin(result); + + mJoinedSessionDelegate(mNetworkSession, this); + mJoinedSessionDelegate = null; } + + /// + /// Leave and dispose of any currently associated network session. You will find yourself + /// back in the lobby. You must already be in a session to leave it. + /// public void LeaveSession() { Debug.Assert(mNetworkSession != null); @@ -134,6 +172,9 @@ namespace CS_3505_Project_06 } + /// + /// Set up the network session to simulate 200ms latency and 10% packet loss. + /// public void SimulateBadNetwork() { Debug.Assert(mNetworkSession != null); @@ -143,6 +184,23 @@ namespace CS_3505_Project_06 } + /// + /// Indicate that the game should begin (moving players from the lobby to the game). + /// You must call CreateSession() before calling this. + /// + public void StartGame() + { + Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost); + + mNetworkSession.StartGame(); + mNetworkSession.ResetReady(); + } + + + /// + /// Manages the network session and allows either the lobby or game to update. + /// + /// Pass the time away. public void Update(GameTime gameTime) { if (mNetworkSession == null) @@ -152,6 +210,7 @@ namespace CS_3505_Project_06 else { mNetworkSession.Update(); + ReadPackets(); if (mNetworkSession.SessionState == NetworkSessionState.Lobby) { @@ -169,123 +228,319 @@ namespace CS_3505_Project_06 } else if (mNetworkSession.SessionState == NetworkSessionState.Playing) { - // TODO: in-game update stuff - UpdateTestHarness(gameTime); - - mGame.Update(mTargetTimeSpan); + if (HaveNeededEvents) + { + if (IsLatencyAdjustmentFrame) AdjustLatency(); + mStallCount = 0; + SendLocalEvents(); + ApplyEvents(); + mGame.Update(mTargetTimeSpan); + } + else // Stall! + { + } } } } + /// + /// Allows either the lobby or the game to draw, depending on the state + /// of the network connection and whether or not a game is in progress. + /// + /// Pass the time away. + /// The sprite batch. public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { - if (mNetworkSession != null) + if (mNetworkSession == null) { - if (mNetworkSession.SessionState != NetworkSessionState.Playing) - DrawTestHarness(gameTime, spriteBatch); - else - mLobby.Draw(spriteBatch); + mLobby.Draw(spriteBatch); } else - mLobby.Draw(spriteBatch); - + { + if (mNetworkSession.SessionState == NetworkSessionState.Lobby) + { + mLobby.Draw(spriteBatch); + } + else if (mNetworkSession.SessionState == NetworkSessionState.Playing) + { + mLobby.Draw(spriteBatch); + } + } } + /// + /// Get the chat messages that have been receive since the last time this + /// method was called. + /// + /// List container of the chat messages. + public List ReceiveChats() + { + List chats = mChatPackets; + mChatPackets = new List(); + return chats; + } + /// + /// Send a chat message to all gamers in the session. You should already be + /// in a session before calling this method. + /// + /// The text of the message. + public void SendChat(String message) + { + WriteChat(message); + LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder); + } - void UpdateTestHarness(GameTime gameTime) + /// + /// Send a chat message to a specific gamer in the session. You should already + /// be in a session before calling this method. + /// + /// The text of the message. + /// The gamer to receive the message. + public void SendChat(String message, NetworkGamer recipient) { - // Get user's input state. + WriteChat(message); + LocalGamer.SendData(mPacketWriter, SendDataOptions.ReliableInOrder, recipient); + } - KeyboardState keyState = Keyboard.GetState(); - MouseState mouseState = Mouse.GetState(); - // Make a list of the keys pressed or released this frame. + // Private class variable members + #region Instance Variables - List pressedKeys = new List(); - List releasedKeys = new List(); + NetworkSession mNetworkSession; + PacketReader mPacketReader = new PacketReader(); + PacketWriter mPacketWriter = new PacketWriter(); - Keys[] pressedKeysArray = keyState.GetPressedKeys(); - foreach (Keys k in pressedKeysArray) - if (!lastPressedKeys.Contains(k)) - pressedKeys.Add(k); - else - lastPressedKeys.Remove(k); + JoinedSessionDelegate mJoinedSessionDelegate; + FoundSessionsDelegate mFoundSessionsDelegate; - releasedKeys = lastPressedKeys; - lastPressedKeys = new List(pressedKeysArray); + ILobby mLobby; + IDeterministicGame mGame; - // Get mouse button state. + List mChatPackets = new List(); - bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; + List mLastPressedKeys = new List(); + bool mLastButtonPressed; + + int mLatency; + long mNextLatencyAdjustmentFrame; + int mStallCount; + int mAverageOwd; - /***** Begining of game logic. *****/ + TimeSpan mTargetTimeSpan = new TimeSpan(166666); + public TimeSpan TargetTimeSpan + { + get + { + return mTargetTimeSpan; + } + } - // Debug - allow user on this machine to direct input to any player's state in the game. + #endregion - if (pressedKeys.Contains(Keys.F1)) activePlayer = playerIdentifiers[0]; - if (pressedKeys.Contains(Keys.F2)) activePlayer = playerIdentifiers[1]; - if (pressedKeys.Contains(Keys.F3)) activePlayer = playerIdentifiers[2]; - if (pressedKeys.Contains(Keys.F4)) activePlayer = playerIdentifiers[3]; - // Debug - allow user on this machine to pause/resume game state advances. + // Private implementation methods of the network protocol + #region Private Implementation Methods + + enum PacketType + { + Chat = 1, + Event = 2, + Stall = 3 + } - if (pressedKeys.Contains(Keys.F12) || - pressedKeys.Contains(Keys.P) && (keyState.IsKeyDown(Keys.LeftControl) || keyState.IsKeyDown(Keys.RightControl))) + enum EventType + { + KeyDown = 1, + KeyUp = 2, + MouseDown = 3, + MouseUp = 4, + MouseMove = 5 + } + + + /// + /// Reinitialize the private variables in preparation for new game to start. + /// + void Reset() + { + mLatency = 1; + mNextLatencyAdjustmentFrame = 1; + mStallCount = 0; + mAverageOwd = AverageOneWayDelay; + + // TODO: The game object needs to be reset, too. + //mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]); + } + + + /// + /// Allows either the lobby or the game to draw, depending on the state + /// of the network connection and whether or not a game is in progress. + /// + /// Pass the time away. + /// The sprite batch. + void ReadPackets() + { + foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers) { - paused = !paused; - return; // Don't update on pause start or stop + while (gamer.IsDataAvailable) + { + NetworkGamer sender; + + gamer.ReceiveData(mPacketReader, out sender); + PacketType packetId = (PacketType)mPacketReader.ReadByte(); + + switch (packetId) + { + case PacketType.Chat: + + short messageLength = mPacketReader.ReadInt16(); + char[] message = mPacketReader.ReadChars(messageLength); + + ChatPacket chatPacket = new ChatPacket(sender, new String(message)); + mChatPackets.Add(chatPacket); + break; + + case PacketType.Event: + + short stallCount = mPacketReader.ReadInt16(); + short averageOwd = mPacketReader.ReadInt16(); + int frameNumber = mPacketReader.ReadInt32(); + byte numEvents = mPacketReader.ReadByte(); + + for (byte i = 0; i < numEvents; ++i) + { + ReadEvent(mPacketReader, sender); + } + + break; + + case PacketType.Stall: + + byte numStalledPeers = mPacketReader.ReadByte(); + byte[] stalledPeers = mPacketReader.ReadBytes(numStalledPeers); + + break; + } + } } + } - // Debug - automatically pause every 1000 frames. + void ReadEvent(PacketReader packetReader, NetworkGamer sender) + { + EventType eventId = (EventType)packetReader.ReadByte(); + long applicationFrame = packetReader.ReadInt32(); - if (mGame.CurrentFrameNumber % 1000 == 0 && mGame.CurrentFrameNumber != lastAutoPause) + switch (eventId) { - paused = true; - lastAutoPause = mGame.CurrentFrameNumber; - } + case EventType.KeyDown: + int keyCode1 = packetReader.ReadInt32(); - //if (pressedKeys.Contains(Keys.Escape)) - // this.Exit(); + break; - // Game update + case EventType.KeyUp: - // Direct inputs to the game engine - only report changes. + int keyCode2 = packetReader.ReadInt32(); - foreach (Keys k in pressedKeys) - mGame.ApplyKeyInput(activePlayer, k, true); + break; - foreach (Keys k in releasedKeys) - mGame.ApplyKeyInput(activePlayer, k, false); + case EventType.MouseDown: - mGame.ApplyMouseLocationInput(activePlayer, mouseState.X, mouseState.Y); + byte buttonId1 = packetReader.ReadByte(); - if (lastButtonPressed != buttonPressed) - mGame.ApplyMouseButtonInput(activePlayer, buttonPressed); + break; - lastButtonPressed = buttonPressed; + case EventType.MouseUp: - if (!paused) - { - // Advance the game engine. + byte buttonId2 = packetReader.ReadByte(); + + break; + + case EventType.MouseMove: - mGame.Update(mTargetTimeSpan); + short x = packetReader.ReadInt16(); + short y = packetReader.ReadInt16(); + + break; } } - void DrawTestHarness(GameTime gameTime, SpriteBatch spriteBatch) + void WriteChat(String message) { + mPacketWriter.Write((byte)PacketType.Chat); + mPacketWriter.Write((short)message.Length); + mPacketWriter.Write(message.ToCharArray()); + } - // BEGIN: Test harness stuff. - if (paused && gameTime.TotalRealTime.Milliseconds < 500) - spriteBatch.DrawString(font, "-=> Paused <=-", new Vector2(10, 130), Color.White); - spriteBatch.DrawString(font, "Press [F1]...[F4] to simulate input for each player. Click X's to end game or terminate player.", new Vector2(10, 540), Color.White); - spriteBatch.DrawString(font, "Press [ESC] to exit and [F12] to pause/unpause. Game auto-pauses every 1000 frames.", new Vector2(10, 570), Color.White); - //END: Test harness stuff. + bool IsLatencyAdjustmentFrame + { + get + { + // TODO + return false; + } + } + + void AdjustLatency() + { + // TODO + } + + + void SendLocalEvents() + { + // TODO: Not finished. + + KeyboardState keyState = Keyboard.GetState(); + MouseState mouseState = Mouse.GetState(); + + // Make a list of the keys pressed or released this frame. + + List pressedKeys = new List(); + List releasedKeys = new List(); + + Keys[] pressedKeysArray = keyState.GetPressedKeys(); + foreach (Keys k in pressedKeysArray) + if (!mLastPressedKeys.Contains(k)) + pressedKeys.Add(k); + else + mLastPressedKeys.Remove(k); + + releasedKeys = mLastPressedKeys; + mLastPressedKeys = new List(pressedKeysArray); + + bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed; + } + + + bool HaveNeededEvents + { + get + { + // TODO + return true; + } + } + void ApplyEvents() + { + // TODO } + + + int AverageOneWayDelay + { + get + { + // TODO + return 12; + } + } + + #endregion } }