Click here to Skip to main content
15,885,278 members
Articles / Programming Languages / C#

A Game Lobby System in C#

Rate me:
Please Sign up or sign in to vote.
4.73/5 (31 votes)
21 Oct 2008CPOL8 min read 287.2K   3.6K   105  
A simple lobby server for hosting multiple small games and allowing players to create and join games of many types.
// created on 20/11/2005 at 13:36

// Stuff for game types. Note that this is all client-side 
// (and interfaces require ClientLobby parameters) because
// the server never needs to know *what* game you're playing.
using System;
using System.Text;
using System.Windows.Forms;

namespace RedCorona.Net.Games {
	public interface IGame {
		// Game types must support these members to allow
		// the server to manage its games correctly
		bool ProcessCode(ClientLobbyProxy lobby, MemberInfo mem, uint code, byte[] bytes, int len);
		Form GetCustomSetupForm();
		void RequestStartGame();
		void StartGame();
		void CloseGame();
	}
	
	public interface IGameType {
		string URL { get; }
		string Version { get; }
		bool CanCreate { get; }
		int ValidateMaxPlayers(int value);
		IGame CreateClientsideGame(ClientLobbyProxy lobby, GameInfo gi);
	}
	
	public abstract class BaseGame: MarshalByRefObject, IGame {
		protected ClientLobbyProxy lobby;
		protected GameInfo game;
		protected Form SetupForm, MainForm;
		protected bool amOwner;
		
		public GameInfo Game { get { return game; } }
		public ClientLobbyProxy Lobby { get { return lobby; } }
		
		public bool OwnsGame { get { return game.CreatorID == lobby.MyID; } }
		
		protected BaseGame(ClientLobbyProxy lobby, GameInfo gi){
			this.lobby = lobby; game = gi;
			amOwner = gi.CreatorID == lobby.MyID;
		}
		
		public virtual bool ProcessCode(ClientLobbyProxy lobby, MemberInfo mem, uint code, byte[] bytes, int len){
			try {
				ByteBuilder b = new ByteBuilder(bytes, len);
				int pi = 0;
				switch(code){
					case ReservedCodes.Chat:
						int scope = ClientInfo.GetInt(b.GetParameter(ref pi).content, 0, 4);
						if(scope == ChatScope.Lobby) break;
						int memberid = ClientInfo.GetInt(b.GetParameter(ref pi).content, 0, 4);
						String msg = Encoding.UTF8.GetString(b.GetParameter(ref pi).content);
						int id = ClientInfo.GetInt(b.GetParameter(ref pi).content, 0, 4);
Console.WriteLine("Chat, scope "+scope+" in game "+id);						
						if(id == game.ID)	ProcessGameChat(memberid, msg, scope);
						break;
					default:
						// If it's been passed to our game, call the 'easy' cover version
						id = ClientInfo.GetInt(b.GetParameter(ref pi).content, 0, 4);
						if(id == game.ID)	return ProcessGameCode(lobby, code, b, ref pi);
						break;
				}
			} catch(ArgumentException) {}
			catch(IndexOutOfRangeException) {}
			return true; // allow normal processing
		}
		
		public virtual void ProcessGameChat(int MemberID, String msg, int scope){
			// Override to do nice things with chat within this game
		}
		
		public virtual bool ProcessGameCode(ClientLobbyProxy lobby, uint code, ByteBuilder data, ref int datapos){
			try {
			switch(code){
				case ReservedCodes.StartGame:
					Console.WriteLine("Starting custom game code");
					StartGame();
					break;
			}
			} catch(Exception e){
				Console.WriteLine("Error running custom game code: "+e);
			}
			return true;
		}
		
		public virtual void SetData(String key, Parameter p, bool global, uint globalflags){
			ByteBuilder bb = new ByteBuilder(9);
			bb.AddParameter(ClientInfo.IntToBytes(game.ID), ParameterType.Int);
			if(global)
				bb.AddParameter(ClientInfo.UintToBytes(globalflags), ParameterType.Uint);
			bb.AddParameter(Encoding.UTF8.GetBytes(key), ParameterType.String);
			bb.AddParameter(p);
			lobby.SendMessage(global ? ReservedCodes.SetGameTypeToken : ReservedCodes.SetGameToken, bb.Read(0, bb.Length), 0);
		}
		
