TCP tests, UDP functional

pull/2/head
kaqu 2 years ago
parent af77b5828e
commit a6cf6829b6
  1. 6
      .vscode/launch.json
  2. 1
      cl1.sh
  3. 1
      cl2.sh
  4. 84
      pandemic_pong.py
  5. 46
      pandemic_pong_client.py
  6. 79
      pandemic_pong_server.py
  7. 6
      pong_constants.py
  8. 7
      pong_game.py
  9. 2
      pong_globalvars.py
  10. 253
      pong_player.py
  11. 1
      srv.sh

@ -8,9 +8,9 @@
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
//"args": ["/dev/input/event6", "/dev/input/event7", "--sizeable"],
"args": ["--sizeable"],
"program": "${file}",
//"args": ["--sizeable", "--server"],
"args": ["--sizeable", "--client", "2", "127.0.0.1"],
"console": "integratedTerminal"
}
]

@ -0,0 +1 @@
./pandemic_pong.py --sizeable --client 1 127.0.0.1

@ -0,0 +1 @@
./pandemic_pong.py --sizeable --client 2 127.0.0.1

@ -9,8 +9,11 @@ History:
--------
21.11.20/KQ Initial version
25.11.20/KQ Class based version w/ scaling ;)
27.11.20/KQ Client/server variant started
"""
DEVLOCAL = True # TODO: Adjust for publication to False!!!
import sys, os, fcntl, time, random
from glob import glob
import os
@ -59,26 +62,35 @@ class pongWindow(QMainWindow):
self.win_height = 960
self.InitWindow()
def InitWindow(self):
def InitWindow(self):
global debug_x, debug_y
self.setWindowTitle(self.title)
self.setGeometry(self.top, self.left, self.win_width, self.win_height)
if bSizeable:
self.resize(int(self.win_width * 2 / 3), int(self.win_height * 2 / 2.8))
self.move(int(self.win_width / 6), int(self.win_height / 8))
if DEVLOCAL == True: # Small windows for convenience ...
self.resize(int(self.win_width * 2 / 3 / 3), int(self.win_height * 2 / 2.8 / 3))
#self.move(int(self.win_width / 2.5), int(self.win_height / 16))
self.move(debug_x, debug_y)
else:
self.resize(int(self.win_width * 2 / 3), int(self.win_height * 2 / 2.8))
self.move(int(self.win_width / 6), int(self.win_height / 8))
else:
self.showFullScreen() # Regular full screen play by default
self.pic = QPixmap("pictures/pong_background.png")
self.show()
# Enforce caching ...
playsound(pgc.player_contact)
playsound(pgc.player_miss)
playsound(pgc.wall_contact)
playsound(pgc.game_exit)
time.sleep(0.5)
playsound(pgc.game_splash)
# Don't annoy developers (except for local play - to test game mechanics !) ...
if (DEVLOCAL == False) or (bServer == False):
# Enforce caching ...
playsound(pgc.player_contact)
playsound(pgc.player_miss)
playsound(pgc.wall_contact)
playsound(pgc.game_exit)
time.sleep(0.5)
playsound(pgc.game_splash)
self.timer = QTimer() # Start processing 'loop'
self.timer.setInterval(25) # 25ms
@ -217,10 +229,35 @@ class pongWindow(QMainWindow):
if __name__ == '__main__':
bSizeable = False
bSizeable = False
bServer = False
player_index = 0 # Illegal, local
player_server = ""
debug_x = 0 # Debug window positions
debug_y = 0
sMsg = "usage: ./pandemic_pong.py --[sizeable|fullscreen] --[server|client <1|2> <ip_adress>]"
if len(sys.argv) > 1:
if sys.argv[1] == "--sizeable":
bSizeable = True
if sys.argv[1] == "--sizeable": # else=default=fullscreen assumed ...
bSizeable = True
if len(sys.argv) > 2:
if sys.argv[2] == "--server":
bServer = True
print("*** PANDEMIC PONG (server) ***")
debug_x = int(pgv.GAMEAREA_MAX_X / 2.5) # Top centered window
debug_y = int(pgv.GAMEAREA_MAX_Y / 40)
elif len(sys.argv) > 4:
if sys.argv[2] == "--client":
player_index = int(sys.argv[3])
player_server = sys.argv[4]
print("*** PANDEMIC PONG (player #{} talking to game server @{}) ***".format(player_index,player_server))
debug_x = int(pgv.GAMEAREA_MAX_X / 3 * 4 * (player_index-1)) # Left(0) or right top window
debug_y = int(pgv.GAMEAREA_MAX_Y / 40)
else:
print(sMsg)
else:
print(sMsg)
else: # Local version
print("*** PANDEMIC PONG (local) ***")
# Figure out 2 highest event queues as they are assumed to be the gamepads
maxEvent = 0
@ -235,8 +272,22 @@ if __name__ == '__main__':
random.seed()
game = PongGame()
ball = PongObject(pgv.GAMEAREA_MAX_X/2, pgv.GAMEAREA_MAX_Y/2, 20, 20, 8.0, 2.5)
player1 = PongPlayer(sEventQueue1, 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(sEventQueue2, pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
if player_index == 0: # Local or server?
if bServer == True: # Server version, no gamepads locally avail.
player1 = PongPlayer(None, True, 1, "", 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(None, True, 2, "", pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
else: # Local version (both gamepads assumed locally connected)
player1 = PongPlayer(sEventQueue1, False, 1, "", 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(sEventQueue2, False, 2, "", pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
elif player_index == 1: # Client/server version, player #1
player1 = PongPlayer(sEventQueue1, False, player_index, player_server, 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(None, False, 2, "", pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
elif player_index == 2: # Client/server version, player #2
player1 = PongPlayer(None, False, 1, "", 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
if DEVLOCAL == True: # 2 gamepads required FOR LOCAL DEVELOPMENT ONLY!
player2 = PongPlayer(sEventQueue2, False, player_index, player_server, pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
else: # 2nd player assumed on 1st gamepad (only one pad required ... !)
player2 = PongPlayer(sEventQueue1, False, player_index, player_server, pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
except IOError as e:
import errno
if e.errno == errno.EACCES:
@ -248,6 +299,7 @@ if __name__ == '__main__':
sys.exit(-1)
app = QApplication(sys.argv) # Eval command line args
window = pongWindow() # Create Qt5 GUI
rc = app.exec_() # & run logic

@ -0,0 +1,46 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pandemic_pong_client.py
Pandemic Pong Client
"""
import socket
HEADERSIZE = 10
print("Client starting, ", end="")
sockClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockClient.connect((socket.gethostname(), 5005))
#sockClient.setblocking(False) # Do not block ...
print("listening ...")
sockClient.send("GET / HTTP/1.1\r\n\r\n".encode()) # Initiate streaming on server
# Will return:
# HTTP/1.0 200 OK
# Server: BaseHTTP/0.6
# Python/3.8.5
# Date: Mon, 30 Nov 2020 09:59:52 GMT
# Content-type: application/octet-stream
# <CR/LF><CR/LF><CR/LF>
msg = sockClient.recv(160).decode() # Skip server header
sockClient.setblocking(False) # Do not block ...
#print("Server:<<<{}>>>".format(msg))
while True:
try:
msg = sockClient.recv(4).decode()
if len(msg) < 1:
print("Server side close detected.")
break
print("{}: {}h".format(len(msg),msg))
except BlockingIOError: # Always on empty receives ...
pass
except KeyboardInterrupt:
print("Exiting")
break
except:
pass #print("Receive error")

