Bots introduced as player substitutes!

master
kaqu 2 years ago
parent 65a66fa4ff
commit 3032c89af5
  1. 4
      .vscode/launch.json
  2. 15
      README.md
  3. 98
      game_objects/pong_bot.py
  4. 25
      game_objects/pong_game.py
  5. 27
      game_objects/pong_player.py
  6. 13
      game_objects/pong_viewer.py
  7. 40
      pandemic_pong.py
  8. 8
      pp_player1_bot.sh
  9. 8
      pp_player2_bot.sh

@ -9,8 +9,8 @@
"type": "python",
"request": "launch",
"program": "${file}",
"args": ["--sizeable", "--server"],
//"args": ["--sizeable", "--player", "1", "127.0.0.1"],
//"args": ["--sizeable", "--server"],
"args": ["--sizeable", "--player", "1", "127.0.0.1", "--bot"],
//"args": ["--sizeable", "--viewer", "127.0.0.1"],
"console": "integratedTerminal"
}

@ -109,11 +109,20 @@ To connect player #2, use
./pp_player2.sh <remote_host_ip_or_name>
If you want to access a remote game server, edit the IP address within
the cl1.sh or cl2.sh file respectively.
the pp_player1.sh and pp_player2.sh file respectively.
Also, you may want to play a client in fullscreen.
To achieve this, substitute --sizeable by --fullscreen in both files.
It is possible to have bots as players! Use
./pp_player1_bot.sh <remote_host_ip_or_name>
for (left) player #1 or
./pp_player2_bot.sh <remote_host_ip_or_name>
for right player #2 respectively.
Finally, you may also connect one pure view client as
@ -162,8 +171,8 @@ After some 2-5s this effect will disappear. Currently, there is no clean initial
## 4. Outlook ##
* Streaming synchronization needs improvement.
* Streaming synchronization still needs improvement.
* Maybe HTTP streaming shall be moved to raw TCP streaming ...
* Game mechanics: Maybe introduce an incubation time? Or let infection die over time ...
* Game mechanics: Maybe introduce an incubation time?

