Welcome,\r
CreateGame,\r
FindGame,\r
+ FindingGames, // TODO: New state.\r
+ JoiningGame, // TODO: New state.\r
Connected\r
}\r
\r
\r
}\r
\r
+ // TODO: New method.\r
+ void JoinedSession(NetworkSession session, NetworkGame networkGame)\r
+ {\r
+ if (session != null)\r
+ {\r
+ currentState = lobbyState.Connected;\r
+ }\r
+ else\r
+ {\r
+ // TODO: This should do something more than just throw the player back to the welcome screen.\r
+ currentState = lobbyState.Welcome;\r
+ Console.WriteLine("Couldn't create/join the session.");\r
+ }\r
+ }\r
+\r
+ // TODO: New method.\r
+ void FoundSessions(AvailableNetworkSessionCollection sessions, NetworkGame networkGame)\r
+ {\r
+ availableSessions = sessions;\r
+\r
+ if (availableSessions != null && availableSessions.Count > 0)\r
+ {\r
+ networkGame.JoinSession(availableSessions[0], JoinedSession);\r
+ currentState = lobbyState.JoiningGame;\r
+\r
+ availableSessions.Dispose();\r
+ availableSessions = null;\r
+ }\r
+ else\r
+ {\r
+ // TODO: This should do something more than just throw the player back to the welcome screen.\r
+ currentState = lobbyState.Welcome;\r
+ Console.WriteLine("No sessions to join!");\r
+ }\r
+ }\r
+\r
public long Update(GameTime gameTime, NetworkGame networkGame)\r
{\r
\r
UpdateSpotLight(gameTime);\r
currentKeyboardState = Keyboard.GetState();\r
\r
- if (networkGame.sessionExists())\r
+ if (networkGame.HasActiveSession)\r
{\r
players = networkGame.NetworkGamers;\r
}\r
{\r
currentState = lobbyState.Welcome;\r
ready = false;\r
- if (networkGame.sessionExists())\r
+ if (networkGame.HasActiveSession)\r
{\r
players = null;\r
networkGame.LeaveSession();\r
}\r
if (currentKeyboardState.IsKeyDown(Keys.Y) && previousKeyboardState.IsKeyUp(Keys.Y))\r
{\r
- currentState = lobbyState.Connected;\r
- networkGame.CreateSession(4);\r
+ currentState = lobbyState.JoiningGame;\r
+ networkGame.CreateSession(JoinedSession);\r
} \r
break;\r
\r
currentState = lobbyState.Welcome;\r
ready = false;\r
}\r
- availableSessions = networkGame.FindSessions();\r
- if (availableSessions != null)\r
- {\r
- networkGame.JoinSession(availableSessions[0]);\r
- currentState = lobbyState.Connected;\r
-\r
- availableSessions.Dispose();\r
- availableSessions = null;\r
- }\r
+ networkGame.FindSessions(FoundSessions);\r
+ currentState = lobbyState.FindingGames;\r
break;\r
\r
case lobbyState.Connected:\r
if (currentKeyboardState.IsKeyDown(Keys.X) && previousKeyboardState.IsKeyUp(Keys.X))\r
{\r
ready = false;\r
- if (networkGame.sessionExists())\r
+ if (networkGame.HasActiveSession)\r
{\r
players = null;\r
networkGame.LeaveSession();\r
if (currentKeyboardState.IsKeyDown(Keys.R) && previousKeyboardState.IsKeyUp(Keys.R))\r
networkGame.LocalGamer.IsReady = true;\r
\r
- if (networkGame.sessionExists())\r
+ if (networkGame.HasActiveSession)\r
{\r
localPlayer = networkGame.LocalGamer;\r
players = networkGame.NetworkGamers;\r
\r
break;\r
\r
- case lobbyState.FindGame:\r
+ case lobbyState.FindingGames:\r
spriteBatch.Draw(selectGameScreen, backgroundPos, null, Color.White, 0, zero, scale, SpriteEffects.None, 0);\r
if(availableSessions == null)\r
spriteBatch.DrawString(menuFont, "searching for available games ....", new Vector2(150, 100), Color.Gray, 0f, zero, .7f, SpriteEffects.None, 0.5f);\r
\r
namespace CS_3505_Project_06\r
{\r
+ /// <summary>\r
+ /// A manager class to handle network interactions between peers and\r
+ /// lobby/game switching.\r
+ /// </summary>\r
public class NetworkGame\r
{\r
+ // Private class variable members\r
+ #region Instance Variables\r
+\r
NetworkSession mNetworkSession;\r
\r
+ JoinedSessionDelegate mJoinedSessionDelegate;\r
+ FoundSessionsDelegate mFoundSessionsDelegate;\r
+\r
ILobby mLobby;\r
IDeterministicGame mGame;\r
+ \r
+ List<Keys> mLastPressedKeys = new List<Keys>();\r
+ bool mLastButtonPressed;\r
+\r
+ int mLatency;\r
+ long mNextLatencyAdjustmentFrame;\r
+ int mStallCount;\r
+ int mAverageOwd;\r
\r
TimeSpan mTargetTimeSpan = new TimeSpan(166666);\r
public TimeSpan TargetTimeSpan\r
}\r
}\r
\r
- List<Keys> lastPressedKeys;\r
- bool lastButtonPressed;\r
-\r
- Object[] playerIdentifiers = { "One", "Two", "Three", "Four" }; // Any objects will do, strings are easy to debug.\r
+ #endregion\r
\r
- // For debugging\r
\r
- Object activePlayer;\r
- bool paused;\r
- long lastAutoPause;\r
+ /// <summary>\r
+ /// Called when a session has been created or joined using CreateSession() or JoinSession().\r
+ /// </summary>\r
+ /// <param name="session">The new session that was created or joined.</param>\r
+ /// <param name="networkGame">The NetworkGame that joined the session.</param>\r
+ public delegate void JoinedSessionDelegate(NetworkSession session, NetworkGame networkGame);\r
\r
- public SpriteFont font;\r
+ /// <summary>\r
+ /// Called when sessions are found as a result of calling FindSessions().\r
+ /// </summary>\r
+ /// <param name="sessions">A container of the available sessions.</param>\r
+ /// <param name="networkGame">The NetworkGame that searched for the sessions.</param>\r
+ public delegate void FoundSessionsDelegate(AvailableNetworkSessionCollection sessions, NetworkGame networkGame);\r
\r
\r
+ /// <summary>\r
+ /// Construct a NetworkGame with a lobby and a game.\r
+ /// </summary>\r
+ /// <param name="lobby">Provides an associated lobby to update and draw.</param>\r
+ /// <param name="game">Provides a game object to be played over the network.</param>\r
public NetworkGame(ILobby lobby, IDeterministicGame game)\r
{\r
Debug.Assert(lobby != null && game != null);\r
\r
mLobby = lobby;\r
mGame = game;\r
-\r
- // Begin: Test harness stuff\r
- lastPressedKeys = new List<Keys>();\r
- activePlayer = playerIdentifiers[0];\r
- paused = false;\r
-\r
- // Reset the game - indicate that player #1 (player 0) owns this instance of the game.\r
-\r
- mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]);\r
}\r
\r
\r
+ /// <summary>\r
+ /// Get the Gamer object for the local player.\r
+ /// </summary>\r
public LocalNetworkGamer LocalGamer\r
{\r
get\r
{\r
+ // TODO: Is this the correct way to get the single local gamer?\r
return mNetworkSession.LocalGamers[0];\r
}\r
}\r
\r
- // I added this as I needed a way to display all gamers not just the first gamer\r
- // -Brady\r
+ /// <summary>\r
+ /// Get all the gamers associated with the active network session.\r
+ /// </summary>\r
public GamerCollection<NetworkGamer> NetworkGamers\r
{\r
get\r
}\r
\r
\r
- public NetworkSession CreateSession()\r
+ /// <summary>\r
+ /// Begin a new network session with the local gamer as the host. You must not\r
+ /// call this method or use JoinSession without first using LeaveSession.\r
+ /// </summary>\r
+ /// <param name="callback">The delegate/method to call when the session is created.</param>\r
+ public void CreateSession(JoinedSessionDelegate callback)\r
{\r
- return CreateSession(mGame.MaximumSupportedPlayers);\r
+ CreateSession(mGame.MaximumSupportedPlayers, callback);\r
}\r
\r
- public NetworkSession CreateSession(int maxGamers)\r
+ /// <summary>\r
+ /// Begin a new network session with the local gamer as the host. You must not\r
+ /// call this method or use JoinSession without first using LeaveSession.\r
+ /// </summary>\r
+ /// <param name="maxGamers">Provide the maximum number of players allowed to connect.</param>\r
+ /// <param name="callback">The delegate/method to call when the session is created.</param>\r
+ public void CreateSession(int maxGamers, JoinedSessionDelegate callback)\r
{\r
- Debug.Assert(mNetworkSession == null); \r
+ Debug.Assert(mNetworkSession == null);\r
\r
- mNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers);\r
+ mJoinedSessionDelegate = callback;\r
+ NetworkSession.BeginCreate(NetworkSessionType.SystemLink, 1, maxGamers, CreateSessionEnd, null);\r
+ }\r
+ private void CreateSessionEnd(IAsyncResult result)\r
+ {\r
+ Debug.Assert(mNetworkSession == null);\r
+\r
+ mNetworkSession = NetworkSession.EndCreate(result);\r
mNetworkSession.AllowHostMigration = true;\r
mNetworkSession.AllowJoinInProgress = false;\r
\r
- return mNetworkSession;\r
+ mJoinedSessionDelegate(mNetworkSession, this);\r
}\r
\r
- // added so I can test if sessionExists and thus be able to call things on NetworkGame safely\r
- // -Brady\r
- public bool sessionExists()\r
+ /// <summary>\r
+ /// Determine whether or not the network game object is associated with any network session.\r
+ /// </summary>\r
+ /// <returns>True if there exists a NetworkSession; false otherwise.</returns>\r
+ public bool HasActiveSession\r
{\r
- return mNetworkSession != null;\r
+ get\r
+ {\r
+ return mNetworkSession != null;\r
+ }\r
}\r
\r
- public AvailableNetworkSessionCollection FindSessions()\r
+\r
+ /// <summary>\r
+ /// Find available sessions to join.\r
+ /// </summary>\r
+ /// <param name="callback">The delegate/method to call when the search finishes.</param>\r
+ public void FindSessions(FoundSessionsDelegate callback)\r
{\r
- return NetworkSession.Find(NetworkSessionType.SystemLink, 1, new NetworkSessionProperties());\r
+ Debug.Assert(mNetworkSession == null);\r
+\r
+ mFoundSessionsDelegate = callback;\r
+ NetworkSession.BeginFind(NetworkSessionType.SystemLink, 1, null, new AsyncCallback(FindSessionsEnd), null);\r
+ }\r
+ private void FindSessionsEnd(IAsyncResult result)\r
+ {\r
+ AvailableNetworkSessionCollection sessions = NetworkSession.EndFind(result);\r
+ mFoundSessionsDelegate(sessions, this);\r
}\r
\r
- public NetworkSession JoinSession(AvailableNetworkSession availableSession)\r
+ /// <summary>\r
+ /// Join a network session found using FindSessions(). This is for joining a game that\r
+ /// somebody else has already started hosting.\r
+ /// </summary>\r
+ /// <param name="availableSession">Pass the session object to try to join.</param>\r
+ /// <param name="callback">The delegate/method to call when the search finishes.</param>\r
+ public void JoinSession(AvailableNetworkSession availableSession, JoinedSessionDelegate callback)\r
{\r
Debug.Assert(mNetworkSession == null);\r
\r
- mNetworkSession = NetworkSession.Join(availableSession);\r
-\r
- return mNetworkSession;\r
+ mJoinedSessionDelegate = callback;\r
+ NetworkSession.BeginJoin(availableSession, JoinSessionEnd, null);\r
}\r
-\r
- // added to begin the game. I made the LobbyGUI make sure that only the host will call it when everyone is ready.\r
- // This is already taken care of in the update method below. But it may be nice to allow the host to signal the start\r
- // rather then having it start automatically. Just a suggestion. \r
- // -Brady\r
- public void StartGame()\r
+ private void JoinSessionEnd(IAsyncResult result)\r
{\r
- mNetworkSession.StartGame();\r
- mNetworkSession.ResetReady();\r
+ Debug.Assert(mNetworkSession == null);\r
+\r
+ mNetworkSession = NetworkSession.EndJoin(result);\r
+\r
+ mJoinedSessionDelegate(mNetworkSession, this);\r
+ mJoinedSessionDelegate = null;\r
}\r
\r
+\r
+ /// <summary>\r
+ /// Leave and dispose of any currently associated network session. You will find yourself\r
+ /// back in the lobby.\r
+ /// </summary>\r
public void LeaveSession()\r
{\r
Debug.Assert(mNetworkSession != null);\r
}\r
\r
\r
+ /// <summary>\r
+ /// Set up the network session to simulate 200ms latency and 10% packet loss.\r
+ /// </summary>\r
public void SimulateBadNetwork()\r
{\r
Debug.Assert(mNetworkSession != null);\r
}\r
\r
\r
+ /// <summary>\r
+ /// Indicate that the game should begin (moving players from the lobby to the game).\r
+ /// You must call CreateSession() before calling this.\r
+ /// </summary>\r
+ public void StartGame()\r
+ {\r
+ Debug.Assert(mNetworkSession != null && mNetworkSession.IsHost);\r
+\r
+ mNetworkSession.StartGame();\r
+ mNetworkSession.ResetReady();\r
+ }\r
+\r
+\r
+ /// <summary>\r
+ /// Manages the network session and allows either the lobby or game to update.\r
+ /// </summary>\r
+ /// <param name="gameTime">Pass the time away.</param>\r
public void Update(GameTime gameTime)\r
{\r
if (mNetworkSession == null)\r
else\r
{\r
mNetworkSession.Update();\r
+ ReadPackets();\r
\r
if (mNetworkSession.SessionState == NetworkSessionState.Lobby)\r
{\r
}\r
else if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
{\r
- // TODO: in-game update stuff\r
- UpdateTestHarness(gameTime);\r
-\r
- mGame.Update(mTargetTimeSpan);\r
+ if (HaveNeededEvents)\r
+ {\r
+ if (IsLatencyAdjustmentFrame) AdjustLatency();\r
+ mStallCount = 0;\r
+ SendLocalEvents();\r
+ ApplyEvents();\r
+ mGame.Update(mTargetTimeSpan);\r
+ }\r
+ else // Stall!\r
+ {\r
+ }\r
}\r
}\r
}\r
\r
+ /// <summary>\r
+ /// Allows either the lobby or the game to draw, depending on the state\r
+ /// of the network connection and whether or not a game is in progress.\r
+ /// </summary>\r
+ /// <param name="gameTime">Pass the time away.</param>\r
+ /// <param name="spriteBatch">The sprite batch.</param>\r
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)\r
{\r
- if (mNetworkSession != null)\r
+ if (mNetworkSession == null)\r
{\r
- if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
- DrawTestHarness(gameTime, spriteBatch);\r
- else\r
- mLobby.Draw(spriteBatch);\r
+ mLobby.Draw(spriteBatch);\r
}\r
else\r
- mLobby.Draw(spriteBatch);\r
- \r
+ {\r
+ if (mNetworkSession.SessionState == NetworkSessionState.Lobby)\r
+ {\r
+ mLobby.Draw(spriteBatch);\r
+ }\r
+ else if (mNetworkSession.SessionState == NetworkSessionState.Playing)\r
+ {\r
+ mLobby.Draw(spriteBatch);\r
+ }\r
+ }\r
}\r
\r
\r
+ // Private implementation methods of the network protocol\r
+ #region Private Implementation Methods\r
+\r
+ /// <summary>\r
+ /// Reinitialize the private variables in preparation for new game to start.\r
+ /// </summary>\r
+ private void Reset()\r
+ {\r
+ mLatency = 1;\r
+ mNextLatencyAdjustmentFrame = 1;\r
+ mStallCount = 0;\r
+ mAverageOwd = AverageOneWayDelay;\r
\r
+ // TODO: The game object needs to be reset, too.\r
+ //mGame.ResetGame(playerIdentifiers, playerIdentifiers[0]);\r
+ }\r
\r
- void UpdateTestHarness(GameTime gameTime)\r
+\r
+ /// <summary>\r
+ /// Allows either the lobby or the game to draw, depending on the state\r
+ /// of the network connection and whether or not a game is in progress.\r
+ /// </summary>\r
+ /// <param name="gameTime">Pass the time away.</param>\r
+ /// <param name="spriteBatch">The sprite batch.</param>\r
+ private void ReadPackets()\r
{\r
- // Get user's input state.\r
+ PacketReader packetReader = new PacketReader();\r
\r
- KeyboardState keyState = Keyboard.GetState();\r
- MouseState mouseState = Mouse.GetState();\r
+ foreach (LocalNetworkGamer gamer in mNetworkSession.LocalGamers)\r
+ {\r
+ while (gamer.IsDataAvailable)\r
+ {\r
+ NetworkGamer sender;\r
\r
- // Make a list of the keys pressed or released this frame.\r
+ gamer.ReceiveData(packetReader, out sender);\r
+ byte packetId = packetReader.ReadByte();\r
\r
- List<Keys> pressedKeys = new List<Keys>();\r
- List<Keys> releasedKeys = new List<Keys>();\r
+ switch (packetId)\r
+ {\r
+ // Chat Packet\r
+ case 1:\r
+ short messageLength = packetReader.ReadInt16();\r
+ char[] message = packetReader.ReadChars(messageLength);\r
+\r
+ ChatPacket chatPacket;\r
+ chatPacket.sender = sender;\r
+ chatPacket.message = new String(message);\r
+ break;\r
+\r
+ // Event Packet\r
+ case 2:\r
+ short stallCount = packetReader.ReadInt16();\r
+ short averageOwd = packetReader.ReadInt16();\r
+ int frameNumber = packetReader.ReadInt32();\r
+ byte numEvents = packetReader.ReadByte();\r
+\r
+ for (byte i = 0; i < numEvents; ++i)\r
+ {\r
+ ReadEvent(packetReader, sender);\r
+ }\r
+\r
+ break;\r
+\r
+ // Stall Packet\r
+ case 3:\r
+ byte numStalledPeers = packetReader.ReadByte();\r
+ byte[] stalledPeers = packetReader.ReadBytes(numStalledPeers);\r
+\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
\r
- Keys[] pressedKeysArray = keyState.GetPressedKeys();\r
- foreach (Keys k in pressedKeysArray)\r
- if (!lastPressedKeys.Contains(k))\r
- pressedKeys.Add(k);\r
- else\r
- lastPressedKeys.Remove(k);\r
+ private void ReadEvent(PacketReader packetReader, NetworkGamer sender)\r
+ {\r
+ byte eventId = packetReader.ReadByte();\r
+ long applicationFrame = packetReader.ReadInt32();\r
\r
- releasedKeys = lastPressedKeys;\r
- lastPressedKeys = new List<Keys>(pressedKeysArray);\r
+ switch (eventId)\r
+ {\r
+ // Key Down\r
+ case 1:\r
+ int keyCode1 = packetReader.ReadInt32();\r
\r
- // Get mouse button state.\r
+ break;\r
\r
- bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed;\r
+ // Key Up\r
+ case 2:\r
+ int keyCode2 = packetReader.ReadInt32();\r
\r
- /***** Begining of game logic. *****/\r
+ break;\r
\r
- // Debug - allow user on this machine to direct input to any player's state in the game.\r
+ // Mouse Down\r
+ case 3:\r
+ byte buttonId1 = packetReader.ReadByte();\r
\r
- if (pressedKeys.Contains(Keys.F1)) activePlayer = playerIdentifiers[0];\r
- if (pressedKeys.Contains(Keys.F2)) activePlayer = playerIdentifiers[1];\r
- if (pressedKeys.Contains(Keys.F3)) activePlayer = playerIdentifiers[2];\r
- if (pressedKeys.Contains(Keys.F4)) activePlayer = playerIdentifiers[3];\r
+ break;\r
\r
- // Debug - allow user on this machine to pause/resume game state advances.\r
+ // Mouse Up\r
+ case 4:\r
+ byte buttonId2 = packetReader.ReadByte();\r
\r
- if (pressedKeys.Contains(Keys.F12) ||\r
- pressedKeys.Contains(Keys.P) && (keyState.IsKeyDown(Keys.LeftControl) || keyState.IsKeyDown(Keys.RightControl)))\r
- {\r
- paused = !paused;\r
- return; // Don't update on pause start or stop\r
+ break;\r
+\r
+ // Mouse Move\r
+ case 5:\r
+ short x = packetReader.ReadInt16();\r
+ short y = packetReader.ReadInt16();\r
+\r
+ break;\r
}\r
+ }\r
\r
- // Debug - automatically pause every 1000 frames.\r
\r
- if (mGame.CurrentFrameNumber % 1000 == 0 && mGame.CurrentFrameNumber != lastAutoPause)\r
+ private bool IsLatencyAdjustmentFrame\r
+ {\r
+ get\r
{\r
- paused = true;\r
- lastAutoPause = mGame.CurrentFrameNumber;\r
+ // TODO\r
+ return false;\r
}\r
+ }\r
+\r
+ private void AdjustLatency()\r
+ {\r
+ // TODO\r
+ }\r
\r
\r
- //if (pressedKeys.Contains(Keys.Escape))\r
- // this.Exit();\r
+ private void SendLocalEvents()\r
+ {\r
+ // TODO: Not finished.\r
\r
- // Game update\r
+ KeyboardState keyState = Keyboard.GetState();\r
+ MouseState mouseState = Mouse.GetState();\r
\r
- // Direct inputs to the game engine - only report changes.\r
+ // Make a list of the keys pressed or released this frame.\r
\r
- foreach (Keys k in pressedKeys)\r
- mGame.ApplyKeyInput(activePlayer, k, true);\r
+ List<Keys> pressedKeys = new List<Keys>();\r
+ List<Keys> releasedKeys = new List<Keys>();\r
\r
- foreach (Keys k in releasedKeys)\r
- mGame.ApplyKeyInput(activePlayer, k, false);\r
+ Keys[] pressedKeysArray = keyState.GetPressedKeys();\r
+ foreach (Keys k in pressedKeysArray)\r
+ if (!mLastPressedKeys.Contains(k))\r
+ pressedKeys.Add(k);\r
+ else\r
+ mLastPressedKeys.Remove(k);\r
\r
- mGame.ApplyMouseLocationInput(activePlayer, mouseState.X, mouseState.Y);\r
+ releasedKeys = mLastPressedKeys;\r
+ mLastPressedKeys = new List<Keys>(pressedKeysArray);\r
\r
- if (lastButtonPressed != buttonPressed)\r
- mGame.ApplyMouseButtonInput(activePlayer, buttonPressed);\r
+ bool buttonPressed = mouseState.LeftButton == ButtonState.Pressed;\r
+ }\r
\r
- lastButtonPressed = buttonPressed;\r
\r
- if (!paused)\r
+ private bool HaveNeededEvents\r
+ {\r
+ get\r
{\r
- // Advance the game engine.\r
-\r
- mGame.Update(mTargetTimeSpan);\r
+ // TODO\r
+ return true;\r
}\r
}\r
\r
- void DrawTestHarness(GameTime gameTime, SpriteBatch spriteBatch)\r
+ private void ApplyEvents()\r
{\r
+ // TODO\r
+ }\r
\r
- // BEGIN: Test harness stuff.\r
- if (paused && gameTime.TotalRealTime.Milliseconds < 500)\r
- spriteBatch.DrawString(font, "-=> Paused <=-", new Vector2(10, 130), Color.White);\r
-\r
- 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);\r
- spriteBatch.DrawString(font, "Press [ESC] to exit and [F12] to pause/unpause. Game auto-pauses every 1000 frames.", new Vector2(10, 570), Color.White);\r
- //END: Test harness stuff.\r
\r
+ private int AverageOneWayDelay\r
+ {\r
+ get\r
+ {\r
+ // TODO\r
+ return 12;\r
+ }\r
}\r
+\r
+ #endregion\r
}\r
}\r