import stackless import random import logging import pickle import time import ansi import auth import config import telnet import traceback from twisted.internet import reactor from tpers import TPrs, TPLS, Version import tpers class Event(TPrs): def InitPersistent(self, game): self.game = game def InitTransient(self): self.rgtk = TPLS() self.queueValue = [] def oob(self, tok, dgoob): return Oob(self, tok, dgoob) def receive(self, tok): chNotify = tok.chEv self.rgtk.append(tok) while True: ev, value = chNotify.receive() if ev == self: return value ev.rgtk.append(tok) if ev in tok.mpevDgoob and tok.mpevDgoob[ev](value): raise Event.OobBreakException() @classmethod def select(cls, tok, *rgev): for ev in rgev: ev.rgtk.append(tok) chNotify = tok.chEv fOob = False while True: evRcv, value = chNotify.receive() if evRcv in tok.mpevDgoob: if tok.mpevDgoob[ev](value): fOob = True break else: break for ev in rgev: if ev != evRcv: ev.rgtk.remove(tok) if fOob: raise Event.OobBreakException() return evRcv, value @classmethod def selectDg(cls, tok, *rgev_dg): rgev, rgdg = zip(*rgev_dg) ev, value = cls.select(tok, *rgev) return rgdg[rgev.index(ev)](value) def fire(self, value = None): self.queueValue.append(value) self.game.queueEv.append(self) self.game.ensureRun() def fireI(self): rgtkOld = self.rgtk value = self.queueValue[0] self.queueValue = self.queueValue[1:] self.rgtk = TPLS() for tk in rgtkOld: if tk.fRunning: tk.chEv.send((self,value)) stackless.run() class Oob(object): def __init__(self, ev, tok, dgoob): self.ev = ev self.tok = tok self.dgoob = dgoob def __enter__(self): self.ev.rgtk.append(self.tok) self.tok.mpevDgoob[self.ev] = self.dgoob def __exit__(self, etype, eval, tb): if etype != TaskletExit: try: self.tok.mpevDgoob.pop(self.ev) self.ev.rgtk.remove(self.tok) if etype == OobBreakException: return True except Exception: print "exception exiting oob:" traceback.print_exc() print "oob:", etype, eval traceback.print_tb(tb) class OobBreakException(Exception): pass class Runner(TPrs): def InitPersistent(self): self.mpuser_cld = {} print "New Runner created" def InitTransient(self): self.rgclient = [] def GetCld(self, user): cld = self.mpuser_cld.get(user) if cld == None: cld = Cld(user) self.mpuser_cld[user] = cld return cld def RunGameI(self, client): client.runner = self self.rgclient.append(client) def OnDisconnect(): self.rgclient.remove(client) client.addDgQuit(OnDisconnect) return self.RunGame(client) def RunServer(self): for client in self.rgclient: client.quit(True) telnet.RunServer(self.RunGameI) def Run(clsRunner, fn): runner = tpers.Odb.Load(fn) or clsRunner() def ExecCmd(stCmd): if stCmd == "save": tpers.Odb.Save(runner, fn) return "Saved to " + fn return "Unrecognized command: '" + stCmd + "'" telnet.RunCmdServer(ExecCmd) runner.RunServer() class Ownable(TPrs): def InitPersistent(self, owner = None): self.owner = owner self.rgtokOwn = TPLS() if owner != None: owner.rgtokOwn.append(self) def FPersist(self): if self.owner != None and not self.owner.FPersist(): return False return TPrs.FPersist(self) def TransferToOwner(self, ownerNew): self.owner.rgtokOwn.remove(self) ownerNew.rgtokOwn.append(self) self.owner = ownerNew def die(self): if self.owner != None: self.owner.rgtokOwn.remove(self) self.owner = None for tok in [x for x in self.rgtokOwn]: tok.die() self.DontPersist() @Version(2) class Game(Ownable): def InitPersistent(self): Ownable.InitPersistent(self) self.fQuit = False self.running = False self.mptagTokenset = {} self.rgdgdie = [] self.evJoin = Event(self) self.evLeave = Event(self) self.mpuser_cldg = {} self.CreateRgtok(self.GetRgclsTokPers(), True) def InitTransient(self): self.MakeTransient("fScheduled", False) self.rgclient = [] self.rgproc = [PTimer(), PEvent(), PDraw(*self.GetRgtagDraw())] for proc in self.rgproc: proc.setGame(self) # don't recreate tokens when game has died (shouldn't even be persisting) if not self.FDead(): self.CreateRgtok(self.GetRgclsTokTrans(), False) else: print "Dead", self.__class__, "has persisted" def FDead(self): try: rc = self.rc return True except: return False def GetCldg(self, user): cldg = self.mpuser_cldg.get(user) if cldg == None: cldg = Cldg() self.mpuser_cldg[user] = cldg return cldg def GetRgclsTokPers(self): return [] def GetRgclsTokTrans(self): return [] def GetRgtagDraw(self): return ["background", "solid", "overlay"] def CreateRgtok(self, rgclsTok, fPersist): for clsTok in rgclsTok: if FIter(clsTok): tok = clsTok[0](self, *clsTok[1:]) else: tok = clsTok(self) if not fPersist: tok.DontPersist() def tagToken(self, token, tag): if not (tag in self.mptagTokenset): self.mptagTokenset[tag] = set([token]) else: self.mptagTokenset[tag].add(token) def untagToken(self, token, tag): if tag in self.mptagTokenset: self.mptagTokenset[tag].discard(token) def rgtoken(self, tag, rgpriority = []): if not (tag in self.mptagTokenset): return [] rgtoken = [] tokenSet = self.mptagTokenset[tag].copy() for priority in rgpriority: if (priority in self.mptagTokenset): ptokenSet = self.mptagTokenset[priority] & tokenSet for token in ptokenSet: rgtoken.append(token) tokenSet -= ptokenSet for token in tokenSet: rgtoken.append(token) return rgtoken def quit(self): self.running = False self.fQuit = True def finish(self, rc = None): self.running = False self.rc = rc class QuitException(Exception): pass class LoadException(Exception): pass def die(self): for dgdie in self.rgdgdie: dgdie() Ownable.die(self) def runStep(self): try: if self.running: for proc in self.rgproc: proc.process() if not self.running: self.die() for client in self.rgclient: client.leaveGame(self.rc) for client in self.rgclient: client.postRunStep() finally: self.fScheduled = False return self.running def joinGame(self,client): self.rgclient.append(client) self.evJoin.fire(client) def leaveGame(self, client): self.evLeave.fire(client) def leaveGameI(self, client): self.rgclient.remove(client) def ensureRun(self): self.running = True if not self.fScheduled: self.fScheduled = True reactor.callLater(0, self.runStep) def UpgradeFrom(self, versionOld): if versionOld < 2: self.mpuser_cldg = {} # Client data @Version(2) class Cld(TPrs): def InitPersistent(self, user): self.user = user def UpgradeFrom(self, versionOld): if versionOld < 2: del self.mpgame_cldg #per-game client data class Cldg(TPrs): def FPersist(self): if len(self._persistent) == 0: return False return TPrs.FPersist(self) class Client(TPrs): def InitTransient(self): self.chRun = stackless.channel() self.gameCurr = None self.chCont = None self.fRunning = False self.dgJoinGame = None self.rgdgQuit = [] self.fQuit = False self.cld = None self.runner = None def FPersist(self): return False def go(self, fnRun, protocol): self.protocol = protocol self.tskRun = stackless.tasklet(fnRun) self.tskRun(self) if self._runTskRun(): self.gameCurr.ensureRun() def joinGame(self, game): assert not self.fRunning, "already running" chCont = stackless.channel() self.chRun.send((game, chCont)) return chCont.receive() def leaveGame(self, rc = None): self.fRunning = False self.gameCurr.leaveGame(self) self.rc = rc def quit(self): self.leaveGame(None) self.fQuit = True def _runTskRun(self): "run self.tskRun after it has been scheduled, and await results" stackless.run() if not self.tskRun.scheduled: return False self.gameCurr, self.chCont = self.chRun.receive() self.gameCurr.joinGame(self) self.fRunning = True if self.dgJoinGame: self.dgJoinGame(self, self.gameCurr) return True def postRunStep(self): if not self.fRunning: self.gameCurr.leaveGameI(self) if not self.fQuit: self.chCont.send(self.rc) self.fQuit = not self._runTskRun() if self.fQuit: for dgQuit in self.rgdgQuit: dgQuit() return self.fQuit def beep(self): pass def login(self, user, passwd): authr = getattr(auth, config.AUTH + "Auth")() if (authr.FAuthorized(user, passwd)): self.cld = self.runner.GetCld(user) return True return False def addDgQuit(self, dgquit): self.rgdgQuit.append(dgquit) def removeDgQuit(self, dgquit): self.rgdgQuit.remove(dgquit) def Cldg(self, game = None): if game == None: game = self.gameCurr return game.GetCldg(self.cld.user) class Processor(TPrs): def setGame(self, game): self.game = game def process(self): pass class PTimer(Processor): def msNow(self): return int(time.time() * 1000) def setGame(self, game): self.game = game self.fReset = False game.evTick = Event(game) game.ms = 0 game.msDelta = 0 self.dms = -self.msNow() def __setstate__(self, dict): Processor.__setstate__(self, dict) self.fReset = True def process(self): if self.fReset: # keep game.ms constant self.dms = self.game.ms - self.msNow() self.fReset = False msNew = self.msNow() + self.dms self.game.msDelta = msNew - self.game.ms self.game.ms = msNew self.game.evTick.fire(self.game.msDelta) class PEvent(Processor): def setGame(self, game): self.game = game game.queueEv = [] game.rgdgdie.append(self.die) def die(self): for ev in self.game.queueEv: for tk in ev.rgtk: tk.die() ev.stkValue = [] self.game.queueEv = [] def process(self): while self.game.queueEv: self.game.queueEv[0].fireI() self.game.queueEv.pop(0) class PDraw(Processor): def InitPersistent(self, *rgprio): self.rgprio = rgprio self.msDrawAgain = -1 self.fTimerSet = False def process(self): if self.game.ms >= self.msDrawAgain: for client in self.game.rgclient: ascr = ansi.Ascr() for tok in self.game.rgtoken("drawable", self.rgprio): try: tok.draw(ascr, client) except Exception: print "error drawing" traceback.print_exc() client.protocol.Draw(ascr) self.msDrawAgain = self.game.ms + 120 self.fTimerSet = False elif not self.fTimerSet: reactor.callLater((self.msDrawAgain - self.game.ms) / 1000.0, self.game.ensureRun) self.fTimerSet = True class Taggable(Ownable): def InitPersistent(self, owner, *rgtag): Ownable.InitPersistent(self, owner) game = owner while not isinstance(game, Game): game = game.owner self.game = game self.tags = set() self.tag(*rgtag) def tag(self, *rgtag): self.tags |= set(rgtag) for tag in rgtag: self.game.tagToken(self, tag) def untag(self, *rgtag): self.tags -= set(rgtag) for tag in rgtag: self.game.untagToken(self, tag) def die(self): for tag in self.tags.copy(): self.untag(tag) Ownable.die(self) class Token(Taggable): def InitPersistent(self, owner, *rgtag): Taggable.InitPersistent(self, owner, *rgtag) self.msGamePreWait = 0 self.ms = 0 self.fRunning = True self.rgtokOwn = TPLS() def InitTransient(self): Taggable.InitTransient(self) self.chEv = stackless.channel() self.tasklet = stackless.tasklet(self.runI) self.mpevDgoob = {} self._fWriteToPersistent = True self.tasklet() def __enter__(self): return self def __exit__(self, etype, eval, traceback): self.die() def wait(self, msDelta): # have we waited once already this tick? If not, reset if (self.msGamePreWait != self.game.ms): self.ms = self.game.ms msEnd = self.ms + msDelta delay = (msEnd - self.game.ms) / 1000.0 if delay > 0: reactor.callLater(delay, self.game.ensureRun) while (self.game.ms < msEnd): self.game.evTick.receive(self) self.ms = msEnd self.msGamePreWait = self.game.ms def die(self): self.fRunning = False Taggable.die(self) self.tasklet.kill() # the token may be killing itself; make sure it's properly marked as dead before killing the tasklet def run(self): pass def runI(self): try: self.run() #except TaskletExit: # raise except Exception: print "token script crashed:" traceback.print_exc() def FIter(l): try: iter(l) return True except Exception: return False class TokenClient(Token): def InitPersistent(self, owner, client, *rgtag): Token.InitPersistent(self, owner, *rgtag) self.client = client def FPersist(self): return False