Initial work, pong paddles move, lots of the groundwork for multiplayer or alternate displays is already done

This commit is contained in:
2013-12-14 22:54:21 -05:00
commit 26eb57c61d
9 changed files with 445 additions and 0 deletions

54
actor.py Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
)