@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pandemic_pong_server.py
Pandemic Pong Server
"""
import time, threading, socket
import socketserver
from http.server import BaseHTTPRequestHandler, HTTPServer
iCounter = 0
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
"""HTTP-GET may be called from standard web browser to test ..."""
global iCounter
if self.path != '/': # Webserver: localhost:8000/ !
self.send_error(404, "Object not found")
return
self.send_response(200)
# self.send_header('Content-type', 'text/html; charset=utf-8') # For web server requests
# Will prompt for download(!) on web servers!
self.send_header('Content-type', 'application/octet-stream') # But we transfer binary data
self.end_headers()
# serve up an infinite stream
bError = False
while bError == False:
msg = "{0:04x}".format(iCounter) # {<varindex>:<formatstr>}
try:
print("Sending {}h".format(msg))
self.wfile.write(msg.encode())
time.sleep(0.1)
iCounter += 1
except BrokenPipeError:
print("doGET: Client #{} terminated.".format(iCounter))
bError = True
# Launch 3 listener threads.
class Thread(threading.Thread):
def __init__(self, i):
print("Starting thread #{} ...".format(i))
threading.Thread.__init__(self)
self.i = i
self.daemon = True
self.start()
def run(self):
httpd = HTTPServer(addr, Handler, False)
# Prevent the HTTP server from re-binding every handler.
# https://stackoverflow.com/questions/46210672/
httpd.socket = sock
httpd.server_bind = self.server_close = lambda self: None
print("Thread listening ...")
try:
httpd.serve_forever() # Never returns ...
except BrokenPipeError: # Not working?!
print("Client #{} terminated, respawning ...".format(self.i))
Thread(self.i) # Respawn right away, await another client
print("run(<thread>) exiting") # Never reached ...
# Create ONE socket.
addr = ('', 5005)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr)
sock.listen(5)
print("Server started & listening ...")
try:
[Thread(i) for i in range(3)]
time.sleep(9e9) # This shall be the main program service
except KeyboardInterrupt:
print("\rExiting server. ")

@ -45,3 +45,9 @@ game_splash = './sounds/splash.wav'
game_exit = './sounds/bye.wav'
game_win = './sounds/gamewin.wav'
match_win = './sounds/matchwin.wav'
"""IP network access"""
PANDEMIC_PONG_PORT = 5005
PACKETTYPE_ALLDATA = 0
PACKETTYPE_PLAYER = 1
UDP_TRANSFER_FORMAT = "iiiffffii"

@ -156,8 +156,11 @@ class PongGame:
return False
# 1. Retrieve user entries
bChanged = player1.eval_gamepad()
bChanged = player2.eval_gamepad()
if pgv.bIsServer == True:
bChanged = player1.eval_socket(player2)
else:
bChanged = player1.eval_gamepad()
bChanged = player2.eval_gamepad()
if self.state == pgc.STATE_PLAY: # Actually playing?
# 2. Adjust player positions

@ -12,3 +12,5 @@ GAMEAREA_MIN_X = 0 # Game area left border
GAMEAREA_MAX_X = 1920 # Assume FHD resolution by default
GAMEAREA_MIN_Y = 15 # Game area top border
GAMEAREA_MAX_Y = 940 # Game area bottom range
bIsServer = False # Global server indication

@ -9,10 +9,15 @@ Pandemic Pong Player Logic
import os, fcntl
import libevdev
import struct # Packing/unpacking
import socket # External communication
import pong_constants as pgc # Pong global constants
import pong_globalvars as pgv # Pong global variables
UDP_PACKET_SIZE = struct.calcsize(pgc.UDP_TRANSFER_FORMAT) # Size of receiver buffer
serverUDP = None
def print_event(e):
"""Sample from libevdev docs, useful for testing ..."""
@ -30,12 +35,32 @@ def print_event(e):
class PongPlayer:
"""The player consists of gamepad input & variables"""
def __init__(self, path, x, y, w, h):
# Gamepad init.
self.fd = open(path, "rb") # File descriptor to close (later ...)
fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NONBLOCK)
self.dev = libevdev.Device(self.fd) # Actual gamepad of this player
self.state = pgc.BTN_STATE_NONE # Button mask (as listed above)
def __init__(self, path, bThisIsTheServer, player_index, player_server, x, y, w, h):
# Gamepad init., either local, client or server
global UDP_PACKET_SIZE, serverUDP
if path:
self.fd = open(path, "rb") # File descriptor to close (later ...)
fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NONBLOCK)
self.dev = libevdev.Device(self.fd) # Actual gamepad of this player
else:
self.dev = None
self.player_index = player_index # Who am I?
self.player_server = player_server # IP of game server
if len(player_server) > 0: # IP provided
self.socketUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP sender socket
self.bUseServer = True
else:
self.bUseServer = False
if (pgv.bIsServer == False) and (bThisIsTheServer): # Is this THE server?
# But init. only once
UDP_PACKET_SIZE = struct.calcsize(pgc.UDP_TRANSFER_FORMAT) # Size of receiver buffer
serverUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP Receiver
serverUDP.bind(("127.0.0.1", pgc.PANDEMIC_PONG_PORT)) # Localhost
serverUDP.setblocking(False) # Do not block ...
pgv.bIsServer = True # But call me only once ...
self.state = pgc.BTN_STATE_NONE # Button mask (as listed above)
self.x = x # Current position
self.y = y
self.w = w # Dimensions
@ -59,90 +84,158 @@ class PongPlayer:
def exit(self):
"""To quit gracefully, we should close the event queues ..."""
if self.dev:
self.fd.close()
self.fd.close()
def eval_data(self, type_value, value, code_value):
bChanged = False
# Event type 1 (EV_KEY)
# Event code 288 (BTN_TRIGGER) <- 1=[Blue/X]
# Event code 289 (BTN_THUMB) <- 1=[Red/A]
# Event code 290 (BTN_THUMB2) <- 1=[Yellow/B]
# Event code 291 (BTN_TOP) <- 1=[Green/Y]
# Event code 292 (BTN_TOP2) <- 1=[Frontal left]
# Event code 293 (BTN_PINKIE) <- 1=[Frontal right]
# Event code 294 (BTN_BASE)
# Event code 295 (BTN_BASE2)
# Event code 296 (BTN_BASE3) <- 1=[Centre left]
# Event code 297 (BTN_BASE4) <- 1=[Centre right]
if type_value == 1: # Non-Cross events
if value == 1: # Simple buttons may act as toggles
if code_value == 288: # BTN_TRIGGER
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_TRIGGER # self.state ^ pgc.BTN_TRIGGER
self.color = pgc.COL_BLUE # Infect 'blue'
self.delay = pgc.COLORDELAY # Start infectious time immediately ...
elif code_value == 289: # BTN_THUMB
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_THUMB # self.state ^ pgc.BTN_THUMB
self.color = pgc.COL_RED
self.delay = pgc.COLORDELAY
elif code_value == 290: # BTN_THUMB2
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_THUMB2 # self.state ^ pgc.BTN_THUMB2
self.color = pgc.COL_YELLOW
self.delay = pgc.COLORDELAY
elif code_value == 291: # BTN_TOP
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_TOP # self.state ^ pgc.BTN_TOP
self.color = pgc.COL_GREEN
self.delay = pgc.COLORDELAY
def eval_gamepad(self):
elif code_value == 292: # BTN_TOP2
self.state = self.state ^ pgc.BTN_TOP2
elif code_value == 293: # BTN_PINKIE
self.state = self.state ^ pgc.BTN_PINKIE
elif code_value == 296: # BTN_BASE3
self.state = self.state ^ pgc.BTN_BASE3
elif code_value == 297: # BTN_BASE4
self.state = self.state ^ pgc.BTN_BASE4
bChanged = True
# Event type 3 (EV_ABS)
# Event code 0 (ABS_X) <- Cross: 127=Neutral, 0=[Left], 255=[Right]
# Event code 1 (ABS_Y) <- Cross: 127=Neutral, 0=[Top], 255=[Down]
elif type_value == 3: # EV_ABS
if code_value == 0: # ABS_X
if value == 0:
self.delta_x = self.delta_x - 1
elif value == 255:
self.delta_x = self.delta_x + 1
else: # 127/Neutral
self.delta_x = 0
else: # ABS_Y
if value == 0:
self.delta_y = self.delta_y - 1
elif value == 255:
self.delta_y = self.delta_y + 1
else: # 127/Neutral
self.delta_y = 0
bChanged = True
return bChanged
def eval_gamepad(self):
"""Decode gamepad events (if any ...)"""
global UDP_PACKET_SIZE, serverUDP
bChanged = False
try:
for e in self.dev.events():
#print_event(e)
# Event type 1 (EV_KEY)
# Event code 288 (BTN_TRIGGER) <- 1=[Blue/X]
# Event code 289 (BTN_THUMB) <- 1=[Red/A]
# Event code 290 (BTN_THUMB2) <- 1=[Yellow/B]
# Event code 291 (BTN_TOP) <- 1=[Green/Y]
# Event code 292 (BTN_TOP2) <- 1=[Frontal left]
# Event code 293 (BTN_PINKIE) <- 1=[Frontal right]
# Event code 294 (BTN_BASE)
# Event code 295 (BTN_BASE2)
# Event code 296 (BTN_BASE3) <- 1=[Centre left]
# Event code 297 (BTN_BASE4) <- 1=[Centre right]
if e.type.value == 1: # Non-Cross events
if e.value == 1: # Simple buttons may act as toggles
if e.code.value == 288: # BTN_TRIGGER
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_TRIGGER # self.state ^ pgc.BTN_TRIGGER
self.color = pgc.COL_BLUE # Infect 'blue'
self.delay = pgc.COLORDELAY # Start infectious time immediately ...
elif e.code.value == 289: # BTN_THUMB
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_THUMB # self.state ^ pgc.BTN_THUMB
self.color = pgc.COL_RED
self.delay = pgc.COLORDELAY
elif e.code.value == 290: # BTN_THUMB2
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_THUMB2 # self.state ^ pgc.BTN_THUMB2
self.color = pgc.COL_YELLOW
self.delay = pgc.COLORDELAY
elif e.code.value == 291: # BTN_TOP
if self.delay <= 0: # As long as we're not infectious ...
self.state = pgc.BTN_TOP # self.state ^ pgc.BTN_TOP
self.color = pgc.COL_GREEN
self.delay = pgc.COLORDELAY
elif e.code.value == 292: # BTN_TOP2
self.state = self.state ^ pgc.BTN_TOP2
elif e.code.value == 293: # BTN_PINKIE
self.state = self.state ^ pgc.BTN_PINKIE
elif e.code.value == 296: # BTN_BASE3
self.state = self.state ^ pgc.BTN_BASE3
elif e.code.value == 297: # BTN_BASE4
self.state = self.state ^ pgc.BTN_BASE4
bChanged = True
# Event type 3 (EV_ABS)
# Event code 0 (ABS_X) <- Cross: 127=Neutral, 0=[Left], 255=[Right]
# Event code 1 (ABS_Y) <- Cross: 127=Neutral, 0=[Top], 255=[Down]
elif e.type.value == 3: # EV_ABS
if e.code.value == 0: # ABS_X
if e.value == 0:
self.delta_x = self.delta_x - 1
elif e.value == 255:
self.delta_x = self.delta_x + 1
else: # 127/Neutral
self.delta_x = 0
else: # ABS_Y
if e.value == 0:
self.delta_y = self.delta_y - 1
elif e.value == 255:
self.delta_y = self.delta_y + 1
else: # 127/Neutral
self.delta_y = 0
bChanged = True
#if bChanged:
# print("State=",self.state, "X=", self.delta_x, "Y=", self.delta_y)
try:
if self.dev:
for e in self.dev.events():
bChanged = bChanged or self.eval_data(e.type.value, e.value, e.code.value)
if bChanged and self.bUseServer:
bData = struct.pack(pgc.UDP_TRANSFER_FORMAT,
pgc.PACKETTYPE_PLAYER,
self.player_index,
self.state,
self.x,
self.y,
self.delta_x,
self.delta_y,
self.color,
self.delay
)
self.socketUDP.sendto(bData, (self.player_server, pgc.PANDEMIC_PONG_PORT))
else: # This player is remote!
pass
except libevdev.EventsDroppedException:
print("Dropped!")
for e in self.dev.sync():
print_event(e)
if self.dev:
for e in self.dev.sync():
print_event(e)
return bChanged
def eval_socket(self, other_player): # Shall be called player1.eval_socket(player2) ...
"""Decode socket events (if any ...)"""
global UDP_PACKET_SIZE, serverUDP
try: # Receive self.player_index data
data, addr = serverUDP.recvfrom(UDP_PACKET_SIZE) # blocks! buffer size is 1024 bytes
if data[0] == pgc.PACKETTYPE_ALLDATA:
pass # Future extension reserve ...
else: # Player data
_, player_index, state, x, y, delta_x, delta_y, color, delay = struct.unpack(pgc.UDP_TRANSFER_FORMAT, data)
print("({}) #{} S:{} X:{} Y:{} DX:{} DY:{} C:{} D:{}".format(
addr[0],
player_index,
state,
x,
y,
delta_x,
delta_y,
color,
delay)
)
# TODO: Needs to be sent to other player ...
if player_index == self.player_index:
self.state = state
self.x = x
self.y = y
self.delta_x = delta_x
self.delta_y = delta_y
self.color = color
self.delay = delay
else:
other_player.state = state
other_player.x = x
other_player.y = y
other_player.delta_x = delta_x
other_player.delta_y = delta_y
other_player.color = color
other_player.delay = delay
return True # Change indication ...
except BlockingIOError:
pass
return False
def eval_position(self, min_x, max_x):
"""Player movement w/ area restrictions"""
bChanged = False

@ -0,0 +1 @@
./pandemic_pong.py --sizeable --server
Loading…
Cancel
Save