@ -0,0 +1,98 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pong_bot.py
Pandemic Pong Bot Logic
"""
from random import randrange
import game_objects.pong_constants as pgc # Pong global constants
import game_objects.pong_globalvars as pgv # Pong global variables
from game_objects.pong_player import PongPlayer
from game_objects.pong_object import PongObject
delay = 50 # Usually, server is already waiting in STATE_START ...
resenddelay = 0 # Don't send too many datagrams ...
back_to_neutral_y = False
lastcolor = 0 # No color assumed
def bot_game_logic(ball, player):
"""Actual PLAY state player bot logic"""
global resenddelay, back_to_neutral_y, lastcolor
if resenddelay > 0:
resenddelay -= 1
return 0, 0, 0
if back_to_neutral_y: # Reset/unpress y-axis movement
print("Reset y-axis to neutral")
back_to_neutral_y = False
return 3, 127, 1 # Y-Axis Neutral
resenddelay = 10
# Controlling vertical miss first:
if (ball.y + ball.h/2) > (player.y + 2*player.h/3): # Ball is below player?
print("ball below, going down")
back_to_neutral_y = True
return 3, 255, 1
elif (ball.y + ball.h/2) < (player.y + player.h/3): # Ball is above player?
print("ball above, going up")
back_to_neutral_y = True
return 3, 0, 1
# If Y axis is good, check on x axis
if abs(ball.x - player.x) < 100: # Ball within range?
if lastcolor == pgc.COL_WHITE: # Code selection permissible?
if ball.color == pgc.COL_WHITE: # Ball not already infected?
color = randrange(288,291) # Color button codes
print("---------------- Infecting with {} ".format(color))
lastcolor = color - 287
if color == 288:
lastcolor = pgc.COL_BLUE
elif color == 289:
lastcolor = pgc.COL_RED
elif color == 290:
lastcolor = pgc.COL_YELLOW
else:
lastcolor = pgc.COL_GREEN
else: # Ball infected! Try to match color
if ball.color == pgc.COL_BLUE:
color = 288
elif ball.color == pgc.COL_RED:
color = 289
elif ball.color == pgc.COL_YELLOW:
color = 290
else: # Green
color = 291
print("---------------- Syncing color {}".format(color))
lastcolor = ball.color
resenddelay = 4
return 1, 1, color
elif abs(ball.x - player.x) > (pgc.GAMEAREA_MAX_X / 2): # Ball on other side?
lastcolor = False # Permit new color selection
return 0, 0, 0 # Nothing changed!
def run_bot(game_state, ball, player):
"""Run the bot by sneaking in 'button presses'"""
global delay
# TODO: Bot logic ...
if game_state == pgc.STATE_WELCOME: # Server starting, wait
delay = 50
elif game_state == pgc.STATE_START: # Start new game (after a short delay ...)
if delay > 0:
delay -= 1
else:
return 1, 1, 297 # Code [Start]
elif game_state == pgc.STATE_PLAY: # Game is up
return bot_game_logic(ball, player) # Then: Play it!
return 0, 0, 0 # Bot just wait's ...
if __name__ == "__main__":
print("pong_bot has no function, call 'pandemic_pong.py'")

@ -16,6 +16,7 @@ from game_objects.pong_player import PongPlayer
from game_objects.pong_object import PongObject
import game_objects.pong_viewserver as vs # Pong TCP view server
from game_objects.pong_sound import AsyncSound
from game_objects.pong_bot import run_bot
class PongGame:
"""Actual game mechanics (mostly ;)"""
@ -175,13 +176,22 @@ class PongGame:
if (pgv.bIsServer == True) or (pgv.bIsLocal == True): # Game engine runs locally or on server only!
if self.game_fsm(player1, player2) == False: # Exit
return False
# 1. Retrieve user entries
if pgv.bIsServer == True:
bChanged = player1.eval_socket(player2) # Either from network inputs
else:
bChanged = player1.eval_gamepad() # Or from actual USB gamepad inputs
bChanged = player2.eval_gamepad()
else: # or other ...
if player1.bIsBot: # Bot playing ...
bot_type_value, bot_value, bot_code_value = run_bot(self.state, ball, player1)
else: # From actual USB gamepad inputs
bot_type_value, bot_value, bot_code_value = 0, 0, 0
bChanged = player1.eval_gamepad(bot_type_value, bot_value, bot_code_value)
if player2.bIsBot: # Bot playing ...
bot_type_value, bot_value, bot_code_value = run_bot(self.state, ball, player2)
else: # From actual USB gamepad inputs
bot_type_value, bot_value, bot_code_value = 0, 0, 0
bChanged = player2.eval_gamepad(bot_type_value, bot_value, bot_code_value)
if (pgv.bIsServer == True) or (pgv.bIsLocal == True):
if self.state == pgc.STATE_PLAY: # Actually playing?
@ -202,7 +212,12 @@ class PongGame:
else: # Player #1: +1
player1.score = player1.score + 1
ball.reinit(-(8 + (random() - 0.5) * 4), (random() - 0.5) * 10)
self.playsound = pgc.PLAYERMISSSOUND
self.playsound = pgc.PLAYERMISSSOUND
player1.clear_UDP_queue() # Dry up queues ...
else: # All other non-play states
player1.clear_UDP_queue() # Dry up queues ...
else: # This is a remote player or a viewer ...
if self.state == pgc.STATE_EXIT: # If we're on exit, terminate!
if self.delay <= 10: # But permit some exit sound play ...

@ -59,11 +59,12 @@ def print_event(e):
class PongPlayer:
"""The player consists of gamepad input & variables"""
def __init__(self, path, player_index, player_server, x, y, w, h):
def __init__(self, path, player_index, player_server, x, y, w, h, bIsBot):
# Gamepad init., either local, client or server
global UDP_PACKET_SIZE, serverUDP
if path:
self.bIsBot = bIsBot # Store actual or bot status
if (not bIsBot) and 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
@ -179,15 +180,18 @@ class PongPlayer:
return bChanged
def eval_gamepad(self):
def eval_gamepad(self, bot_type_value, bot_value, bot_code_value):
"""Decode gamepad events (if any ...)"""
global UDP_PACKET_SIZE, serverUDP
bChanged = False
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 self.dev or self.bIsBot:
if self.dev: # USB gamepad device avail.
for e in self.dev.events():
bChanged = bChanged or self.eval_data(e.type.value, e.value, e.code.value)
else: # Sneak in bot driven 'keys'!
bChanged = self.eval_data(bot_type_value, bot_value, bot_code_value)
if bChanged and self.bUseServer:
bData = struct.pack(pgc.UDP_TRANSFER_FORMAT,
@ -207,8 +211,8 @@ class PongPlayer:
self.seqnum += 1
else:
self.seqnum = 0
else: # This player is remote!
pass
else: # This player is remote or bot!
pass
except libevdev.EventsDroppedException:
print("Dropped!")
@ -218,6 +222,12 @@ class PongPlayer:
return bChanged
def clear_UDP_queue(self):
try: # Clear UDP queue of remainders ...
data, addr = serverUDP.recvfrom(UDP_PACKET_SIZE)
except BlockingIOError: # Until no more packets ...
pass
def eval_socket(self, other_player): # Shall be called player1.eval_socket(player2) ...
"""Decode socket events (if any ...)"""
global UDP_PACKET_SIZE, serverUDP
@ -247,7 +257,6 @@ class PongPlayer:
other_player.delta_y = delta_y
other_player.color = color
other_player.delay = delay
return True # Change indication ...
except BlockingIOError:

@ -47,15 +47,18 @@ def draw_buttonstate(x, y, player, painter, scale_x, scale_y):
class pongWindow(QMainWindow):
"""Qt5 base class"""
def __init__(self, debug_x, debug_y, player_index, viewserver, game, ball, player1, player2):
def __init__(self, debug_x, debug_y, player_index, viewserver, game, ball, player1, player2, bIsBot):
super().__init__()
self.title = "Pandemic Pong"
if pgv.bIsLocal:
self.title = "Pandemic Pong (Local)"
elif pgv.bIsViewer:
self.title = "Pandemic Pong (Viewer)"
elif player_index > 0:
self.title = "Pandemic Pong (Player #{})".format(player_index)
elif player_index > 0:
if bIsBot:
self.title = "Pandemic Pong (Player bot #{})".format(player_index)
else:
self.title = "Pandemic Pong (Player #{})".format(player_index)
self.top = 0
self.left = 0
self.win_width = 1920
@ -243,10 +246,10 @@ class pongWindow(QMainWindow):
self.update() # Enforce redraw (explicitely)
self.timer.start() # Re-enable timer event
def run_Qt5_viewer(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2):
def run_Qt5_viewer(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2, bIsBot):
"""Qt5 viewer"""
app = QApplication(sys.argv) # Eval command line args
window = pongWindow(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2) # Create Qt5 GUI
window = pongWindow(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2, bIsBot) # Create Qt5 GUI
return app.exec_() # & run logic

@ -34,7 +34,9 @@ if __name__ == '__main__':
player_server = ""
debug_x = 0 # Debug window positions
debug_y = 0
sMsg = "usage: ./pandemic_pong.py --[--sizeable|fullscreen] --[server | [viewer | player <1|2>] <ip_adress>]"
bIsBot1 = False
bIsBot2 = False
sMsg = "usage: ./pandemic_pong.py --[--sizeable|fullscreen] --[server | [viewer | player <1|2>] <ip_adress> [--bot]]"
if len(sys.argv) > 1:
if sys.argv[1] == "--sizeable": # else=default=fullscreen assumed ...
pgv.bSizeable = True
@ -55,12 +57,20 @@ if __name__ == '__main__':
if pgc.DEVLOCAL == True:
debug_x = int(pgc.GAMEAREA_MAX_X / 2.5) # Bottom centered window
debug_y = int(pgc.GAMEAREA_MAX_Y / 2)
elif len(sys.argv) == 5:
elif len(sys.argv) >= 5:
if sys.argv[2] == "--player":
pgv.bIsLocal = False
player_index = int(sys.argv[3])
player_server = sys.argv[4]
print("*** PANDEMIC PONG (player #{} talking to game server @{}) ***".format(player_index,player_server))
bot_msg = ""
if len(sys.argv) == 6: # Bot option?
if sys.argv[5] == "--bot":
bot_msg = "bot"
if player_index == 2:
bIsBot2 = True
else: # Player 1 bot assumed
bIsBot1 = True
print("*** PANDEMIC PONG (player #{} {} talking to game server @{}) ***".format(player_index, bot_msg, player_server))
if pgc.DEVLOCAL == True:
debug_x = int(pgc.GAMEAREA_MAX_X / 3 * 4 * (player_index-1)) # Left(0) or right top window
debug_y = int(pgc.GAMEAREA_MAX_Y / 40)
@ -88,23 +98,23 @@ if __name__ == '__main__':
ball = PongObject(pgc.GAMEAREA_MAX_X/2, pgc.GAMEAREA_MAX_Y/2, 20, 20, 8.0, 2.5)
if player_index == 0: # Local or viewer or server?
if pgv.bIsServer == True: # Server, no gamepads locally avail.
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot2)
elif pgv.bIsViewer == True: # Viewer, no gamepads locally avail.
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot2)
else: # Local version (both gamepads assumed locally connected)
player1 = PongPlayer(sEventQueue1, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(sEventQueue2, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(sEventQueue1, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
player2 = PongPlayer(sEventQueue2, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot2)
elif player_index == 1: # Client/server version, player #1, only one queue (the last one!) avail. ...
if pgc.DEVLOCAL == True:
player1 = PongPlayer(sEventQueue1, player_index, player_server, 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(sEventQueue1, player_index, player_server, 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
else:
player1 = PongPlayer(sEventQueue2, player_index, player_server, 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(sEventQueue2, player_index, player_server, 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
player2 = PongPlayer(None, 2, "", pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot2)
elif player_index == 2: # Client/server version, player #2, only one queue (the last one!) avail. anyway ...
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player2 = PongPlayer(sEventQueue2, player_index, player_server, pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160)
player1 = PongPlayer(None, 1, "", 10, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot1)
player2 = PongPlayer(sEventQueue2, player_index, player_server, pgc.GAMEAREA_MAX_X-40, pgc.GAMEAREA_MAX_Y/2-50, 20, 160, bIsBot2)
# TODO: Pretty ugly patch for handler data access ... (better: separate sender class)
vs.init_send_structures(game, player1, player2, ball)
@ -129,7 +139,7 @@ if __name__ == '__main__':
rc = 0
if pgv.bIsServer == False: # This is NOT the headless server!
from game_objects.pong_viewer import run_Qt5_viewer
rc = run_Qt5_viewer(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2)
rc = run_Qt5_viewer(debug_x, debug_y, player_index, viewserver, game, ball, player1, player2, bIsBot1 or bIsBot2)
else: # Headless server-side game loop
while True:

@ -0,0 +1,8 @@
# Pandemic Pong Player #1 as bot
if [ -z $1 ]; then
echo "usage: ./pp_player1_bot.sh <gameserver>"
exit
fi
./pandemic_pong.py --sizeable --player 1 $1 --bot
echo "Pandemic Pong player #1 bot client terminated."

@ -0,0 +1,8 @@
# Pandemic Pong Player #2 as bot
if [ -z $1 ]; then
echo "usage: ./pp_player2_bot.sh <gameserver>"
exit
fi
./pandemic_pong.py --sizeable --player 2 $1 --bot
echo "Pandemic Pong player #2 bot client terminated."
Loading…
Cancel
Save