Recent

Author Topic: how to make online game  (Read 13085 times)

piGrimm

  • Guest
Re: how to make online game
« Reply #15 on: November 10, 2017, 09:01:15 am »
lNet is the best with its TLCLEventer, indy is huge, oldschool, and cumbersome
https://lnet.wordpress.com
lNet – Lightweight networking library for Pros
with TLCLEventer, the main app loop is hooked nicely, while async socket I/O work clean in the background
Usage is simple and transparent, with the lNet Visual Components, so you can concentrate on your game, instead of reading tons of (almost dead indy) docs  :D
« Last Edit: November 10, 2017, 09:04:20 am by piGrimm »

shs

  • Sr. Member
  • ****
  • Posts: 310
Re: how to make online game
« Reply #16 on: November 11, 2017, 02:06:21 am »
@Mr.Madguy
can you show that code on lazarus please?

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: how to make online game
« Reply #17 on: November 11, 2017, 02:58:48 pm »
@Mr.Madguy
can you show that code on lazarus please?
What code? Initial Delphi code? I had no time to find good enough networking library or implement threaded code myself. Implementation is very simple - server thread is used to listen port and client threads are used to call Accept inside them, so OnAccept actually happens in another thread, that terminates, when OnAccept ends, so there is nothing bad in using blocking operations (like read) there. But if you mean second code, where example of exiting from message loop is shown - it's Lazarus code.
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

shs

  • Sr. Member
  • ****
  • Posts: 310
Re: how to make online game
« Reply #18 on: November 18, 2017, 08:00:08 am »
hey sorry i was away for the past few weeks.
yes in lazarus code, can you show me an example when you have time?
there's a question i want to ask you. i now managed to made the server and client program but what if there's more than 1 client? how can i create another box? because the shape is always created in the form in the code you gave me
thank you :)

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: how to make online game
« Reply #19 on: November 18, 2017, 09:47:59 am »
hey sorry i was away for the past few weeks.
yes in lazarus code, can you show me an example when you have time?
there's a question i want to ask you. i now managed to made the server and client program but what if there's more than 1 client? how can i create another box? because the shape is always created in the form in the code you gave me
thank you :)
I can't provide example right now, but it isn't hard. May be later today or on Monday. It just requires some basic protocol and client identification. You should understand, that server can accept connections from more than one client - you just need to keep listening to port. Server should start to broadcast data between clients then, cuz every client should know everything about all other clients. And it requires some basic protocol, like <client X connected>, <client X disconnected>, <coord change for client X>. It's like IRC chat.

In my example server also plays role of one of clients. It was like that for old LAN games. In current games server usually doesn't have client functionality. Server usually just processes game logic and broadcasts data between clients.

So, what you need for server:
1) You need to implement some TClient class. It should store client connection, client ID and client coordinates.
2) When new client connects - you need to create new TClient object, when disconnects - destroy it. Store them in list.
3) Send <client X connected> and <client X disconnected> messages to every client, when it happens.
4) TClient should have some sort of DataChanged flag - in order to avoid excessive traffic.
5) On every frame server should go through this list of clients and send data to every client about other clients (if this data has been changed) - i.e. just broadcast information.

What you need for client:
1) You need to implement some TClient class. It should store client ID, coordinates and reference to TShape object.
2) When new client connects - you need to create new TClient object, when disconnects - destroy it. Store them in list.
3) When <client X connected> message is received - create new shape via NewShape := TShape.Create(Form);NewShape.Parent := Form;
4) When <client X disconnected> message is received - destroy it via Form.RemoveControl(Shape); Don't call Destroy/Free directly!!!
5) When <client X coord changed> message is received - change coordinates for Client[X].Shape.

Everything else - is the same, as it was.
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

rvk

  • Hero Member
  • *****
  • Posts: 6162
Re: how to make online game
« Reply #20 on: November 18, 2017, 09:51:34 am »
You already have some code for multiple clients we gave in your other topic "can i make serve?". Try to change the code in this topic yourself for once. Otherwise you'll never learn.