		public virtual void SetData(String key, String value, bool global, uint globalflags){
			SetData(key, new Parameter(Encoding.UTF8.GetBytes(value), ParameterType.String), global, globalflags);
		}
		
		public virtual void SetData(String key, int value, bool global, uint globalflags){
			SetData(key, new Parameter(ClientInfo.IntToBytes(value), ParameterType.Int), global, globalflags);
		}
		
		public void SendToGameOwner(uint code, byte[] msg, byte ParamType){
			ByteBuilder bb = new ByteBuilder();
			bb.AddParameter(ClientInfo.UintToBytes(code), ParameterType.Uint);
			//bb.AddParameter(ClientInfo.IntToBytes(game.CreatorID), ParameterType.Int);
			bb.AddParameter(ClientInfo.IntToBytes(game.ID), ParameterType.Uint);
			if(ParamType != 0) bb.AddParameter(msg, ParamType);
			else bb.Add(msg);
			lobby.SendMessage(ReservedCodes.ToGameOwner, bb.Read(0, bb.Length), 0);
		}
		
		public virtual Form GetCustomSetupForm(){
			return SetupForm; // default stuff is adequate
		}
		
		public virtual void RequestStartGame(){
			// If we're the owner, this gets called when the user actually
			// asks to start the game. Examples of sensible overrides:
			//  - Check all compulsory positions are filled
			if(!OwnsGame) return;
			if(AllAreReady()) {
				lobby.SendMessage(ReservedCodes.StartGame, BaseLobby.PrepareTwoIDs(game.ID, 0x4), 0);
			} else lobby.SendMessage(ReservedCodes.GameBroadcast, BaseLobby.PrepareTwoIDs((int)ReservedCodes.ReadyToStart, game.ID), 0);
		}
		
		public virtual bool AllAreReady(){
			// Override if standard ready test is not good enough
			for(int i = 0; i < game.PlayerFlags.Length; i++){
				if(game.Players[i] == game.CreatorID) continue; // assume creator is always ready
				if(0 == (game.PlayerFlags[i] & PlayerGameFlags.Ready))
					return false;
			}
			return true;
		}
		
		public virtual void StartGame(){
			// Set the game flag to 'In Progress'
			
		}
		public virtual void CloseGame(){
			if(MainForm != null){
				MainForm.Close();
				MainForm = null;
			}
			if(SetupForm != null){
				SetupForm.Close();
				SetupForm = null;
			}
		}
	}
	
	public abstract class BaseGameType : MarshalByRefObject, IGameType {
		public abstract String URL { get; }
		public abstract string Version { get; }
		public virtual bool CanCreate { get { return true; } }
		
		public int ValidateMaxPlayers(int value){ return value; }
		public abstract IGame CreateClientsideGame(ClientLobbyProxy lobby, GameInfo gi);
		
		public static Version GetVersion(string Version){
			string[] sa = Version.Split(' ')[0].Split('.');
			try { return new Version(Int32.Parse(sa[0]), Int32.Parse(sa[1]), Int32.Parse(sa[2]));
			} catch { return new Version(); }
		}
	}
	
	public interface IServersideGame {
		GameInfo Game { get; set; }
		ServerLobbyProxy Lobby { get; set; }
		
		void Initialise();
		void Close();
		bool CanJoin(int memberID, int requestID, string password, out string msg);
		void Joined(int memberID);
		void Left(int memberID);
		bool GameMessage(int memberID, uint code, ByteBuilder bb, int startPos);
		//HttpResponse HandleWebMessage(HttpRequest r);
	}
	
	public interface IServersideGameType {
		string URL { get; }
		string Version { get; }
		IServersideGame CreateGame(ServerLobbyProxy lobby, GameInfo gi);
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
I'm a recent graduate (MSci) from the University of Cambridge, no longer studying Geology. Programming is a hobby so I get to write all the cool things and not all the boring things Smile | :) . However I now have a job in which I have to do a bit of work with a computer too.

Comments and Discussions