There are a few things you need to consider when designing such a network protocol. Do you want to have a dedicated server, player hosted or peer to peer (from your first post it looks like you want a player hosted game) and the question if the players are in a local network or you need to connect over the internet.
Generally I would recommend you a dedicated server approach, the dedicated server can than be hosted by one of the players, but this generally makes it easier to have game clients and game server logic separated. If the host wants to join the game, they are just another client for the server.
Also for the beginning I would recommend you for now to focus on local network, as this is much easier for a beginner.
Then the question is requirements you have to the handling of the connection and the flow of the game. Is you game very structured, e.g. turn based (e.g. chess where each player can only continue after the other has made their move) or is it a more chaotic real time where the game is going forth if the players react or not. Then how are your delay requirements and can you deal with package loss, e.g. in chess delay doesn't matter and if your move "get's lost" you get into trouble, while in a first person shooter, delay is crucial, and if one shot of you is lost it doesn't matter as much as you can just shoot again.
From this you can then select your protocols. general rule of thumb: Low latency -> UDP, structured and reliable -> TCP. You can also of course use higher level protocols, e.g. you could use Websockets if you want a TCP based event driven but connection based flow (so where each client has one persistent connection), or HTTP if you have a stateless request-response flow (for most games not realy fitted as most games the server might want to initiate communication and in HTTP the server can only send data to the client as a response to a request from the client)
From my understanding tabletop games are usually more a structured game, where a situation occurs and every player choses their moves and then the reacts to those moves. So you could go with a very simpe TCP based system where each client connects to the server and then when it's their turn, the server just awaits data from them, and because no one else is able to do anything during this players phase, the server can pretty much just sit and wait for that players client to make their move. This is rather easy to build with raw TCP.
But you might want to have multiple channels for connections anyway, for example if you want to have a chat function, best to create a new connection for the chat which simplifies dealing with distinquishing between the different kinds of data (with different formats).
Lastly there is the question on how much you trust your participants. If you don't a naive approach (like described above) might open the door for exploitation, e.g. looking at the example above, a malicious player could literally just waste time by not sending any data, while all other clients have to wait. So you might need additional security mechanisms.
For the beginning I would keep it simple, start with a local network TCP based apporach and look how far you come. Simple TCP communication can be done using the "ssockets" unit, which provides everything you need to get started with a TCP client-server architecture.
A very simple server with ssockets looks like this:
program Project1;
{$mode objfpc}{$H+}
uses
classes, Generics.Collections, ssockets;
type
TPlayerMove = record
// Player move information
end;
TConnectionList = specialize TThreadList<TInetSocket>;
{ TGameHandler }
TGameHandler = class(TThread)
private
FClients: TConnectionList;
function ConnectedPlayers: Integer;
function GetConnection(AIndex: Integer): TInetSocket;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure ClientConnected(Sender: TObject; Connection: TSocketStream);
end;
const MAX_PLAYERS = 4;
const MIN_PLAYERS = 2;
{ TGameHandler }
function TGameHandler.ConnectedPlayers: Integer;
var
clients: specialize TList<TInetSocket>;
begin
clients := FClients.LockList;
try
Result := clients.Count;
finally
FClients.UnlockList;
end;
end;
function TGameHandler.GetConnection(AIndex: Integer): TInetSocket;
var
clients: specialize TList<TInetSocket>;
begin
clients := FClients.LockList;
try
Result := clients[AIndex];
finally
FClients.UnlockList;
end;
end;
procedure TGameHandler.Execute;
var
Player: TInetSocket;
NextMove: TPlayerMove;
begin
while ConnectedPlayers < MIN_PLAYERS do
Sleep(100); // Wait for enough players to start
// Game loop here
// e.g.
Player := GetConnection(0); // get first player
Player.Read(NextMove, SizeOf(NextMove)); // get next move from player, will wait until information is sent
// Do something with next move
// etc
end;
constructor TGameHandler.Create;
var
clients: specialize TList<TInetSocket>;
begin
clients := FClients.LockList;
try
while clients.Count > 0 do
begin
clients[0].Free;
clients.Delete(0);
end;
finally
FClients.UnlockList;
end;
FClients := TConnectionList.Create;
inherited Create;
FreeOnTerminate := True;
end;
destructor TGameHandler.Destroy;
begin
FClients.Free;
inherited Destroy;
end;
procedure TGameHandler.ClientConnected(Sender: TObject; Connection: TSocketStream);
begin
if ConnectedPlayers < MAX_PLAYERS then
Connection.Free
else
FClients.Add(Connection); // Additional code to handle new players here
end;
var
Server: TInetServer;
GameHandler: TGameHandler;
begin
GameHandler := TGameHandler.Create;
GameHandler.FreeOnTerminate := True;
Server := TInetServer.Create('0.0.0.0', 1337); // 0 IP -> All ips can be bound, port 1337
try
Server.OnConnect:=GameHandler.ClientConnected;
Server.StartAccepting;
finally
Server.Free;
end;
end.