Next I see you removed your post with your code in that topic. That's not a very nice thing to do. We do all the work and you won't show your code (for future readers/beginners). I hope Mr.Madguy doesn't do all the same work.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: how to make online game
« Reply #21 on: November 18, 2017, 05:19:10 pm »
You already have some code for multiple clients we gave in your other topic "can i make serve?". Try to change the code in this topic yourself for once. Otherwise you'll never learn.

Next I see you removed your post with your code in that topic. That's not a very nice thing to do. We do all the work and you won't show your code (for future readers/beginners). I hope Mr.Madguy doesn't do all the same work.
I specialize on UI, graphics and data structures. There are some areas of programming, where I have theoretical knowledge only, so it would be interesting for me to get some experience in this areas. Networking - is one of this areas. That's why I try to help with this programs - I want to check, if I can do it myself, not just help.

Here is examples. Delphi again, cuz I don't have time to find good enough library for Lazarus.

UPD: All problems are fixed now!

Server:
Code: Pascal  [Select][+][-]
  1. unit ServerMain;
  2.  
  3. interface
  4.  
  5. uses
  6.   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  7.   Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Web.Win.Sockets, Vcl.ExtCtrls,
  8.   Vcl.StdCtrls, Generics.Collections, System.SyncObjs;
  9.  
  10. type
  11.   TDataType = (dtConnect, dtDisconnect, dtCoords);
  12.   TCoords = record
  13.     X, Y:Integer;
  14.   end;
  15.   TData = record
  16.     DataType:TDataType;
  17.     ClientId:Integer;
  18.     Coords:TCoords;
  19.   end;
  20.   TClientState = (csConnect, csActive, csDisconnect, csRemove);
  21.   TClient = class
  22.     State:TClientState;
  23.     Id:Integer;
  24.     Coords:TCoords;
  25.     Changed:Boolean;
  26.     Socket:TCustomIpClient;
  27.     function Send(var ABuffer;ASize:Integer):Integer;
  28.   end;
  29.   TClientList = TList<TClient>;
  30.   TForm1 = class(TForm)
  31.     Memo1: TMemo;
  32.     Timer1: TTimer;
  33.     TcpServer1: TTcpServer;
  34.     procedure TcpServer1Accept(Sender: TObject; ClientSocket: TCustomIpClient);
  35.     procedure FormCreate(Sender: TObject);
  36.     procedure FormDestroy(Sender: TObject);
  37.     procedure Timer1Timer(Sender: TObject);
  38.   private
  39.     { Private declarations }
  40.   public
  41.     { Public declarations }
  42.     Clients:TClientList;
  43.     Sect:TCriticalSection;
  44.     CurrentId:Integer;
  45.     function GenerateId:Integer;
  46.   end;
  47.  
  48. var
  49.   Form1: TForm1;
  50.  
  51. implementation
  52.  
  53. {$R *.dfm}
  54.  
  55. function TClient.Send(var ABuffer;ASize:Integer):Integer;
  56.   var ReadReady, WriteReady, ExceptionHappened:Boolean;
  57. begin
  58.   Result := 0;
  59.   Socket.Select(@ReadReady, @WriteReady, @ExceptionHappened);
  60.   if WriteReady and (not ExceptionHappened) then begin
  61.     Result := Socket.SendBuf(ABuffer, ASize);
  62.   end;
  63. end;
  64.  
  65. procedure TForm1.FormCreate(Sender: TObject);
  66. begin
  67.   CurrentId := 0;
  68.   Sect := TCriticalSection.Create;
  69.   Sect.Acquire;
  70.   Clients := TClientList.Create;
  71.   Sect.Release;
  72.   Memo1.Lines.Add('Server started: ' + String(TcpServer1.LocalHost) + ':' + String(TcpServer1.LocalPort));
  73. end;
  74.  
  75. procedure TForm1.FormDestroy(Sender: TObject);
  76. begin
  77.   Sect.Acquire;
  78.   FreeAndNil(Clients);
  79.   Sect.Release;
  80.   FreeAndNil(Sect);
  81. end;
  82.  
  83. function TForm1.GenerateId:Integer;
  84. begin
  85.   Result := CurrentId;
  86.   Inc(CurrentId);
  87. end;
  88.  
  89. procedure TForm1.TcpServer1Accept(Sender: TObject;
  90.   ClientSocket: TCustomIpClient);
  91.   var Client:TClient;Buffer:TData;
  92. begin
  93.   Client := nil;
  94.   if Assigned(Sect) then begin
  95.     Sect.Acquire;
  96.     if Assigned(Clients) then begin
  97.       Client := TClient.Create;
  98.       Client.State := csConnect;
  99.       Client.ID := GenerateId;
  100.       Client.Socket := ClientSocket;
  101.       Clients.Add(Client);
  102.     end;
  103.     Sect.Release;
  104.   end;
  105.   if not Assigned(Client) then Exit;
  106.   while ClientSocket.WaitForData(-1) do begin
  107.     if ClientSocket.ReceiveBuf(Buffer, SizeOf(Buffer)) = SizeOf(Buffer) then begin
  108.       if Buffer.DataType = dtCoords then begin
  109.         if (Buffer.Coords.X <> Client.Coords.X) or
  110.         (Buffer.Coords.Y <> Client.Coords.Y)
  111.         then begin
  112.           if Assigned(Sect) then begin
  113.             Sect.Acquire;
  114.             Client.Coords := Buffer.Coords;
  115.             Client.Changed := True;
  116.             Sect.Release;
  117.           end;
  118.         end;
  119.       end;
  120.     end;
  121.   end;
  122.   if Assigned(Sect) then begin
  123.     Sect.Acquire;
  124.     if Client.State = csConnect then begin
  125.       Client.State := csRemove;
  126.     end
  127.     else begin
  128.       Client.State := csDisconnect;
  129.     end;
  130.     Client.Socket := nil;
  131.     Sect.Release;
  132.   end;
  133. end;
  134.  
  135. procedure TForm1.Timer1Timer(Sender: TObject);
  136.   var Client1, Client2:TClient;Buffer:TData;
  137.   ClientsToRemove:TClientList;Flag:Boolean;
  138. begin
  139.   ClientsToRemove := TClientList.Create;
  140.   Sect.Acquire;
  141.   if Assigned(Clients) then begin
  142.     for Client1 in Clients do begin
  143.       FillChar(Buffer, SizeOf(Buffer), 0);
  144.       Buffer.ClientId := Client1.Id;
  145.       case Client1.State of
  146.         csConnect:begin
  147.           Buffer.DataType := dtConnect;
  148.           Flag := True;
  149.           for Client2 in Clients do begin
  150.             if Client1.Id <> Client2.Id then begin
  151.               Flag := Flag and (Client2.Send(Buffer, SizeOf(Buffer)) = SizeOf(Buffer));
  152.             end;
  153.           end;
  154.           for Client2 in Clients do begin
  155.             if Client1.Id <> Client2.Id then begin
  156.               Buffer.DataType := dtConnect;
  157.               Buffer.ClientId := Client2.Id;
  158.               Flag := Flag and (Client1.Send(Buffer, SizeOf(Buffer)) = SizeOf(Buffer));
  159.               Buffer.DataType := dtCoords;
  160.               Buffer.Coords := Client2.Coords;
  161.               Flag := Flag and (Client1.Send(Buffer, SizeOf(Buffer)) = SizeOf(Buffer));
  162.             end;
  163.           end;
  164.           if Flag then begin
  165.             Memo1.Lines.Add('Client Id = ' + IntToStr(Client1.Id) + ' connected from ' +
  166.             String(Client1.Socket.RemoteHost) + ':' + String(Client1.Socket.RemotePort));
  167.             Client1.State := csActive;
  168.           end;
  169.         end;
  170.         csActive:begin
  171.           if Client1.Changed then begin
  172.             Buffer.DataType := dtCoords;
  173.             Buffer.Coords := Client1.Coords;
  174.             Flag := True;
  175.             for Client2 in Clients do begin
  176.               if Client1.Id <> Client2.Id then begin
  177.                 Flag := Flag and (Client2.Send(Buffer, SizeOf(Buffer)) = SizeOf(Buffer));
  178.               end;
  179.             end;
  180.             if Flag then begin
  181.               Client1.Changed := False;
  182.             end;
  183.           end;
  184.         end;
  185.         csDisconnect:begin
  186.           Buffer.DataType := dtDisconnect;
  187.           Flag := True;
  188.           for Client2 in Clients do begin
  189.             if Client1.Id <> Client2.Id then begin
  190.               Flag := Flag and (Client2.Send(Buffer, SizeOf(Buffer)) = SizeOf(Buffer));
  191.             end;
  192.           end;
  193.           if Flag then begin
  194.             Memo1.Lines.Add('Client Id = ' + IntToStr(Client1.Id) + ' disconnected');
  195.             Client1.State := csRemove;
  196.           end;
  197.         end;
  198.         csRemove:begin
  199.           ClientsToRemove.Add(Client1);
  200.         end;
  201.       end;
  202.     end;
  203.   end;
  204.   Sect.Release;
  205.   Sect.Acquire;
  206.   if Assigned(Clients) then begin
  207.     for Client1 in ClientsToRemove do begin
  208.       Clients.Remove(Client1);
  209.       Client1.Free;
  210.     end;
  211.   end;
  212.   Sect.Release;
  213.   ClientsToRemove.Free;
  214. end;
  215.  
  216. end.

