551 lines
15 KiB
Python
551 lines
15 KiB
Python
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
|