Initial work, pong paddles move, lots of the groundwork for multiplayer or alternate displays is already done
This commit is contained in:
54
actor.py
Executable file
54
actor.py
Executable file
@@ -0,0 +1,54 @@
|
||||
from common import *
|
||||
import event
|
||||
import display
|
||||
import uuid
|
||||
import registry
|
||||
import dpath.util
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
class Actor(event.EventHandler, registry.Registerable):
|
||||
def __init__(self, *args, **kwargs):
|
||||
registry.Registerable.__init__(self, *args, **kwargs)
|
||||
event.EventHandler.__init__(self, *args, **kwargs)
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.vx = 0
|
||||
self.vy = 0
|
||||
dpath.util.merge(
|
||||
self.__eventHandlers__,
|
||||
{
|
||||
'moveUp': self._event_moveUp,
|
||||
'moveDown': self._event_moveDown
|
||||
}
|
||||
)
|
||||
|
||||
def _event_moveUp(self, evt):
|
||||
self.y -= 1
|
||||
|
||||
def _event_moveDown(self, evt):
|
||||
self.y += 1
|
||||
|
||||
def frame(self, display):
|
||||
return getattr(
|
||||
self,
|
||||
"frameFor{}".format(display.__class__.__name__)
|
||||
)()
|
||||
|
||||
class Text(Actor):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Actor.__init__(self, *args, **kwargs)
|
||||
self.__text__ = kwargs.get('text', "")
|
||||
|
||||
def setText(self, text):
|
||||
self.__text__ = text
|
||||
|
||||
def frameForCursesDisplay(self):
|
||||
def drawForCurses(disp):
|
||||
disp.__screen__.addstr(self.y, self.x, self.__text__)
|
||||
return drawForCurses
|
||||
|
||||
class Paddle(Text):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Text.__init__(self, *args, **kwargs)
|
||||
self.__text__ = "|"
|
||||
11
common.py
Executable file
11
common.py
Executable file
@@ -0,0 +1,11 @@
|
||||
import curses
|
||||
import Queue
|
||||
import argparse
|
||||
import sys
|
||||
import logging
|
||||
import traceback
|
||||
import json
|
||||
import threading
|
||||
|
||||
FRAMERATE=1.0/60.0
|
||||
|
||||
115
display.py
Executable file
115
display.py
Executable file
@@ -0,0 +1,115 @@
|
||||
from common import *
|
||||
import event
|
||||
import game
|
||||
import registry
|
||||
|
||||
class Display(event.EventHandler, registry.Registerable):
|
||||
# --- Static methods
|
||||
|
||||
@staticmethod
|
||||
def CurrentDisplay():
|
||||
return getattr(Display, '__currentDisplay__')
|
||||
|
||||
@staticmethod
|
||||
def NewDisplay(displayType):
|
||||
display = globals()["{}Display".format(displayType.title())]()
|
||||
setattr(Display, '__currentDisplay__', display)
|
||||
return display
|
||||
|
||||
@staticmethod
|
||||
def StaticRefresh():
|
||||
display = Display.CurrentDisplay()
|
||||
display.refresh()
|
||||
|
||||
# --- Member methods
|
||||
def __init__(self, *args, **kwargs):
|
||||
registry.Registerable.__init__(self, *args, **kwargs)
|
||||
event.EventHandler.__init__(self, *args, **kwargs)
|
||||
self.__lock_actors__ = threading.RLock()
|
||||
self.__lock_timer__ = threading.RLock()
|
||||
self.__lock_drawing__ = threading.RLock()
|
||||
self.__actors__ = {}
|
||||
self.__setDrawTimer__()
|
||||
|
||||
def __setDrawTimer__(self):
|
||||
self.__drawTimer__ = threading.Timer(FRAMERATE, Display.StaticRefresh)
|
||||
self.__drawTimer__.start()
|
||||
|
||||
def cleanup(self):
|
||||
self.__drawTimer__.cancel()
|
||||
|
||||
# ---
|
||||
|
||||
def checkInput(self):
|
||||
raise Exception("lol implement me")
|
||||
|
||||
def refresh(self):
|
||||
try:
|
||||
with self.__lock_drawing__:
|
||||
self.__screen__.clear()
|
||||
for actor in self.__actors__.values():
|
||||
self.drawActor(actor)
|
||||
self.__setDrawTimer__()
|
||||
except Exception, e:
|
||||
game.Game.CurrentGame().addEvent(('endGame', traceback.format_exc()))
|
||||
|
||||
def addActor(self, actor):
|
||||
logger = logging.getLogger()
|
||||
with self.__lock_actors__:
|
||||
if not actor.uuid in self.__actors__.keys():
|
||||
logger.debug("Added actor {}".format(actor.uuid))
|
||||
self.__actors__[actor.uuid] = actor
|
||||
|
||||
def delActor(self, actor):
|
||||
with self.__lock_actors__:
|
||||
try:
|
||||
del(self.__actors__, actor.uuid)
|
||||
except KeyError, e:
|
||||
return
|
||||
|
||||
class CursesDisplay(Display):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.logger = logging.getLogger()
|
||||
self.logger.info("CursesDisplay starting")
|
||||
Display.__init__(self, *args, **kwargs)
|
||||
self.__screen__ = curses.initscr()
|
||||
self.__screen__.keypad(1)
|
||||
try:
|
||||
curses.curs_set(0)
|
||||
except:
|
||||
logger.warn("I couldn't make the cursor invisible - I'm sorry")
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
|
||||
def cleanup(self):
|
||||
self.logger.info("CursesDisplay cleaning up")
|
||||
curses.nocbreak()
|
||||
self.__screen__.keypad(0)
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
Display.cleanup(self)
|
||||
|
||||
def refresh(self):
|
||||
Display.refresh(self)
|
||||
self.__screen__.refresh()
|
||||
|
||||
def checkInput(self):
|
||||
key = self.__screen__.getch()
|
||||
if key == ord('q'):
|
||||
raise event.EventBubble(('endGame',
|
||||
'User terminated the game',
|
||||
'/Game/uuid/*'))
|
||||
elif key == ord('w'):
|
||||
raise event.EventBubble(('moveUp', '', '/Player/cn/player1'))
|
||||
elif key == ord('s'):
|
||||
raise event.EventBubble(('moveDown', '', '/Player/cn/player1'))
|
||||
elif key == curses.KEY_UP:
|
||||
raise event.EventBubble(('moveUp', '', '/Player/cn/player2'))
|
||||
elif key == curses.KEY_DOWN:
|
||||
raise event.EventBubble(('moveDown', '', '/Player/cn/player2'))
|
||||
|
||||
def drawActor(self, actor):
|
||||
# Curses displays have it easy; actors return us a function to call
|
||||
# with a reference to ourselves, and they draw on us.
|
||||
actor.frame(self)(self)
|
||||
|
||||
21
event.py
Executable file
21
event.py
Executable file
@@ -0,0 +1,21 @@
|
||||
from common import *
|
||||
|
||||
# Raise an EventBubble with a tuple(eventName, eventMessage) to post a new
|
||||
# message to the queue. If you bubble a list of tuples - [(), (), ()] - then
|
||||
# each tuple in the list of events is added to the queue.
|
||||
class EventBubble(Exception):
|
||||
pass
|
||||
|
||||
# Events are just tuples of (eventName, eventMessage), where eventMessage
|
||||
# can be any object appropriate for eventName
|
||||
class EventHandler:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__eventHandlers__ = {}
|
||||
|
||||
def handleEvent(self, event):
|
||||
self.__eventHandlers__[event[0]](event[1])
|
||||
|
||||
def bindEvent(self, eventName, function):
|
||||
self.__eventHandlers__ = getattr(self, "__eventHandlers__", {})
|
||||
self.__eventHandlers__[eventName] = function
|
||||
|
||||
111
game.py
Executable file
111
game.py
Executable file
@@ -0,0 +1,111 @@
|
||||
from common import *
|
||||
import event
|
||||
import display
|
||||
import threading
|
||||
import actor
|
||||
import player
|
||||
import registry
|
||||
import dpath.util
|
||||
|
||||
class Game(event.EventHandler, registry.Registerable):
|
||||
|
||||
# ---- Static methods
|
||||
|
||||
# ---- This is largely unnecessary now that events are handled via exception bubbling
|
||||
@staticmethod
|
||||
def CurrentGame():
|
||||
return Game.__currentGame__
|
||||
|
||||
@staticmethod
|
||||
def NewGame(flags):
|
||||
if hasattr(Game, '__currentGame__'):
|
||||
raise AttributeError("Attempted to create a new game while one was already in progress")
|
||||
if flags['type'] == 'server':
|
||||
game = ServerHost(display=flags['display'])
|
||||
elif flags['hostname']:
|
||||
game = ServerGame(display=flags['display'], hostname=flags['hostname'])
|
||||
else:
|
||||
game = LocalGame(display=flags['display'])
|
||||
setattr(Game, '__currentGame__', game)
|
||||
return game
|
||||
|
||||
# ---- Member methods
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
registry.Registerable.__init__(self, *args, **kwargs)
|
||||
event.EventHandler.__init__(self, *args, **kwargs)
|
||||
self.__eventQueue__ = Queue.Queue()
|
||||
self.__display__ = display.Display.NewDisplay(kwargs['display'])
|
||||
self.__players__ = []
|
||||
self.gameRunning = True
|
||||
logger = logging.getLogger()
|
||||
logger.info("Display is: {}".format(str(self.__display__)))
|
||||
|
||||
self.__players__.append(player.Player(registryKey='player1'))
|
||||
self.__players__.append(player.Player(registryKey='player2'))
|
||||
self.__players__[1].x = 25
|
||||
logger = logging.getLogger()
|
||||
logger.debug("Adding actors {} and {}".format(self.__players__[0].uuid, self.__players__[1].uuid))
|
||||
self.__display__.addActor(self.__players__[0])
|
||||
self.__display__.addActor(self.__players__[1])
|
||||
|
||||
def eventLoop(self):
|
||||
reg = registry.Registry.GetRegistry()
|
||||
logger = logging.getLogger()
|
||||
logger.info("gameRunning: {}".format(self.gameRunning))
|
||||
while self.gameRunning:
|
||||
evlist = []
|
||||
try:
|
||||
try:
|
||||
evlist = self.__eventQueue__.get_nowait()
|
||||
except Queue.Empty, e:
|
||||
pass
|
||||
self.__display__.checkInput()
|
||||
except event.EventBubble, e:
|
||||
evlist = e.message
|
||||
if not evlist:
|
||||
continue
|
||||
if not isinstance(evlist, list):
|
||||
evlist = [evlist]
|
||||
for ev in evlist:
|
||||
if len(ev) < 2:
|
||||
logger.error("Malformed event : {}".format(ev))
|
||||
target = "/*/uuid/*"
|
||||
if len(ev) >= 3:
|
||||
target = ev[2]
|
||||
logger.debug("Event: {} => {}".format(ev[0], target))
|
||||
for line in str(ev[1]).split('\n'):
|
||||
logger.debug(" {}".format(line))
|
||||
# Targeted event?
|
||||
for pair in dpath.util.search(reg, target, yielded=True):
|
||||
logger.debug(" processed by {}".format(pair[0]))
|
||||
pair[1].handleEvent(ev)
|
||||
|
||||
def handleEvent(self, ev):
|
||||
if ev[0] == 'endGame':
|
||||
self.gameRunning = False
|
||||
|
||||
def addEvent(self, event):
|
||||
self.__eventQueue__.put(event)
|
||||
|
||||
def cleanup(self):
|
||||
self.__display__.cleanup()
|
||||
for player in self.__players__:
|
||||
player.cleanup()
|
||||
|
||||
class LocalGame(Game):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Game.__init__(self, *args, **kwargs)
|
||||
|
||||
def handleEvent(self, ev):
|
||||
Game.handleEvent(self, ev)
|
||||
|
||||
|
||||
class ServerHost(Game):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Game.__init__(self, *args, **kwargs)
|
||||
|
||||
class ServerGame(Game):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Game.__init__(self, *args, **kwargs)
|
||||
|
||||
9
harnesses.py
Executable file
9
harnesses.py
Executable file
@@ -0,0 +1,9 @@
|
||||
from common import *
|
||||
import actor
|
||||
import display
|
||||
|
||||
def textActor():
|
||||
ta = actor.Text(text="lolzors")
|
||||
ta.x = 10
|
||||
ta.y = 10
|
||||
display.Display.CurrentDisplay().addActor(ta)
|
||||
51
multipong.py
Executable file
51
multipong.py
Executable file
@@ -0,0 +1,51 @@
|
||||
from common import *
|
||||
import event
|
||||
import display
|
||||
import player
|
||||
import game
|
||||
|
||||
def parse():
|
||||
parser = argparse.ArgumentParser(description='A multiplayer pong clone')
|
||||
parser.add_argument('-H', '--hostname', help='The hostname of a server to connect to', required=False)
|
||||
parser.add_argument('-s', '--server', help='Start the game in server mode for clients to connect', required=False)
|
||||
parser.add_argument('-S', '--speed', help='The starting speed of the ping pong ball', required=False)
|
||||
parser.add_argument('-d', '--display', help='The display type to use (curses)', required=False, default='curses')
|
||||
parser.add_argument('-l', '--loglevel', help='The loglevel (DEBUG|INFO|WARNING|ERROR|CRITICAL)', required=False, default='INFO')
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse()
|
||||
flags = {'type': 'local', 'hostname': '', 'display': args.display}
|
||||
if args.server:
|
||||
flags['type'] = 'server'
|
||||
else:
|
||||
flags['type'] = 'local'
|
||||
if args.hostname:
|
||||
flags['type'] = 'remote'
|
||||
flags['hostname'] = str(args.hostname)
|
||||
FORMAT = '%(asctime)-15s %(message)s'
|
||||
logging.basicConfig(filename="logfile.txt",
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
level=getattr(logging, args.loglevel))
|
||||
logger = logging.getLogger()
|
||||
logger.info("Starting:")
|
||||
for line in json.dumps(flags, indent=4, sort_keys=True).split('\n'):
|
||||
logger.info(line)
|
||||
|
||||
pong = None
|
||||
try:
|
||||
pong = game.Game.NewGame(flags)
|
||||
pong.eventLoop()
|
||||
except Exception, e:
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
logger.error(line)
|
||||
logger.info("Cleaning up ...")
|
||||
try:
|
||||
pong.cleanup()
|
||||
except Exception, e:
|
||||
for line in traceback.format_exc().split('\n'):
|
||||
logger.error(line)
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
10
player.py
Executable file
10
player.py
Executable file
@@ -0,0 +1,10 @@
|
||||
from common import *
|
||||
import event
|
||||
import actor
|
||||
|
||||
class Player(actor.Paddle, event.EventHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
actor.Paddle.__init__(self, *args, **kwargs)
|
||||
|
||||
def cleanup(self):
|
||||
raise Exception("lol implement me")
|
||||
63
registry.py
Executable file
63
registry.py
Executable file
@@ -0,0 +1,63 @@
|
||||
from common import *
|
||||
import dpath.util
|
||||
import uuid
|
||||
import pprint
|
||||
|
||||
class Registry(dict):
|
||||
@staticmethod
|
||||
def GetRegistry():
|
||||
try:
|
||||
ret = getattr(Registry, '__curRegistry__')
|
||||
except AttributeError, e:
|
||||
ret = Registry()
|
||||
setattr(Registry, '__curRegistry__', ret)
|
||||
return ret
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
# Circular dependencies, ye are my bane
|
||||
import event
|
||||
import display
|
||||
import actor
|
||||
import game
|
||||
import player
|
||||
# --
|
||||
self.__validClasses__ = (
|
||||
event.EventHandler,
|
||||
actor.Actor,
|
||||
player.Player,
|
||||
game.Game,
|
||||
display.Display
|
||||
)
|
||||
|
||||
def search(self, path):
|
||||
res = []
|
||||
for item in dpath.util.search(self, path, yielded=True):
|
||||
res.append(item)
|
||||
return res
|
||||
|
||||
def register(self, value, key = None):
|
||||
logger = logging.getLogger()
|
||||
if not issubclass(value.__class__, self.__validClasses__):
|
||||
raise TypeError("Registry only accepts objects of type: {}"
|
||||
"".format(self.__validClasses__))
|
||||
for baseClass in self.__validClasses__:
|
||||
if issubclass(value.__class__, baseClass):
|
||||
cn = baseClass.__name__
|
||||
ouuid = uuid.uuid4()
|
||||
uuidpath = "/{}/uuid/{}".format(cn, ouuid)
|
||||
cnpath = "/{}/cn/{}".format(cn, key)
|
||||
dpath.util.new(self, uuidpath, value)
|
||||
if key:
|
||||
dpath.util.new(self, cnpath, value)
|
||||
logger.debug("Registered {} as {} | {}".format(value, uuidpath, cnpath))
|
||||
for line in pprint.pformat(self, indent=4).split('\n'):
|
||||
logger.debug("registry: {}".format(line))
|
||||
return ouuid
|
||||
|
||||
class Registerable:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.uuid = Registry.GetRegistry().register(
|
||||
self,
|
||||
kwargs.get('registryKey', None)
|
||||
)
|
||||
Reference in New Issue
Block a user