netPong - multiplayer game

Share your advanced PureBasic knowledge/code with the community.
Nituvious
Addict
Addict
Posts: 1029
Joined: Sat Jul 11, 2009 4:57 am
Location: United States

netPong - multiplayer game

Post by Nituvious »

Well, I've been trying to learn networking and I figured it would be pretty fun to try and create an online version of the pong game as a chance to learn from.

It's really bad though, but maybe if I post the code I can learn from others and maybe get some tips on improvement. :wink:
I also hope this could help someone out there that might need it to serve as an example for them. But I don't think that's likely since the code is ugly and really simple.

Code: Select all

; -----------------------------------------------------
; title: NetPong
;
; description: Multiplayer Pong game
; played over a network
; 
; author: nituvious
; last modified: August 11, 2010
; -----------------------------------------------------

If InitSprite() = 0 : MessageRequester("Error","Sprite engine could not initialize.") : End : EndIf
If InitKeyboard() = 0 : MessageRequester("Error","No input hardware found?") : End : EndIf
If InitMouse() = 0 : MessageRequester("Error","No input hardware found?") : End : EndIf
If InitNetwork() = 0 : MessageRequester("Error","No network hardware found?") : End : EndIf

Enumeration
	#mainWindow
	#serverID
EndEnumeration

#curVersion = "v0.02"

Global *packetSend = AllocateMemory(26)
Global *packetRecv = AllocateMemory(26)

Global debugFlag.l,gameMenuNumber,keyPressed$,connectAttempt,scroller
Global gameType.l=-1,connectionStatus,joinServer,clientEvent,serverIP$,serverPort.l,serverPort$,serverID
Global serverNetworkEvent,eventClientID
Global clientNetworkEvent,clientEvent
Global player2DirectionChange = 0
Global gotoJoinGame,chatmsg
Global xline=0, chatmsgline$,chatline1$,chatline2$,chatline3$,chatline4$,chatline5$


Global colorRed=10,colorGreen=255,colorBlue=25

Global timerBackSpace = ElapsedMilliseconds() + 150
Global timerPacketSendS = ElapsedMilliseconds() + 50
Global timerPacketSend = ElapsedMilliseconds() + 10
Global timerScroller = ElapsedMilliseconds() + 10

Global PlayerStruct,ID.l,X.f,Y.f,Score.l,Lives.l
Structure PlayerStruct
	ID.l
	X.l
	Y.l
	W.l
	H.l
	Score.l
EndStructure

Global BallStruct,BallX.f,BallY.f
Structure BallStruct
	ballX.l
	ballY.l
	ballRadius.l
	ballSpeed.l
	ballDirection1.l
	ballDirection2.l
EndStructure

Global Dim PlayerArray.PlayerStruct(1)
PlayerArray(0)\ID = 0
PlayerArray(0)\X = 15
PlayerArray(0)\Y= 245
PlayerArray(0)\W = 15
PlayerArray(0)\H = 100

PlayerArray(1)\ID = 0
PlayerArray(1)\X = 770
PlayerArray(1)\Y= 245
PlayerArray(1)\W = 15
PlayerArray(1)\H = 100