Client:
Code: Pascal  [Select][+][-]
  1. unit ClientMain;
  2.  
  3. interface
  4.  
  5. uses
  6.   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  7.   Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Web.Win.Sockets, Generics.Collections;
  8.  
  9. type
  10.   TDataType = (dtConnect, dtDisconnect, dtCoords);
  11.   TCoords = record
  12.     X, Y:Integer;
  13.   end;
  14.   TData = record
  15.     DataType:TDataType;
  16.     ClientId:Integer;
  17.     Coords:TCoords;
  18.   end;
  19.   TClient = class
  20.     Id:Integer;
  21.     Shape:TShape;
  22.     constructor Create(AId:Integer);
  23.     destructor Destroy;override;
  24.   end;
  25.   TClientList = TDictionary<Integer, TClient>;
  26.   TForm1 = class(TForm)
  27.     TcpClient1: TTcpClient;
  28.     Timer1: TTimer;
  29.     Shape1: TShape;
  30.     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  31.     procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  32.     procedure Timer1Timer(Sender: TObject);
  33.     procedure FormCreate(Sender: TObject);
  34.     procedure FormDestroy(Sender: TObject);
  35.   private
  36.     { Private declarations }
  37.   public
  38.     { Public declarations }
  39.     Keys:array[Word] of Boolean;
  40.     Clients:TClientList;
  41.     procedure Clear;
  42.   end;
  43.  
  44. var
  45.   Form1: TForm1;
  46.  
  47. implementation
  48.  
  49. {$R *.dfm}
  50.  
  51. constructor TClient.Create(AId:Integer);
  52. begin
  53.   Shape := TShape.Create(Form1);
  54.   Shape.Parent := Form1;
  55. end;
  56.  
  57. destructor TClient.Destroy;
  58. begin
  59.   Form1.RemoveControl(Shape);
  60. end;
  61.  
  62. procedure TForm1.FormCreate(Sender: TObject);
  63. begin
  64.   Clients := TClientList.Create;
  65. end;
  66.  
  67. procedure TForm1.FormDestroy(Sender: TObject);
  68. begin
  69.   Clear;
  70.   FreeAndNil(Clients);
  71. end;
  72.  
  73. procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  74.   Shift: TShiftState);
  75. begin
  76.   Keys[Key] := True;
  77. end;
  78.  
  79. procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  80. begin
  81.   Keys[Key] := False;
  82. end;
  83.  
  84. procedure TForm1.Timer1Timer(Sender: TObject);
  85.   var ReadReady, WriteReady, ExceptionHappened:Boolean;
  86.   Buffer:TData;Client:TClient;
  87. begin
  88.   if Keys[VK_UP] then Shape1.Top := Shape1.Top - 2;
  89.   if Keys[VK_DOWN] then Shape1.Top := Shape1.Top + 2;
  90.   if Keys[VK_LEFT] then Shape1.Left := Shape1.Left - 2;
  91.   if Keys[VK_Right] then Shape1.Left := Shape1.Left + 2;
  92.   if not Assigned(Clients) then Exit;
  93.   if TcpClient1.Connected then begin
  94.     TcpClient1.Select(@ReadReady, @WriteReady, @ExceptionHappened);
  95.     while ReadReady do begin
  96.       if ExceptionHappened or (TcpClient1.PeekBuf(Buffer, 1) <= 0) then begin
  97.         TcpClient1.Disconnect;
  98.         Break;
  99.       end;
  100.       if TcpClient1.ReceiveBuf(Buffer, SizeOf(Buffer)) = SizeOf(Buffer) then begin
  101.         case Buffer.DataType of
  102.           dtConnect:begin
  103.             Clients.Add(Buffer.ClientId, TClient.Create(Buffer.ClientId));
  104.           end;
  105.           dtDisconnect:begin
  106.             if Clients.ContainsKey(Buffer.ClientId) then begin
  107.               Client := Clients[Buffer.ClientId];
  108.               if Assigned(Client) then begin
  109.                 Client.Free;
  110.                 Clients.Remove(Buffer.ClientId);
  111.               end;
  112.             end;
  113.           end;
  114.           dtCoords:begin
  115.             if Clients.ContainsKey(Buffer.ClientId) then begin
  116.               Client := Clients[Buffer.ClientId];
  117.               if Assigned(Client) then begin
  118.                 Client.Shape.Left := Buffer.Coords.X;
  119.                 Client.Shape.Top := Buffer.Coords.Y;
  120.               end;
  121.             end;
  122.           end;
  123.         end;
  124.       end;
  125.       TcpClient1.Select(@ReadReady, @WriteReady, @ExceptionHappened);
  126.     end;
  127.     if WriteReady and (not ExceptionHappened) then begin
  128.       Buffer.DataType := dtCoords;
  129.       Buffer.ClientId := 0;
  130.       Buffer.Coords.X := Shape1.Left;
  131.       Buffer.Coords.Y := Shape1.Top;
  132.       TcpClient1.SendBuf(Buffer, SizeOf(Buffer));
  133.     end;
  134.   end
  135.   else begin
  136.     Clear;
  137.   end;
  138. end;
  139.  
  140. procedure TForm1.Clear;
  141.   var Client:TClient;
  142. begin
  143.   for Client in Clients.Values do begin
  144.     Client.Free;
  145.   end;
  146.   Clients.Clear;
  147. end;
  148.  
  149. end.
  150.  
