From 10edbacf869109972191747168f65569dedde1d7 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Sun, 19 Jul 2020 21:31:54 -0400 Subject: [PATCH] WIP robot creation in board editor! - refactor client - now has a _stack_ of current Games, the top of which is active - prevents shutdown of whiteboard when "leaving" to edit source code - add tpers.StaticIndex / inspector.py for interactively querying object graphs --- basetoken.py | 33 +++++++-- engine.py | 112 ++++++++++++++++++++-------- inspector.py | 7 ++ scripting.py | 15 ++-- telnet.py | 20 ++--- tpers.py | 58 +++++++++++++++ whiteboard.py | 198 +++++++++++++++++++++++++++++++++++++++++--------- 7 files changed, 353 insertions(+), 90 deletions(-) create mode 100644 inspector.py diff --git a/basetoken.py b/basetoken.py index f5908b1..d2c8682 100644 --- a/basetoken.py +++ b/basetoken.py @@ -73,7 +73,7 @@ class Terminal(Token): self.fBlink = fBlink def getKey(self): - return self.client.evKey.receive(self) + return self.client.EvKey(self.game).receive(self) def getPrintableKey(self): while True: @@ -199,9 +199,14 @@ class Terminal(Token): class LeaveJoinToken(Token): def run(self): + evLeave = self.game.evSuspend if self.FOnActivate() else self.game.evLeave + evJoin = self.game.evActivate if self.FOnActivate() else self.game.evJoin while True: - Event.selectDg(self, (self.game.evLeave, self.OnLeave), (self.game.evJoin, self.OnJoin)) - + Event.selectDg(self, (evLeave, self.OnLeave), (evJoin, self.OnJoin)) + + def FOnActivate(self): + return False + class AutoJoiner(LeaveJoinToken): def InitPersistent(self, owner, *rgclsTok): Token.InitPersistent(self, owner) @@ -234,6 +239,9 @@ class AliveWithPlayers(LeaveJoinToken): Token.InitTransient(self) self.rgclient = [] + def FOnActivate(self): + return True + def OnLeave(self, client): self.rgclient.remove(client) if len(self.rgclient) == 0: @@ -705,9 +713,18 @@ class MiMenuHoriz(MiMenuI): mi.DrawWithParent(selor, ascr, x, y, wT, miSelected == mi) x = x + wT -def DgProjectMiButton(dgExec): +class ProjectedValue(TPrs): + def InitPersistent(self, value, dgStName): + self.value = value + self.dgStName = dgStName + def StName(self): + return self.dgStName(self.value) + def Value(self): + return self.value + +def DgProjectMiButton(dgExec, dgStName = None): def DgProject(value): - return MiButton(None, lambda: dgExec(value), value) + return MiButton(None, lambda: dgExec(value), ProjectedValue(value, dgStName) if dgStName else value) return DgProject class RgmiProjected(TPrs): @@ -781,13 +798,15 @@ class OvStatic(TokenClient): def Value(self, stName = None): return self.miMenu.Value(stName) + class OvMenu(OvStatic): def run(self): while True: - self.miMenu.HandleKey(self.client.evKey.receive(self)) + self.miMenu.HandleKey(self.client.EvKey(self.game).receive(self)) def draw(self, ascr, client): self.drawI(ascr, client, True) + class OvPopup(OvStatic): def InitPersistent(self, owner, client, miMenu): OvStatic.InitPersistent(self, owner, client, miMenu, self.MIDDLE, "drawable", "menu") @@ -800,7 +819,7 @@ class OvPopup(OvStatic): self.evShow.receive(self) self.fAwake = True while self.fAwake: - key = self.client.evKey.receive(self) + key = self.client.EvKey(self.game).receive(self) if not self.miMenu.HandleKey(key): if key == ansi.K_TAB or key == ansi.K_DEL or key == ' ' or ansi.FEnter(key): self.fAwake = False diff --git a/engine.py b/engine.py index 4631c91..c1a871c 100644 --- a/engine.py +++ b/engine.py @@ -184,7 +184,7 @@ class Ownable(TPrs): tok.die() self.DontPersist() -@Version(2) +@Version(3) class Game(Ownable): def InitPersistent(self): Ownable.InitPersistent(self) @@ -194,12 +194,16 @@ class Game(Ownable): self.rgdgdie = [] self.evJoin = Event(self) self.evLeave = Event(self) + self.evActivate = Event(self) + self.evSuspend = Event(self) self.mpuser_cldg = {} self.CreateRgtok(self.GetRgclsTokPers(), True) def InitTransient(self): self.MakeTransient("fScheduled", False) - self.rgclient = [] + self.rgclientActive = [] + self.rgclientSuspended = [] + self.rgclientLeaving = [] self.rgproc = [PTimer(), PEvent(), PDraw(*self.GetRgtagDraw())] for proc in self.rgproc: proc.setGame(self) @@ -240,6 +244,9 @@ class Game(Ownable): if not fPersist: tok.DontPersist() + def RgclientConnected(self): + return self.rgclientActive + self.rgclientSuspended + def tagToken(self, token, tag): if not (tag in self.mptagTokenset): self.mptagTokenset[tag] = set([token]) @@ -268,10 +275,12 @@ class Game(Ownable): return rgtoken def quit(self): + assert len(self.rgclientSuspended) == 0 self.running = False self.fQuit = True def finish(self, rc = None): + assert len(self.rgclientSuspended) == 0 self.running = False self.rc = rc class QuitException(Exception): @@ -291,22 +300,44 @@ class Game(Ownable): proc.process() if not self.running: self.die() - for client in self.rgclient: - client.leaveGame(self.rc) - for client in self.rgclient: + for client in self.rgclientActive: + if client not in self.rgclientLeaving: + client.leaveGame(self.rc) + deadClients = list(self.rgclientLeaving) + del self.rgclientLeaving[:] + for client in deadClients: + if client in self.rgclientActive: + self.rgclientActive.remove(client) + if client in self.rgclientSuspended: + self.rgclientSuspended.remove(client) client.postRunStep() finally: self.fScheduled = False return self.running def joinGame(self,client): - self.rgclient.append(client) + self.rgclientActive.append(client) self.evJoin.fire(client) + self.evActivate.fire(client) def leaveGame(self, client): - self.evLeave.fire(client) + if self.running: + if client in self.rgclientActive: + self.evSuspend.fire(client) + self.evLeave.fire(client) + self.rgclientLeaving.append(client) + + def reactivateClient(self, client): + assert client in self.rgclientSuspended + self.rgclientSuspended.remove(client) + self.rgclientActive.append(client) + self.evActivate.fire(client) + + def suspendClient(self, client): + assert client in self.rgclientActive + self.rgclientActive.remove(client) + self.rgclientSuspended.append(client) + self.evSuspend.fire(client) - def leaveGameI(self, client): - self.rgclient.remove(client) def ensureRun(self): self.running = True if not self.fScheduled: @@ -315,6 +346,9 @@ class Game(Ownable): def UpgradeFrom(self, versionOld): if versionOld < 2: self.mpuser_cldg = {} + if versionOld < 3: + self.evActivate = Event(self) + self.evSuspend = Event(self) # Client data @Version(2) @@ -336,9 +370,9 @@ class Client(TPrs): def InitTransient(self): self.chRun = stackless.channel() self.gameCurr = None + self.mpgame_evKey = {} + self.rggameSuspended = [] self.chCont = None - self.fRunning = False - self.dgJoinGame = None self.rgdgQuit = [] self.fQuit = False self.cld = None @@ -353,19 +387,33 @@ class Client(TPrs): 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() + if not self.gameCurr: + chCont = stackless.channel() + self.chRun.send((game, chCont)) + return chCont.receive() + else: + self.gameCurr.suspendClient(self) + self.rggameSuspended.append(self.gameCurr) + self.gameCurr = game + self.gameCurr.joinGame(self) def leaveGame(self, rc = None): - self.fRunning = False self.gameCurr.leaveGame(self) - self.rc = rc + if self.gameCurr in self.mpgame_evKey: + del self.mpgame_evKey[self.gameCurr] + + if len(self.rggameSuspended) == 0: + self.gameCurr = None + self.rc = rc + else: + self.gameCurr = self.rggameSuspended.pop() + self.gameCurr.reactivateClient(self) def quit(self): - self.leaveGame(None) + while self.gameCurr: + self.leaveGame(None) self.fQuit = True + def _runTskRun(self): "run self.tskRun after it has been scheduled, and await results" stackless.run() @@ -373,20 +421,17 @@ class Client(TPrs): 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.gameCurr: if not self.fQuit: self.chCont.send(self.rc) self.fQuit = not self._runTskRun() if self.fQuit: for dgQuit in self.rgdgQuit: dgQuit() + del self.rgdgQuit[:] return self.fQuit def beep(self): pass @@ -401,9 +446,18 @@ class Client(TPrs): def removeDgQuit(self, dgquit): self.rgdgQuit.remove(dgquit) - def Cldg(self, game = None): - if game == None: - game = self.gameCurr + + def EvKey(self, game): + if game: + if game not in self.mpgame_evKey: + self.mpgame_evKey[game] = Event(game) + return self.mpgame_evKey[game] + + def KeyPressed(self, ch): + if self.gameCurr: + self.EvKey(self.gameCurr).fire(ch) + + def Cldg(self, game): return game.GetCldg(self.cld.user) class Processor(TPrs): @@ -463,7 +517,7 @@ class PDraw(Processor): def process(self): if self.game.ms >= self.msDrawAgain: - for client in self.game.rgclient: + for client in self.game.rgclientActive: ascr = ansi.Ascr() for tok in self.game.rgtoken("drawable", self.rgprio): try: @@ -552,8 +606,8 @@ class Token(Taggable): def runI(self): try: self.run() - #except greenlet.GreenletExit: - # raise + except greenlet.GreenletExit: + raise except Exception: print "token script crashed:" traceback.print_exc() diff --git a/inspector.py b/inspector.py new file mode 100644 index 0000000..140ae44 --- /dev/null +++ b/inspector.py @@ -0,0 +1,7 @@ +# to use: +# python -i inspector.py +# >>> i.Rgclass() ... +from whiteboard import * + +o = tpers.Odb.Load('whiteboard.marm') +i = tpers.StaticIndex().Populate(o) diff --git a/scripting.py b/scripting.py index 47ae961..869c860 100644 --- a/scripting.py +++ b/scripting.py @@ -28,9 +28,10 @@ def RegStmt(clsStmt): class Botdef(TPrs): def InitPersistent(self, stName, ach, x, y): self.stName = stName - self.ast = ast + self.ach = ach self.x = x self.y = y + self.fPlayer = False self.syntOnTouch = SyntBlock() class Flagdef(TPrs): @@ -62,6 +63,9 @@ class Vm(TPrs): def FFlagSet(self, flagdef): return self.mpflagdef[flagdef] + def Fail(self, stMessage, synt): + self.Log(Fail(stMessage, synt)) + def Log(self, fail): print(fail.stMessage, fail.synt) @@ -155,7 +159,7 @@ class Synt(Typeable): pass def Eval(self, vm): "Execute yourself, in the context of vm." - return Fail("Missing information", self) + return vm.Fail("Missing information", self) def StypeForChild(self, syntChild): "Return the stype that should be applied to the given child." @@ -361,7 +365,7 @@ class SyntFlagRef(Synt): try: return vm.FFlagSet(self.flagdef) except: - return Fail("No such flag", self) + return vm.Fail("No such flag", self) class SyntIsFlagSet(SyntDesc): desc = [["set"]] @@ -422,10 +426,9 @@ class Fail(TPrs): edit a script while the game is running and see the effect of their changes immediately; this suggests that failures should be logged and the script should continue to run. """ - def InitPersistent(self, vm, stFailure, synt): + def InitPersistent(self, stFailure, synt): self.stFailure = stFailure self.synt = synt - vm.Log(self) # projection cursor class Pcur(object): @@ -880,7 +883,7 @@ class Pov(TokenClient): def run(self): while True: - key = self.client.evKey.receive(self) + key = self.client.EvKey(self.game).receive(self) if key == ansi.K_PGDN: self.block.Eval(Vm(self.defs)) psel = self.pselstate.PselByClient(self.client) diff --git a/telnet.py b/telnet.py index 3046868..d58c0e4 100644 --- a/telnet.py +++ b/telnet.py @@ -41,8 +41,6 @@ class AnsiTelnet(protocol.Protocol): print "created" self.ast = None self.client = client - self.evKey = None - self.evArrow = None self.Refresh() def connectionMade(self): @@ -122,27 +120,21 @@ class AnsiTelnet(protocol.Protocol): print "negotiated!" self.transport.write(ansi.esc + "2J") #cls self.transport.write(ansi.esc + "=25l") #hide the cursor - self.client.dgJoinGame = self.CreateEvents self.client.addDgQuit(self.transport.loseConnection) self.client.go(self.factory.fnRun, self) def RcvText(self, text): - if self.client.evKey: - for ch in text: - self.client.evKey.fire(ch) + for ch in text: + self.client.KeyPressed(ch) mpcode_key = {"A": ansi.K_UP, "B": ansi.K_DOWN, "C": ansi.K_RIGHT, "D": ansi.K_LEFT, "K": ansi.K_END, "H": ansi.K_HOME, "V": ansi.K_PGUP, "U": ansi.K_PGDN } def RcvAnsi(self, code, rgarg): - if self.client.evKey: - if code in self.mpcode_key: - self.client.evKey.fire(self.mpcode_key[code]) - else: - print "unrecognized code", code, rgarg - - def CreateEvents(self, client, game): - client.evKey = engine.Event(game) + if code in self.mpcode_key: + self.client.KeyPressed(self.mpcode_key[code]) + else: + print "unrecognized code", code, rgarg def Draw(self, ascr): self.transport.write(self.ascr.AstDiff(ascr)) diff --git a/tpers.py b/tpers.py index a9097e0..f40a7d9 100644 --- a/tpers.py +++ b/tpers.py @@ -319,3 +319,61 @@ def _unpickle_method(func_name, obj, cls): import copy_reg import types copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method) + +def hashable(v): + try: + hash(v) + return True + except: + return False + +class StaticIndex(TPrs): + def InitPersistent(self, fIncludeTemp = False): + self.mpv_a_rge = {} + self.rgv = set() + self.fIncludeTemp = fIncludeTemp + + def FPersist(self): + return False + + def AddEAV(self, e, a, v): + if v not in self.mpv_a_rge: + self.mpv_a_rge[v] = {} + a_rge = self.mpv_a_rge[v] + if a not in a_rge: + a_rge[a] = [] + a_rge[a].append(e) + + def Populate(self, v, a = (), e = None): + if hashable(v): + if e: + self.AddEAV(e, a, v) + if v in self.rgv: + return self + self.rgv.add(v) + if isinstance(v, TPrs): + attrs = set(v._persistent.keys()) + if self.fIncludeTemp: + attrs = attrs.union(set(v.__dict__.keys())) + for attr in attrs: + child = getattr(v, attr) + self.Populate(child, (attr,), v) + elif isinstance(v, clsList) or isinstance(v, clsTuple): + for ichild, child in enumerate(v): + self.Populate(child, a + (ichild,), e) + elif isinstance(v, clsSet): + for child in v: + self.Populate(child, a, e) + elif isinstance(v, clsDict): + for k, child in v.items(): + self.Populate(child, a + (k,), e) + return self + + def Rgclass(self): + return set([v.__class__ for v in self.rgv]) + + def RgvWithClass(self, cls): + return [v for v in self.rgv if isinstance(v, cls)] + + def Rgref(self, v): + return self.mpv_a_rge.get(v) diff --git a/whiteboard.py b/whiteboard.py index 3049be7..d1b0cc2 100755 --- a/whiteboard.py +++ b/whiteboard.py @@ -20,6 +20,7 @@ from basetoken import * from tpers import * from datetime import * from util import * +from scripting import Botdef, Flagdef, Defs, PselState, Pov import telnet import time import login @@ -129,8 +130,13 @@ class Drawing(Ownable): def SetTimestamp(self): self.dtLastModified = datetime.now() -@Version(3) -class Board(Drawing): +@Version(4) +class Board(Drawing): + def InitPersistent(self, owner, stName, user, rglayer=None, dtLastModified=None): + super(Board, self).InitPersistent(owner, stName, user, rglayer=rglayer, dtLastModified=dtLastModified) + self.defs = Defs() + self.layerObj = self.rglayer[0] + def StType(self): return "Board" def W(self): return config.W @@ -139,6 +145,10 @@ class Board(Drawing): def draw(self, ascr): for layer in reversed(self.rglayer): ascr.PutAscr(layer.ascr) + + def DrawObjectDefs(self, ascr): + for botdef in self.defs.rgbotdef: + ascr.PutAch(botdef.ach, botdef.x, botdef.y) def Save(self): fn = config.DIR_ANSI + "/" + "_".join([str(x) for x in time.localtime()]) @@ -158,6 +168,9 @@ class Board(Drawing): self.user = gameWB.user if versionOld < 3: self.chat = Chat() + if versionOld < 4: + self.defs = Defs() + self.layerObj = self.rglayer[0] class Sprite(Drawing): def InitPersistent(self, owner, stName, user, w, h): @@ -225,19 +238,22 @@ class Chat(TPrs): def InitTransient(self): TPrs.InitTransient(self) self.cchl = 100 - self.DetachFromGame() + self.MakeTransient("game", None) + self.MakeTransient("evChat", None) def InitPersistent(self): TPrs.InitPersistent(self) self.rgchl = [] def AttachToGame(self, game): - self.game = game - self.evChat = Event(game) + with self.SetTransiently(): + self.game = game + self.evChat = Event(game) def DetachFromGame(self): - self.game = None - self.evChat = None + with self.SetTransiently(): + self.game = None + self.evChat = None def Add(self, chl): if len(chl.st) > 0: @@ -248,8 +264,10 @@ class Chat(TPrs): def CChl(self): return len(self.rgchl) + def CUsers(self): - return len(self.game.rgclient) + return len(self.game.RgclientConnected()) + def DrawMsg(self, ascr, x, y, w, h, colBg=ansi.BLACK, ichl=0): yDraw = y + h - 1 for chl in self.rgchl[ichl:]: @@ -274,8 +292,8 @@ class Chat(TPrs): def DrawUsers(self, ascr, x, y, w, h, colBg=ansi.BLACK, iuser=0): yDraw = y - for client in sorted(self.game.rgclient, lambda c1, c2: cmp(c1.Cldg().iclient, c2.Cldg().iclient))[iuser:]: - ascr.PutSt("{0:{1}}".format(str(client.Cldg().iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg) + for client in sorted(self.game.RgclientConnected(), lambda c1, c2: cmp(c1.Cldg(self.game).iclient, c2.Cldg(self.game).iclient))[iuser:]: + ascr.PutSt("{0:{1}}".format(str(client.Cldg(self.game).iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg) yDraw = yDraw + 1 if yDraw == y + h: break @@ -320,19 +338,38 @@ class Cursor(TokenClient): miQuit = MiButton("Back to Menu", self.client.leaveGame) self.cf = self.MenuDrawing(miFg, miBg, miChars, miQuit) self.miChat = MiMenu([None, MiChat(self.client, 13, self.game.drawing.chat), MiTypein(self.SendMsg), None, miQuit, None]) - self.rgmiLayer = RgmiProjected(self.game.drawing.rglayer, DgProjectMiButton(self.SelectLayer)) + self.rgmiLayer = RgmiProjected(self.game.drawing.rglayer, DgProjectMiButton(self.SelectLayer, self.StLayerName)) self.miLayers = self.MenuLayers(miQuit) self.ovPopup = OvPopup(self, self.client, MiTab.MiTabbed(["Drawing", self.cf], [self.StLayerTab(), self.miLayers], ["Chat", self.miChat])) - miStatus = MiMenuHoriz([MiStatic("Hit for menu"), MiValue(miChars)]) + miStatus = MiMenuHoriz([MiDgText(self.StStatus), MiValue(miChars)]) miStatus.ListenForNotice(self.miChat) self.ovStatus = OvStatic(self, self.client, miStatus, OvStatic.BOTTOM) + def StLayerName(self, layer): + return ('[Bot] ' if layer == self.game.drawing.layerObj else '') + layer.StName() + + def StStatus(self): + stStatus = ": Menu" + if self.GetLayer() == self.game.drawing.layerObj: + botdef = self.BotdefAtPos(self.pos) + if botdef: + stStatus += " | : Edit bot" + elif self.game.drawing.layerObj.ascr.GetAch(self.pos.X(), self.pos.Y()) != ansi.achBlank: + stStatus += " | : Make bot" + return stStatus + + def BotdefAtPos(self, pos): + for botdef in self.game.drawing.defs.rgbotdef: + if botdef.x == pos.X() and botdef.y == pos.Y(): + return botdef + def MenuDrawing(self, miFg, miBg, miChars, miQuit): return MiMenu([None, miFg, miBg, None, miChars, None, MiButton("Save", self.Save), None, miQuit, None]) def MenuLayers(self, miQuit): return MiMenu(Rgseq([None], self.rgmiLayer, [None, MiButton("New Layer", self.NewLayer), MiButton("Move up", self.LayerUp), MiButton("Move down", self.LayerDown), - None, MiToggle("Show One"), None, MiStatic("Rename selected layer:"), MiTypein(self.RenameLayer, "LayerName", 80), None, miQuit, None])) + None, MiToggle("Show One"), MiButton("Set as bot layer", self.SetBotLayer), None, + MiStatic("Rename selected layer:"), MiTypein(self.RenameLayer, "LayerName", 80), None, miQuit, None])) def StLayerTab(self): return "Layers" def Save(self): self.game.drawing.Save() @@ -345,7 +382,8 @@ class Cursor(TokenClient): return True def GetLayer(self): - return self.ovPopup.GetSelection("layer").Value() + selLayer = self.ovPopup.GetSelection("layer") + return selLayer.Value().Value() if selLayer else None def NewLayer(self): ilayer = 1 @@ -373,6 +411,9 @@ class Cursor(TokenClient): self.GetLayer().SetName(st) return True + def SetBotLayer(self): + self.game.drawing.layerObj = self.GetLayer() + def blinkCursor(self, fBlink): self.fBlink = fBlink if not fBlink and self.cDontBlink > 0: @@ -391,7 +432,7 @@ class Cursor(TokenClient): def run(self): with self.game.drawing.chat.evChat.oob(self, self.OnChat): while True: - key = self.client.evKey.receive(self) + key = self.client.EvKey(self.game).receive(self) fDontBlink = True if key == ansi.K_LEFT: self.pos.Left() @@ -419,6 +460,8 @@ class Cursor(TokenClient): self.Skip(self.pos.Up) elif key == ansi.K_PGDN: self.Skip(self.pos.Down) + elif key == ansi.K_NEWLINE or key == ansi.K_RETURN: + self.EditBot() elif ansi.FKeyPrintable(key): if key.isdigit(): self.PutWb(chr(self.cf.Value("Characters")[IchFromCh(key)])) @@ -466,6 +509,41 @@ class Cursor(TokenClient): def FDrawCursor(self): return self.fBlink or self.cDontBlink > 0 + def EditBot(self): + if self.GetLayer() == self.game.drawing.layerObj: + botdef = self.BotdefAtPos(self.pos) + ach = self.game.drawing.layerObj.ascr.GetAch(self.pos.X(), self.pos.Y()) + fNewBot = ach != ansi.achBlank if not botdef else False + if not fNewBot and not botdef: + return + if fNewBot: + botdef = Botdef('Bot ' + str(len(self.game.drawing.defs.rgbotdef) + 1) + ' (' + ansi.Ach(ach).ch + ')', ach, self.pos.X(), self.pos.Y()) + + evDone = Event(self.game) + def DgDone(val): + return lambda: evDone.fire(val) + + miBotName = MiTypein(None, "BotName", 80) + miBotName.SetValue(botdef.stName) + miMenu = MiMenu([None, + MiStatic("Bot Name"), miBotName, None, + MiToggle("Is Player", botdef.fPlayer), None, + MiButton("Save", DgDone(True)), MiButton("Cancel", DgDone(False)), None, + MiButton("Edit Code (live)", lambda: self.client.joinGame(self.game.GameSyntEdit(botdef.syntOnTouch))), None]) + + ov = OvMenu(self, self.client, miMenu, OvMenu.MIDDLE) + try: + result = evDone.receive(self) + finally: + ov.die() + if result: + botdef.stName = miBotName.Value() + botdef.fPlayer = miMenu.MiByName("Is Player").Value() + if fNewBot: + self.game.drawing.defs.rgbotdef.append(botdef) + + + def draw(self, ascr, client): if self.FDrawCursor(): if client == self.client: @@ -475,7 +553,7 @@ class Cursor(TokenClient): chCursor = chr(177) self.Put(chCursor, ascr, *self.XzYzScreenPic()) elif self.fBlink: - self.Put(str(self.client.Cldg().iclient)[0], ascr, *self.XzYzScreenPic()) + self.Put(str(self.client.Cldg(self.game).iclient)[0], ascr, *self.XzYzScreenPic()) class CursorSprite(Cursor): def InitPersistent(self, game, client): @@ -492,7 +570,9 @@ class CursorSprite(Cursor): return MiMenu(Rgseq([None], self.rgmiLayer, [None, MiButton("New Frame", self.NewLayer), MiButton("Move up", self.LayerUp), MiButton("Move down", self.LayerDown), None, MiStatic("Rename selected frame:"), MiTypein(self.RenameLayer, "LayerName", 80), None, MiButton("Animate", self.Animate), None, miQuit, None])) + def StStatus(self): return "Hit for menu" def StLayerTab(self): return "Frames" + def NewLayer(self): stLayer = "Frame " + str(len(self.game.drawing.rglayer) + 1) layer = self.GetLayer() @@ -521,10 +601,14 @@ class WhiteboardDraw(TokenClient): def draw(self, ascr, client): if client == self.client: + fDrawObjects = True if self.owner.miLayers.Value("Show One"): ascr.PutAscr(self.owner.GetLayer().ascr) + fDrawObjects = self.owner.GetLayer() == self.owner.layerObj else: self.game.drawing.draw(ascr) + if fDrawObjects: + self.game.drawing.DrawObjectDefs(ascr) class SpriteDraw(TokenClient): def InitPersistent(self, owner, client): @@ -671,10 +755,10 @@ class Joiner(LeaveJoinToken): self.board = self.game.drawing def OnJoin(self, client): - with client.Cldg().SetTransiently() as cldg: + with client.Cldg(self.game).SetTransiently() as cldg: cldg.iclient = -1 iclient = 1 - rgiclient = [clientT.Cldg().iclient for clientT in self.game.rgclient] + rgiclient = [clientT.Cldg(self.game).iclient for clientT in self.game.RgclientConnected()] while iclient in rgiclient: iclient = iclient + 1 cldg.iclient = iclient @@ -813,7 +897,29 @@ class GameLogin(Game): def GetRgclsTokTrans(self): return [[AutoJoiner, login.LoginTerm]] -@Version(6) +class Mpsubgame(TPrs): + def InitPersistent(self, game, dgMakeGame): + self.game = game + self.dgMakeGame = dgMakeGame + self.mpkey_subgame = {} + + def SubgameFromKey(self, key): + if not (key in self.mpkey_subgame): + subgame = self.dgMakeGame(key) + subgame.rgdgdie.append(Partial(self.KillSubgame, key)) + self.mpkey_subgame[key] = subgame + return self.mpkey_subgame[key] + + def KillSubgame(self, key): + del self.mpkey_subgame[key] + + def __contains__(self, key): + return key in self.mpkey_subgame + + def __getitem__(self, key): + return self.mpkey_subgame[key] + +@Version(7) class GameLobby(Game): def InitPersistent(self): self.rgproject = [] @@ -821,30 +927,37 @@ class GameLobby(Game): def InitTransient(self): Game.InitTransient(self) - self.mpdrawing_game = {} + self.mpdrawing_game = Mpsubgame(self, self.MakeGameFromDrawing) + self.mpdrawingsynt_game = Mpsubgame(self, self.MakeGameSyntEdit) def GetRgclsTokTrans(self): return [[AutoJoiner, Lobby]] def GameFromDrawing(self, drawing): - if not (drawing in self.mpdrawing_game): - if drawing.StType() == "Board": - game = GameWB(self, drawing) - elif drawing.StType() == "Sprite": - game = GameSprEdit(self, drawing) - else: - assert False, "Lobby not prepared to deal with drawing of type " + drawing.StType() - game.rgdgdie.append(Partial(self.KillBoard, drawing)) - self.mpdrawing_game[drawing] = game - return game - return self.mpdrawing_game[drawing] + return self.mpdrawing_game.SubgameFromKey(drawing) + def MakeGameFromDrawing(self, drawing): + if drawing.StType() == "Board": + return GameWB(self, drawing) + elif drawing.StType() == "Sprite": + return GameSprEdit(self, drawing) + else: + assert False, "Lobby not prepared to deal with drawing of type " + drawing.StType() + + # we accidentally persisted references to this method that are immediately removed by Chat.InitTransient() def KillBoard(self, drawing): - del self.mpdrawing_game[drawing] + pass + + def GameFromSynt(self, drawing, synt): + return self.mpdrawingsynt_game.SubgameFromKey((drawing, synt)) + + def MakeGameSyntEdit(self, drawing_synt): + drawing, synt = drawing_synt + return GameSyntEdit(drawing, synt) def CclientDrawing(self, drawing): if drawing in self.mpdrawing_game: - return len(self.mpdrawing_game[drawing].rgclient) + return len(self.mpdrawing_game[drawing].RgclientConnected()) return 0 def StNameProject(self, project): @@ -883,7 +996,12 @@ class GameLobby(Game): project.rgdrawing.append(board) del self.rgboard self.rgproject = [project] - self.mpuser_cldg = {} # ugh, upgrading parent classes is broken in our model :( + + # ugh, upgrading parent classes is broken in our model :( + if versionOld < 6: + Game.UpgradeFrom(self, 1) + elif versionOld < 7: + Game.UpgradeFrom(self, 2) class GameWB(Game): def InitPersistent(self, gameLobby, board): @@ -905,6 +1023,18 @@ class GameWB(Game): def GetRgtagDraw(self): return ["background", "solid", "cursor", "menu", "msgscroller"] + + def GameSyntEdit(self, synt): + return self.gameLobby.GameFromSynt(self.drawing, synt) + +class GameSyntEdit(Game): + def InitPersistent(self, board, synt): + Game.InitPersistent(self) + self.board = board + self.synt = synt + + def GetRgclsTokTrans(self): + return [[PselState, self.board.defs, self.synt], [AutoJoiner, [Pov, self.board.defs, self.synt]]] class GameSprEdit(GameWB): def ClsCursor(self):