Global Dim BallArray.BallStruct(0)
BallArray(0)\ballX = 395
BallArray(0)\ballY = 245
BallArray(0)\ballRadius = 5
BallArray(0)\ballSpeed = 5
BallArray(0)\ballDirection1 = 1
BallArray(0)\ballDirection2 = 1
OpenWindow(#mainWindow,0,0,800,600,"")

;If OpenWindowedScreen(WindowID(#mainWindow),0,0,800,600,1,0,0) = 0
If OpenScreen(800,600,32,"netPong " + #curVersion) = 0
	MessageRequester("Error","Couldn't open a screen! Try changing resolution.")
EndIf

Procedure GameDisplay()
	If gameMenuNumber = 4
		StartDrawing(ScreenOutput())
		DrawingMode(#PB_2DDrawing_Transparent)

		; -- display
		DrawText(365,115,Str(PlayerArray(0)\Score),RGB(colorRed,colorGreen,colorBlue))
		DrawText(415,115,Str(PlayerArray(1)\Score),RGB(colorRed,colorGreen,colorBlue))
		Box(5,5,790,110,RGB(colorRed,colorGreen,colorBlue))
		Box(6,6,788,89,RGB(0,0,0))
		Box(6,96,788,18,RGB(0,0,0))

		DrawText(10,11,chatline1$)
		DrawText(10,26,chatline2$)
		DrawText(10,41,chatline3$)
		DrawText(10,56,chatline4$)
		DrawText(10,71,chatline5$)

		DrawText(10,97,"> "+keyPressed$,RGB(200,200,255))
		Box(5,130,791,465,RGB(colorRed,colorGreen,colorBlue))
		Box(6,131,789,463,RGB(0,0,0))

		; -- Players
		Box(PlayerArray(0)\X,PlayerArray(0)\Y,PlayerArray(0)\W,PlayerArray(0)\H,RGB(colorRed,colorGreen,colorBlue))
		Box(PlayerArray(1)\X,PlayerArray(1)\Y,PlayerArray(1)\W,PlayerArray(1)\H,RGB(colorRed,colorGreen,colorBlue))
		; -- Ball
		Circle(BallArray(0)\ballX,BallArray(0)\ballY,BallArray(0)\ballRadius,RGB(colorRed,colorGreen,colorBlue))

		; -- calculate the direction of the ball...
		If gameType = 0
			BallArray(0)\ballX + BallArray(0)\ballDirection1
			BallArray(0)\ballY + BallArray(0)\ballDirection2
			If BallArray(0)\ballX >= 789
				BallArray(0)\ballDirection1 = -BallArray(0)\ballSpeed
				PlayerArray(0)\Score + 1
			EndIf	
			If BallArray(0)\ballX <= 11
				BallArray(0)\ballDirection1 = BallArray(0)\ballSpeed
				PlayerArray(1)\Score + 1
			EndIf
			If BallArray(0)\ballY >= 588
				BallArray(0)\ballDirection2 = -BallArray(0)\ballSpeed
			EndIf
			If BallArray(0)\ballY <= 136
				BallArray(0)\ballDirection2 = BallArray(0)\ballSpeed
			EndIf
			; -- Collision detection code for detecting when a ball hits a paddle
			; -- I need to find a better way of doing this
			If ((BallArray(0)\ballX+BallArray(0)\ballRadius >= PlayerArray(0)\X) And (BallArray(0)\ballY+BallArray(0)\ballRadius >= PlayerArray(0)\Y)) And ((BallArray(0)\ballX-BallArray(0)\ballRadius <= PlayerArray(0)\W + PlayerArray(0)\X) And (BallArray(0)\ballY-BallArray(0)\ballRadius <= PlayerArray(0)\H+PlayerArray(0)\Y))
				BallArray(0)\ballDirection1 = BallArray(0)\ballSpeed
			EndIf
			If ((BallArray(0)\ballX+BallArray(0)\ballRadius >= PlayerArray(1)\X) And (BallArray(0)\ballY+BallArray(0)\ballRadius >= PlayerArray(1)\Y)) And ((BallArray(0)\ballX-BallArray(0)\ballRadius <= PlayerArray(1)\W + PlayerArray(1)\X) And (BallArray(0)\ballY-BallArray(0)\ballRadius <= PlayerArray(1)\H+PlayerArray(1)\Y))
				BallArray(0)\ballDirection1 = -BallArray(0)\ballSpeed
			EndIf
		EndIf
		StopDrawing()
	EndIf
EndProcedure

Procedure DrawMenu()
	; -- Main menu
	If gameMenuNumber = 0
		ExamineMouse()
		StartDrawing(ScreenOutput())
		RoundBox(50,75,100,25,5,10,RGB(120,175,60))
		RoundBox(55,105,100,25,5,10,RGB(120,175,60))
		DrawingMode(#PB_2DDrawing_Transparent)
		FrontColor(RGB(255,255,255))
		DrawText(75,80,"Join")
		DrawText(80,110,"Host")
		; -- buttons
		If MouseX() >= 50 And MouseX() <= 150 And MouseY() >= 75 And MouseY() <= 100
			FrontColor(RGB(255,0,0))
			DrawText(75,80,"Join")
			If MouseButton(#PB_MouseButton_Left)
				gameMenuNumber = 1
			EndIf
		EndIf
		If MouseX() >= 55 And MouseX() <= 155 And MouseY() >= 105 And MouseY() <= 135
			FrontColor(RGB(255,0,0))
			DrawText(80,110,"Host")
			If MouseButton(#PB_MouseButton_Left)
				gameMenuNumber = 2
			EndIf
		EndIf
		Circle(MouseX(),MouseY(),2,RGB(0,255,100))
		If timerScroller < ElapsedMilliseconds()
			scroller+2
			timerScroller = ElapsedMilliseconds() + 10
		EndIf
		If scroller>800 : scroller=-600 : EndIf
		DrawText(scroller,565,"netPong by Nituvious :: Super crappy code, I know! ;)",RGB(0,100,255))
		DrawText(scroller+110,580,"PureBasic v4.50 <3",RGB(0,100,255))
		StopDrawing()
	EndIf

	; -- Join a server menu
	If gameMenuNumber = 1
		ExamineMouse()
		ExamineKeyboard()
		StartDrawing(ScreenOutput())
		DrawText(0,0,"Server: "+keyPressed$,RGB(0,100,255))
		DrawText(0,20,"IP: "+ServerIP$+" Port: "+Str(serverPort),RGB(0,100,255))
		keyPressed$ + KeyboardInkey()
		If KeyboardPushed(#PB_Key_Back)
			If timerBackSpace < ElapsedMilliseconds()
				keyPressed$ = Left(keyPressed$,Len(keyPressed$)-1)
				timerBackSpace = ElapsedMilliseconds() + 150
			EndIf
		EndIf
		If KeyboardReleased(#PB_Key_Return)
			locPort = FindString(keyPressed$,":",0)
			If locPort <> 0
				serverPort$ = Mid(keyPressed$,locPort+1)
				serverIP$ = Mid(keyPressed$,0,Len(keyPressed$)-Len(serverPort$)-1)
				serverPort = Val(serverPort$)
				joinServer = OpenNetworkConnection(serverIP$,serverPort,#PB_Network_UDP)
				PokeS(*packetSend,"challenge")
				SendNetworkData(joinServer,*packetSend,26)
				waitAcknowledge:
				clientEvent = NetworkClientEvent(joinServer)
				If clientEvent
					Select clientEvent
						Case #PB_NetworkEvent_Data
							recvLen = ReceiveNetworkData(joinServer,*packetRecv,26)
							If recvLen <> 0
								If PeekS(*packetRecv) = "acknowledge"
									Debug "holy crap no way!"
									gameType = 1
									gameMenuNumber = 4
								EndIf
							EndIf
					EndSelect
				Else
					connectAttempt+1
					Delay(10)
					If connectAttempt > 100
						connectAttempt = 0
						gameMenuNumber = 0
						Debug "connect exceeds 100"
					Else
						Goto waitAcknowledge
					EndIf
				EndIf
			EndIf
			keyPressed$ = ""
		EndIf
		Circle(MouseX(),MouseY(),2,RGB(0,255,100))
		StopDrawing()
	EndIf

	; -- Host a server menu
	If gameMenuNumber = 2
		ExamineMouse()
		ExamineKeyboard()
		StartDrawing(ScreenOutput())
		DrawText(0,0,"Port: "+keyPressed$,RGB(0,100,255))
		DrawText(0,20,"Port: "+Str(serverPort),RGB(0,100,255))
		keyPressed$ + KeyboardInkey()
		If KeyboardPushed(#PB_Key_Back)
			If timerBackSpace < ElapsedMilliseconds()
				keyPressed$ = Left(keyPressed$,Len(keyPressed$)-1)
				timerBackSpace = ElapsedMilliseconds() + 150
			EndIf
		EndIf
		If KeyboardReleased(#PB_Key_Return)
			serverPort = Val(keyPressed$)
			If serverPort <> 0
				If CreateNetworkServer(#serverID,serverPort,#PB_Network_UDP)
					gameType = 0 ; -- types = 0 and 1 ---- 0 = server, 1 = client
				EndIf
			EndIf
			keyPressed$ = ""
		EndIf
		Circle(MouseX(),MouseY(),2,RGB(0,255,100))
		StopDrawing()
	EndIf
EndProcedure


Procedure DrawDebug()
	; -- draw debugging information
	If debugFlag = #True
		StartDrawing(ScreenOutput())
		DrawingMode(#PB_2DDrawing_Transparent)
		DrawText(0,0,"Debug",RGB(10,255,25))
		DrawText(0,15,"Ball: X/"+Str(BallArray(0)\ballX)+" Y/"+Str(BallArray(0)\ballY),RGB(10,255,25))
		DrawText(150,15,"ballSpeed: "+Str(BallArray(0)\ballSpeed),RGB(10,255,25))
		DrawText(0,30,"Player 1: X/"+Str(PlayerArray(0)\X)+" Y/"+Str(PlayerArray(0)\Y),RGB(10,255,25))
		DrawText(0,45,"Player 2: X/"+Str(PlayerArray(1)\X)+" Y/"+Str(PlayerArray(1)\Y),RGB(10,255,25))
		StopDrawing()
	EndIf
EndProcedure

Procedure Events(*d)
	Delay(1)
	If ExamineKeyboard()
		If KeyboardPushed(#PB_Key_Escape)
			CloseScreen()
			End
		EndIf
		If gameMenuNumber = 4
			; -- Text input thing
			keyPressed$ + KeyboardInkey()
			If KeyboardPushed(#PB_Key_Back)
				If timerBackSpace < ElapsedMilliseconds()
					keyPressed$ = Left(keyPressed$,Len(keyPressed$)-1)
					timerBackSpace = ElapsedMilliseconds() + 150
				EndIf
			EndIf
			If KeyboardReleased(#PB_Key_Return)
				If FindString(UCase(keyPressed$),UCase("/recolor "),0)
					colorRed = Val(StringField(keyPressed$, 2, " "))
	  				colorGreen = Val(StringField(keyPressed$, 3, " "))
	  				colorBlue = Val(StringField(keyPressed$, 4, " "))
					keyPressed$ = ""
				EndIf
				; -- if we're sending a message to the other player, we should check its length
				If (Len(keyPressed$) < 25) And (Len(keyPressed$) <> 0)
					chatmsg = 1
				EndIf
			EndIf
			; -- handles input during a game
			; -- The server will add/decrease players position
			If debugFlag = #True
				;{
				If KeyboardPushed(#PB_Key_Up)
					If PlayerArray(0)\Y >= 138
						PlayerArray(0)\Y - 2
					EndIf
				ElseIf KeyboardPushed(#PB_Key_Down)
					If PlayerArray(0)\Y <= 488
						PlayerArray(0)\Y + 2
					EndIf
				EndIf
				If KeyboardPushed(#PB_Key_W)
					PlayerArray(1)\Y - 2
				ElseIf KeyboardPushed(#PB_Key_S)
					PlayerArray(1)\Y + 2
				EndIf
				If KeyboardReleased(#PB_Key_Add)
					BallArray(0)\ballSpeed + 1
				ElseIf KeyboardReleased(#PB_Key_Subtract)
					BallArray(0)\ballSpeed - 1
				EndIf
				If KeyboardReleased(#PB_Key_8)
					BallArray(0)\BallY - 1
				ElseIf KeyboardReleased(#PB_Key_2)
					BallArray(0)\BallY + 1
				EndIf ;}
			Else
				If KeyboardPushed(#PB_Key_Up)
					If PlayerArray(gameType)\Y >= 138
						; sendnetworkdata decrease
						If gameType=0
							PlayerArray(0)\Y - 2
						EndIf
						If gameType=1
							PokeS(*packetSend,"p2up")
							player2DirectionChange = 1
						EndIf
					EndIf
				EndIf
				If KeyboardPushed(#PB_Key_Down)
					If PlayerArray(gameType)\Y <= 488
						; sendnetworkdata increase
						If gameType=0
							PlayerArray(0)\Y + 2
						EndIf
						If gameType=1
							PokeS(*packetSend,"p2down")
							player2DirectionChange = 1
						EndIf
					EndIf
				EndIf
			EndIf
		EndIf
	EndIf
EndProcedure

Procedure NetworkReceive(*a)
	; -- this shall receive the following:
	; ball direction, speed, position and radius
	; player y, score
	Delay(1)
	If gameType = 0 ; server
		serverNetworkEvent = NetworkServerEvent()
		If serverNetworkEvent
			eventClientID = EventClient()
			Select serverNetworkEvent
				Case #PB_NetworkEvent_Data
					recvLenS = ReceiveNetworkData(eventClientID,*packetRecv,26)
					If recvLenS <> 0
						Debug PeekS(*packetRecv)
						If PeekS(*packetRecv) = "challenge"
							PlayerArray(1)\ID = eventClientID
							PokeS(*packetSend,"acknowledge")
							SendNetworkData(PlayerArray(1)\ID,*packetSend,26)
							Delay(100)
							PokeS(*packetRecv,"")
							gameMenuNumber = 4
						EndIf
						If PeekS(*packetRecv) = "p2up"
							PlayerArray(1)\Y - 2
						EndIf
						If PeekS(*packetRecv) = "p2down"
							PlayerArray(1)\Y + 2
						EndIf
						If PeekS(*packetRecv) = "msg"
							xline+1
							If xline = 1 : chatline1$ = "Player2: "+PeekS(*packetRecv + 4)
							ElseIf xline=2 : chatline2$ = "Player2: "+PeekS(*packetRecv + 4)
							ElseIf xline=3 : chatline3$ = "Player2: "+PeekS(*packetRecv + 4)
							ElseIf xline=4 : chatline4$ = "Player2: "+PeekS(*packetRecv + 4)
							ElseIf xline=5 : chatline5$ = "Player2: "+PeekS(*packetRecv + 4) : xline=0 : EndIf
							PokeS(*packetRecv,"",1)
						EndIf
					EndIf
			EndSelect
		EndIf
	EndIf
	
	If gameType = 1 ; client
		clientNetworkEvent = NetworkClientEvent(joinServer)
		If clientNetworkEvent
			Select clientNetworkEvent
				Case #PB_NetworkEvent_Data
					recvLen = ReceiveNetworkData(joinServer,*packetRecv,26)
					If recvLen <> 0
						If PeekS(*packetRecv) = "sv"
							PlayerArray(0)\Y = PeekL(*packetRecv + 4)
							PlayerArray(1)\Y = PeekL(*packetRecv + 8)
							BallArray(0)\ballX = PeekL(*packetRecv + 12)
							BallArray(0)\ballY = PeekL(*packetRecv + 16)
							PlayerArray(0)\Score = PeekL(*packetRecv + 20)
							PlayerArray(1)\Score = PeekL(*packetRecv + 24)
						EndIf
						If PeekS(*packetRecv) = "msg"
							xline+1
							If xline =1 : chatline1$ = "Player1: "+PeekS(*packetRecv + 4)
							ElseIf xline=2 : chatline2$ = "Player1: "+PeekS(*packetRecv + 4)
							ElseIf xline=3 : chatline3$ = "Player1: "+PeekS(*packetRecv + 4)
							ElseIf xline=4 : chatline4$ = "Player1: "+PeekS(*packetRecv + 4)
							ElseIf xline=5 : chatline5$ = "Player1: "+PeekS(*packetRecv + 4) : xline=0 : EndIf
							PokeS(*packetRecv,"",1)
						EndIf
					EndIf
			EndSelect
		EndIf
	EndIf
	
EndProcedure

Procedure NetworkSend(*b)
	; -- this shall send the following:
	; ball direction, speed, position and radius
	; player y, score 
	Delay(1)
	If gameType = 0 ; server
		If gameMenuNumber = 4
			If timerPacketSendS  < ElapsedMilliseconds()
				If chatmsg = 0
					PokeS(*packetSend,"sv")
					PokeL(*packetSend+4,PlayerArray(0)\Y)
					PokeL(*packetSend+8,PlayerArray(1)\Y)
					PokeL(*packetSend+12,BallArray(0)\ballX)
					PokeL(*packetSend+16,BallArray(0)\ballY)
					PokeL(*packetSend+20,PlayerArray(0)\Score)
					PokeL(*packetSend+24,PlayerArray(1)\Score)
					SendNetworkData(PlayerArray(1)\ID,*packetSend,26)
					timerPacketSendS = ElapsedMilliseconds() + 50
				ElseIf chatmsg = 1
					PokeS(*packetSend,"msg")
					PokeS(*packetSend+4,keyPressed$)
					SendNetworkData(PlayerArray(1)\ID,*packetSend,26)
					PokeS(*packetSend,"",1)
					keyPressed$ = ""
					chatmsg = 0
				EndIf
			EndIf
		EndIf
	EndIf
	If gameType=1
		If gameMenuNumber = 4
			If chatmsg = 0
				If player2DirectionChange = 1
					If timerPacketSend  < ElapsedMilliseconds()
						SendNetworkData(joinServer,*packetSend,26)
						PokeS(*packetSend,"")
						timerPacketSend = ElapsedMilliseconds() + 10
						player2DirectionChange = 0
					EndIf
				EndIf
			ElseIf chatmsg = 1
				PokeS(*packetSend,"msg")
				PokeS(*packetSend+4,keyPressed$)
				SendNetworkData(joinServer,*packetSend,26)
				PokeS(*packetSend,"",1)
				keyPressed$ = ""
				chatmsg = 0
			EndIf
		EndIf
	EndIf
EndProcedure

Repeat : Delay(1) : WaitWindowEvent(10)
	debugFlag = #False
	FlipBuffers() :	ClearScreen(RGB(0,0,0))
	CreateThread(@NetworkReceive(),0)
	CreateThread(@NetworkSend(),0)
	DrawMenu()
	CreateThread(@Events(),0)
	GameDisplay()
	;DrawDebug()
ForEver
▓▓▓▓▓▒▒▒▒▒░░░░░
cbrooks
User
User
Posts: 57
Joined: Thu Sep 16, 2010 3:26 am

Re: netPong - multiplayer game

Post by cbrooks »

Thank you for the code - I will take a deep look at it.

I was hoping to be able to make the code I supplied work - since it's simple for me to follow.

Any suggestions on that level would be much appreciated.

(will still look at the other code too tho)
server code:

Code: Select all

;
; ------------------------------------------------------------
;
;   PureBasic - Network (Server) example file
;
;    (c) 2003 - Fantaisie Software
;
; ------------------------------------------------------------
;

If InitNetwork() = 0
  MessageRequester("Error", "Can't initialize the network !", 0)
  End
EndIf

Port = 6832


*Buffer = AllocateMemory(1000)

If CreateNetworkServer(0, Port)

  MessageRequester("PureBasic - Server", "Server created (Port "+Str(Port)+").", 0)
  
  Repeat
      
    SEvent = NetworkServerEvent()
  
    If SEvent
    
      ClientID = EventClient()
  
      Select SEvent
      
        Case #PB_NetworkEvent_Connect
          MessageRequester("PureBasic - Server", "A new client has connected !", 0)
  
        Case #PB_NetworkEvent_Data
          MessageRequester("PureBasic - Server", "Client "+Str(ClientID)+" has send a packet !", 0)
          ReceiveNetworkData(ClientID, *Buffer, 1000)
          MessageRequester("Info", "String: "+PeekS(*Buffer), 0)
  
        Case #PB_NetworkEvent_File
          MessageRequester("PureBasic - Server", "Client "+Str(ClientID)+" has send a file via the network !", 0)
          ReceiveNetworkFile(ClientID, "C:\TEST_Network.ftp3")
  
        Case #PB_NetworkEvent_Disconnect
          MessageRequester("PureBasic - Server", "Client "+Str(ClientID)+" has closed the connection...", 0)
          Quit = 1
    
      EndSelect
    EndIf
    
  Until Quit = 1 
  
  MessageRequester("PureBasic - Server", "Click to quit the server.", 0)
  
  CloseNetworkServer(0)
Else
  MessageRequester("Error", "Can't create the server (port in use ?).", 0)
EndIf

  
End   


client code:

Code: Select all

;
; ------------------------------------------------------------
;
;   PureBasic - Network (Client) example file
;
;    (c) 2003 - Fantaisie Software
;
; ------------------------------------------------------------
;
OpenConsole()
If InitNetwork() = 0
  MessageRequester("Error", "Can't initialize the network !", 0)
  End
EndIf

Port = 6832

ConnectionID = OpenNetworkConnection("127.0.0.1", Port)
If ConnectionID
  ; MessageRequester("PureBasic - Client", "Client connected to server...", 0)
 For t=1 To 1000
 a$=Input()
  
  SendNetworkString(ConnectionID, a$)
  
Next t

    
  ; MessageRequester("PureBasic - Client", "A string has been sent to the server, please check it before quit...", 0)
  
  CloseNetworkConnection(ConnectionID)
Else
  MessageRequester("PureBasic - Client", "Can't find the server (Is it launched ?).", 0)
EndIf
  
End   
Post Reply