« Last Edit: November 18, 2017, 09:31:56 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

shs

  • Sr. Member
  • ****
  • Posts: 310
Re: how to make online game
« Reply #22 on: November 22, 2017, 12:57:30 pm »
@rvk sorry i won't do that again. but i didn't delete it on this topic.
@mr madguy
hi so i managed to create the shape if client connects to the server but i don't know how to send coord for each clients.
let's say there are 5 clients conected to the server. if i send client 1's coord to other clients, how can they know that it's client 1's coord? how can they check if it's client 1?

here are my server and client program

rvk

  • Hero Member
  • *****
  • Posts: 6162
Re: how to make online game
« Reply #23 on: November 22, 2017, 01:47:22 pm »
Ok, for those who are new here some explanation about given code:

Server is only used to pass on every keystroke/movement of all the clients to all other clients.
  • In FormActivate() it starts listening for new clients.
  • In Timer1Timer() it detects a new client and adds it to the Connections[] array. It also sends out 0 to indicate a new client and the client-number to the new client.
  • In Timer2Timer() it receives coordinates from clients and passes it to all other clients.

Client is where the game is.
  • In FormActivate() it connects to the server.
  • In Timer1Timer() it creates a new box (regardless what)... (why?)
  • In Timer2Timer() it receives 0 it will add a client and box. If it receives a number >0 it is the new client itself and creates all boxes for the other clients.

Now... you receive and send out coordinates. You do have a name field in the record but don't use it. Why not.

What I would do:
Code: Pascal  [Select][+][-]
  1. type
  2.   TCoords = record
  3.     MeClient: Integer;
  4.     MaxClient: Integer;
  5.     X, Y:Integer;
  6.   end;
  7.   TClient = record
  8.     Sock:TTCPBlockSocket;
  9.     Coords:TCoords;
  10.     Box:Tshape;
  11.   end;
You see a new MeClient and MaxClient here.
These would only be filled in by the server !!

So the server receives from connection[5] a Coordinates with MeClient=0 and MaxClient=0. It will fill in the MeClient with the I from Connections[i].sock.recvbuffer. And MaxClient with high(connections).

Now this record is send out to all the clients. Those clients can see from the MeClient which box to move. And from MaxClient if they need to add any new/extra boxes (for clients they don't have added yet).

In that case you don't have to send 0 (or >0) to the clients. You can just send an empty Coordinates when the client connects with the correct MeClient and MaxClient.

Try to change the code to do that.
« Last Edit: November 22, 2017, 01:50:27 pm by rvk »

shs

  • Sr. Member
  • ****
  • Posts: 310
Re: how to make online game
« Reply #24 on: November 22, 2017, 02:00:14 pm »
wait sorry i don't get it.
so how will the code look like?
connections[5].recvbuffer(@coords, sizeof(coords));
where do i put the meclient and maxclient

Quote
Now this record is send out to all the clients. Those clients can see from the MeClient which box to move
how can that work?

rvk

  • Hero Member
  • *****
  • Posts: 6162
Re: how to make online game
« Reply #25 on: November 22, 2017, 05:09:19 pm »
where do i put the meclient and maxclient
Exactly where I showed you with the new record structure TCoords.

Quote
Quote
Now this record is send out to all the clients. Those clients can see from the MeClient which box to move
how can that work?
Now you don't even read in the TCoord which is send by the server.
The only communication you do now is sending 0 or >0 from the server to client
and nothing from the client to the server.

I propose you don't send 0 or >0 but a complete TCoord.

I do think you need another ID.
Code: Pascal  [Select][+][-]
  1. type
  2.   TCoords = record // same as in the server
  3.     MeClient, MaxClient: Integer;
  4.     ForClient, X, Y:Integer;
  5.   end;
  6.   TClient = record // no need for sock here
  7.     Coords: TCoords;
  8.     Box: Tshape;
  9.   end;

Then...
When the client connects you don't do anything in the client.
In the server, when a new client connects, you make a new connection in the array.

Code: Pascal  [Select][+][-]
  1.     SetLength(Connections, Length(Connections) + 1);
  2.     Connections[high(Connections)].Sock := TTCPBlockSocket.Create;
  3.     Connections[high(Connections)].Sock.socket := ListenerSocket.Accept;
  4.     Connections[high(Connections)].Name := 'Client ' + IntToStr(length(Connections));
  5.     Connections[high(Connections)].Coords.ForClient := high(Connections);
  6.     Connections[high(Connections)].Coords.X := 25 + (25 * high(Connections)); // initial position
  7.     Connections[high(Connections)].Coords.Y := 25 + (25 * high(Connections)); // initial position
  8.     memo1.Lines.add(Connections[high(connections)].Name + 'is connected');
  9.  
  10.     // update all clients with MaxClient
  11.     for j := 0 to high(connections) do
  12.     begin
  13.       Connections[j].Coords.MaxClient := high(Connections);
  14.     end;
  15.  
  16.     // now send the NEW client to all clients other than NEW
  17.     for j := 0 to high(connections) - 1 do
  18.     begin
  19.       Connections[high(connections)].Coords.MeClient := Connections[j].Coords.ForClient; // always send Coords with MeClient as its ID
  20.       Connections[j].sock.sendbuffer(@Connections[high(connections)].Coords, sizeof(Connections[high(connections)].Coords));
  21.     end;
  22.  
  23.     // and send all clients to the NEW client
  24.     for j := 0 to high(connections) do
  25.     begin
  26.       Connections[j].Coords.MeClient := high(connections); // always send Coords with MeClient as its ID
  27.       Connections[high(connections)].sock.sendbuffer(@Connections[j].Coords, sizeof(Connections[j].Coords));
  28.     end;
  29.  
  30.     Timer2.Enabled := high(connections) > -1;

So, MeClient is always the client ID on the server and when you send a TCoords to a client the MeClient it receives always contains its ID. And MaxClient is always the number of clients.

In the Timer2 on the server you can do this:
Code: Pascal  [Select][+][-]
  1. var Cd: TCoords;
  2. //.....
  3.   while i <= high(connections) do
  4.   begin
  5.     if Connections[i].sock.canread(100) then
  6.     begin
  7.  
  8.       Connections[i].sock.recvbuffer(@Cd, sizeof(Cd));
  9.  
  10.       if (Cd.X <> Connections[i].Coords.X) or (Cd.X <> Connections[i].Coords.Y) then
  11.       begin
  12.         memo1.Lines.add(Connections[i].Name + ' moved');
  13.         Connections[i].Coords.X := Cd.X;
  14.         Connections[i].Coords.Y := Cd.Y;
  15.         for j := 0 to high(connections) do // include own client
  16.         begin
  17.           Connections[i].Coords.MeClient := Connections[j].Coords.ForClient; // always send Coords with MeClient as its ID
  18.           Connections[j].sock.sendbuffer(@Connections[i].Coords, sizeof(Connections[i].Coords));
  19.         end;
  20.       end;
  21.  
  22.     end;
  23.  

Now on the client side you always receive a complete Coords record.
If the MaxClient > high(client) you can add a box for it.

Now for the Client. Delete Timer1. You don't need it. You receive a Coord back from the server. At THAT point you create all boxes.

Code: Pascal  [Select][+][-]
  1.   if Sock.canread(100) then
  2.   begin
  3.     Sock.recvbuffer(@Cd, sizeof(Cd)); // we receive a package
  4.  
  5.     if Cd.MaxClient > High(Clients) then // can be > +1 bigger
  6.       setlength(clients, Cd.MaxClient + 1);
  7.  
  8.  
  9.     clients[Cd.ForClient].Coords := Cd;
  10.     if not assigned(clients[Cd.ForClient].box) then // that's why we dynamically add box when needed
  11.     begin
  12.       clients[Cd.ForClient].box := Tshape.Create(self);
  13.       clients[Cd.ForClient].box.parent := self;
  14.       clients[Cd.ForClient].box.Width := 25;
  15.       clients[Cd.ForClient].box.shape := stsquare;
  16.     end;
  17.     clients[Cd.ForClient].box.top := Cd.Y;
  18.     clients[Cd.ForClient].box.left := Cd.X;
  19.     clients[Cd.ForClient].box.brush.color := clBlack;
  20.     if Cd.ForClient = Cd.MeClient then
  21.       clients[Cd.ForClient].box.brush.color := clyellow;
  22.  
  23.   end;

I've attached the complete client and server I have working here.

One problem... when you delete a client, it can crash.
(maybe it's a better idea to send over the whole array, or have a special delete package)

But play with what you got now first.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: how to make online game
« Reply #26 on: November 23, 2017, 01:02:26 pm »
@rvk sorry i won't do that again. but i didn't delete it on this topic.
@mr madguy
hi so i managed to create the shape if client connects to the server but i don't know how to send coord for each clients.
let's say there are 5 clients conected to the server. if i send client 1's coord to other clients, how can they know that it's client 1's coord? how can they check if it's client 1?

here are my server and client program
Each client should have some unique ID. This ID is generated via GenerateID method on server and sent in ClientID field of TData. When client sends it's data, it leaves this field empty, as client is identified by connection on server.

I've simplified protocol again in order to simplify code. I always send TData with fixed length, that means excessive traffic in some cases. In real situation sending and receiving data should be performed in two steps: 1) Receive header, that contains some data, that allows code to detect length and structure of following data 2) Receive all other data.

Another thing, that isn't good - the way, send errors are being handled on server. Server simply tries to send all data again on next frame in this case. This may cause dead loop in real situation. Buffer overflow -> send error -> send all data again -> buffer overflow again.

Conclusion - it's just an example. It needs lots of improvements to become real server.
« Last Edit: November 23, 2017, 01:06:45 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

 

TinyPortal © 2005-2018