from engine import * from basetoken import * from tpers import * from util import * # scope -- a block? a scril? (scril lets you specifically avoid vars that are defined after a certain point) + game? # method: rgvar(stype) -- returns a list of variables in scope that match the current stype # var -- a variable # contains: stype, sargInitializer # a var IS A SCRIL # the scmd creates a new vari in the current ectx, and adds the scril to a map of things that refer to it # sproj -- an object that can project a sarg into an pw # stypes should be sprojectors and express a default projection as a class method (why?) # MiSarg -- an editor for sargs # MiSargNum, MiSargDrop # MiScriptLine -- projects a scriptline # sobj -- generic hungarian for any object that is usable in scripts # fail -- a FAILURE OBJECT; if it is used as a sarg when evaluating an scmd, a failure is logged and the scmd returns fail. # execution: # ectx -- execution context # contains: mpvar_sobj # block # iscril (selection?) # ectxReturn # methods: call(block) class Block(TPrs): def InitPersistent(self, scrilParent): self.rgscril = [] self.scrilParent = scrilParent def InsertScrilAfter(self, scrilAfter, scmd = None): scrilNew = Scril(self, scmd) if scrilAfter == None: self.rgscril.insert(0, scrilNew) else: self.rgscril.insert(self.rgscril.index(scrilAfter) + 1, scrilNew) return scrilNew def Project(self, pwBlock, dxIndent = 0): if pwBlock == None: pwBlock = PwBlock(None, self, 0) PwButtonHidden(pwBlock, "[insert new line]", None, self.InsertScrilAfter, dxIndent) for scril in self.rgscril: scril.Project(pwBlock, dxIndent) return pwBlock class Typeable(TPrs): def InitTransient(self): self.steditTypein = Stedit() def GetStTypein(self): st = self.steditTypein.GetValue() return None if st == "" else st def SetStTypein(self, stTypein): self.steditTypein.SetValue(stTypein or "") def HandleTypeinKey(self, key): return self.steditTypein.HandleKey(key) # Sarg -- pointer to scripting object / argument to a command # this object type appears to exist solely so that MiSarg can edit something without digging through a list class Sarg(Typeable): def InitPersistent(self, stype, scril): self.stype = stype self.sobj = stype.SobjDefault(scril) def FExpr(self): "Returns true if the sobj must be evaluated to get a result." return self.sobj.__class__ == Scril def Eval(self, ectx): if self.FExpr(): return self.sobj.Exec(ectx) else: return self.sobj class Fail(TPrs): def InitPersistent(self, stFailure): self.stFailure == stFailure # stype -- a description of a sobj (scripting object) # method: FMember(sobj) -- returns true if an object is in the set of things that this stype represents # method: FOverlaps(stype) -- returns true if there is some overlap between the two stypes # -- there is no subtype/supertype relation because stypes are used to help the user, # and I suspect being specific in this manner is too much of a burden to place on them. # methods: rgsarg(scope) -- returns a list of all legal objects that exist in the current scope # this includes functions which return things of this stype, and variables # literals are not included # project(cls, game, sarg) -- returns an MiSarg which displays and edits (default sproj for a sarg?) class Stype(object): """A definition of a scripting object type. All methods are class methods; stypes are never instantiated.""" def __init__(self): raise "do not instantiate stypes" @classmethod def FMember(cls, sobj): return isinstance(sobj, cls.pythontype) @classmethod def FOverlaps(cls, stype): return stype == self @classmethod def Rgsobj(cls, scope): return [] @classmethod def Rgsobj_st(cls, scope): return [(sobj, str(sobj)) for sobj in cls.Rgsobj(scope)] @classmethod def OnSelect(cls, sarg, sobj): sarg.sobj = sobj @classmethod def Sproj(cls, sarg, pwBlock, pwScril): ProjectTypein(pwScril, sarg, str(sarg.sobj), cls.Rgsobj_st, cls.OnSelect) return False def ProjectTypein(pwParent, typeable, st, dgRgsobj_st, dgOnSelect): def OnSelect(sobj): dgOnSelect(typeable, sobj) typeable.SetStTypein(None) if typeable.GetStTypein() != None: pwParent = PwDropdown(pwParent) PwTypein(pwParent, st, typeable) if typeable.GetStTypein() != None: for sobj, st in dgRgsobj_st(typeable): PwButton(pwParent, st, sobj, OnSelect) class StypeBool(Stype): pythontype = bool @classmethod def SobjDefault(cls, scril): return False @classmethod def Rgsobj(cls, scope): return [True, False] class StypeBlock(Stype): pythontype = Block @classmethod def SobjDefault(cls, scril): return Block(scril) @classmethod def Sproj(cls, sarg, pwBlock, pwScril): sarg.sobj.Project(pwBlock, pwScril.DxIndent() + 2) return True # scmd -- a definition of a scripting command class Scmd(object): """A definition of a scripting command. All methods are class methods; scmds are never instantiated.""" def __init__(self): raise "do not instantiate scmds" # used by RegScmd to build a list of all valid scmds for editing rgscmd = [] @classmethod def Rgscmd_st(cls, scril): return [(scmd, scmd.StName()) for scmd in cls.rgscmd] @classmethod def Desc(cls, *rgsarg): """ Returns a list of desces. A desce can be a constant string, representing nonselectable helper text, an sproj, a list containing an sproj and a reference to a passed-in sarg, which explicitly binds that sproj to that sarg, or a list containing a string, which stands for the selectable text which represents the scril itself. ie, [["Move"], " ", [SprojMovable, rgsarg[1]], " to ", [SprojPosition, rgsarg[0]]] If a bare sproj is given, bind it to the first unbound sarg (after processing all explicitly bound sargs). This method must accept invalid sargs; it is the sproj's job to relay the invalidness to the user. """ return cls.desc stName = None @classmethod def StName(cls): "Returns the name that the user types in to select this scmd." if cls.stName != None: return cls.stName for desce in cls.Desc(Scril(None, cls).rgsarg): if type(desce) == list and type(desce[0]) == str: return desce[0] assert False, "no selectable self in desc" @classmethod def Csarg(cls): "Returns the number of arguments that this command takes." return len(cls.rgstypeArg) @classmethod def StypeResult(cls): return cls.stypeResult @classmethod def RgstypeArg(cls): return cls.rgstypeArg def RegScmd(stypeResult, *rgstypeArg): def RegI(cls): cls.stypeResult = stypeResult cls.rgstypeArg = rgstypeArg Scmd.rgscmd.append(cls) return cls return RegI @RegScmd(None) class ScmdBlank(Scmd): stName = "" desc = [[" "]] @classmethod def Exec(cls, ectx): pass @RegScmd(None) class ScmdHelloWorld(Scmd): "Hello, world!" desc = [["Hello World"], "!"] @classmethod def Exec(cls, ectx): print "Hello, world!" @RegScmd(None, StypeBool, StypeBlock, StypeBlock) class ScmdIf(Scmd): desc = [["If"], " ", StypeBool.Sproj, ", then:", StypeBlock.Sproj, "or else:", StypeBlock.Sproj] @classmethod def Exec(cls, ectx, sargExpr, sargIfTrue, sargIfFalse): if sargExpr.Eval(ectx): print "true!" # needs work #=========================================================================== # @RegScmd(None, StypeSt, StypeObj) # class ScmdVar(Scmd): # desc = ["Define a new ", ["variable"], " called \"", StypeSt, "\", set to ", StypeObj, "."] # @classmethod # def Exec(cls, ectx, sargStName, sargInit): # ectx.mpvar_sobj[ectx.ScrilCurr()] = sargInit.Eval() #=========================================================================== class Scril(Typeable): "A line in a script." def InitPersistent(self, blockParent, scmd = None): self.scmd = scmd or ScmdBlank self.rgsarg = [Sarg(stypeArg, self) for stypeArg in self.scmd.RgstypeArg()] self.blockParent = blockParent def SetScmd(self, scmd): if (scmd.Csarg() > self.scmd.Csarg()): self.rgsarg.extend([Sarg(stypeArg, self) for stypeArg in scmd.RgstypeArg()[self.scmd.Csarg():]]) elif (scmd.Csarg() < self.scmd.Csarg()): self.rgsarg = self.rgsarg[:scmd.Csarg()] self.scmd = scmd def Exec(self, ectx): return self.scmd.Exec(ectx, *self.rgsarg) def HandleKey(self, pwKey, pov, psel, key): if ansi.FEnter(key): self.blockParent.InsertScrilAfter(self, ScmdBlank) return True return False def Project(self, pwBlock, dxindent): "adds horizontal pws to a vertical pw" pwScril = None desc = self.scmd.Desc(*self.rgsarg) rgsargUnbound = list(self.rgsarg) #filter bound sargs for desce in desc: if isinstance(desce, list) and len(desce) > 1: rgsargUnbound.remove(desce[1]) fNewScril = True for desce in desc: if fNewScril: pwKey = PwKeyHandler(pwBlock, self.HandleKey) pwScril = PwList(pwKey, self, dxindent) fNewScril = False if isinstance(desce, str): PwStatic(pwScril, desce) elif isinstance(desce, list): if isinstance(desce[0], str): # selection for the scmd / scril ProjectTypein(pwScril, self, desce[0], Scmd.Rgscmd_st, Scril.SetScmd) else: # mapping to sarg fNewScril = self.ProjectSarg(pwBlock, pwScril, desce[0], desce[1]) else: # sproj fNewScril = self.ProjectSarg(pwBlock, pwScril, desce, rgsargUnbound.pop(0)) def ProjectSarg(self, pwBlock, pwScril, sproj, sarg): return sproj(sarg, pwBlock, pwScril) # pw - projected widget # Nothing ever depends on the object identity or permanence of a pw! # They are generated on demand, whenever we need to draw or process a # keypress. class Pw(object): def __init__(self, pwParent): if pwParent != None: pwParent.AddChild(self) self.pwParent = pwParent def PwParent(self): return self.pwParent def Value(self): return None def FSelectable(self): return False def FContainer(self): return False def RgpwChild(self): return [] def HandleKey(self, pov, psel, key): return False def PwFirstSelectable(self): if self.FSelectable(): return self for pwChild in self.RgpwChild(): pw = pwChild.PwFirstSelectable() if pw != None: return pw return None class PwContainer(Pw): def __init__(self, pwParent): Pw.__init__(self, pwParent) self.rgpwContents = [] def AddChild(self, pw): self.rgpwContents.append(pw) def RgpwChild(self): return self.rgpwContents def FContainer(self): return True class PwStatic(Pw): def __init__(self, pwParent, stText): Pw.__init__(self, pwParent) self.stText = stText def DxDyNew(self, w, dxStart, mpksel): rgsnippet = self.Rgsnippet(w, dxStart, mpksel) if len(rgsnippet) == 1: return (dxStart + len(rgsnippet[0].st), 0) else: return (len(rgsnippet[-1].st), len(rgsnippet) - 1) def StTextDisplay(self, ksel): return self.stText def Rgsnippet(self, w, dxStart, mpksel): ksel = mpksel.Get(self) return list(RgSnippetWrapped(self.StTextDisplay(ksel), w, ksel == Ksel.NAV, dxStart)) def ColBg(self, ksel): return ansi.BLACK def ColFg(self): return ansi.WHITE | ansi.FBRIGHT def RgSnippetXY(self, x, y, w, dxStart, mpksel): fFirst = True for snippet in self.Rgsnippet(w, dxStart, mpksel): xT = x + dxStart if fFirst else x yield (snippet, xT, y) y = y + 1 fFirst = False def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): if not fOverlay: ksel = mpksel.Get(self) for (snippet, x, y) in self.RgSnippetXY(x, y, w, dxStart, mpksel): ascr.PutSt(snippet.st, x, y, self.ColFg(), self.ColBg(ksel)) class PwTypein(PwStatic): def __init__(self, pwParent, stSobj, typeable): if typeable.GetStTypein() == None: stDisplay = stSobj else: stDisplay = None PwStatic.__init__(self, pwParent, stDisplay) self.typeable = typeable def Value(self): return self.typeable def StTextDisplay(self, ksel): if self.stText == None: return self.typeable.steditTypein.StForSize(ksel == Ksel.NAV) return self.stText def FSelectable(self): return True def ColBg(self, ksel): if ksel == Ksel.NAV: return ansi.YELLOW elif ksel == Ksel.OTHERNAV: return ansi.MAGENTA return ansi.BLUE def HandleKey(self, pov, psel, key): return self.typeable.HandleTypeinKey(key) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): if not fOverlay: ksel = mpksel.Get(self) fTextSelect = ksel == Ksel.NAV and self.typeable.GetStTypein() != None for (snippet, x, y) in self.RgSnippetXY(x, y, w, dxStart, mpksel): ascr.PutSt(snippet.st, x, y, self.ColFg(), self.ColBg(Ksel.NONE if fTextSelect else ksel)) if fTextSelect: self.typeable.steditTypein.DrawCursor(ascr, x, y, self.ColFg(), self.ColBg(ksel), snippet) else: for pwChild in self.RgpwChild(): pwChild.Draw(ascr, x, y, dxStart, mpksel, fOverlay) class PwButton(PwStatic): def __init__(self, pwParent, stText, value, dgEnter): PwStatic.__init__(self, pwParent, stText) self.value = value self.dgEnter = dgEnter def Value(self): return self.value def FSelectable(self): return True def ColBg(self, ksel): if ksel == Ksel.NAV: return ansi.YELLOW elif ksel == Ksel.OTHERNAV: return ansi.MAGENTA return ansi.BLUE def ColFg(self): return ansi.BLUE | ansi.FBRIGHT def HandleKey(self, pov, psel, key): if key == ansi.K_RETURN: self.dgEnter(self.Value()) return True return False class PwButtonHidden(PwButton): def __init__(self, pwParent, stText, value, dgEnter, dxIndent): PwButton.__init__(self, pwParent, stText, value, dgEnter) self.dxIndent = dxIndent def DxDyNew(self, w, dxStart, mpksel): if mpksel.Get(self) == Ksel.NAV: return PwButton.DxDyNew(self, w - self.dxIndent, dxStart + self.dxIndent, mpksel) return (0, 0) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): if mpksel.Get(self) == Ksel.NAV: PwButton.Draw(self, ascr, x + self.dxIndent, y, w - self.dxIndent, dxStart, mpksel, fOverlay) class PwKeyHandler(PwContainer): def __init__(self, pwParent, dgHandleKey): PwContainer.__init__(self, pwParent) self.dgHandleKey = dgHandleKey def DxDyNew(self, w, dxStart, mpksel): return self.RgpwChild()[0].DxDyNew(w, dxStart, mpksel) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): self.RgpwChild()[0].Draw(ascr, x, y, w, dxStart, mpksel, fOverlay) def HandleKey(self, pov, psel, key): self.dgHandleKey(self, pov, psel, key) class PwBlock(PwContainer): def __init__(self, pwParent, block, dxIndent = 2): PwContainer.__init__(self, pwParent) self.block = block self.dxIndent = dxIndent def Value(self): return self.block def DxIndent(self): return self.dxIndent def DxDyNew(self, w, dxStart, mpksel): return (0, 1 + sum([pw.DxDyNew(w - self.DxIndent(), 0, mpksel)[1] + 1 for pw in self.rgpwContents])) def RgpwChildToDraw(self): return self.RgpwChild() def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): for pw in self.RgpwChildToDraw(): pw.Draw(ascr, x + self.DxIndent(), y, w - self.DxIndent(), 0, mpksel, fOverlay) (dxNew, dyNew) = pw.DxDyNew(w - self.DxIndent(), 0, mpksel) if dxNew > 0 or dyNew > 0: y = y + dyNew + 1 def HandleKey(self, pov, psel, key): if key == ansi.K_UP: return psel.Dec(self) elif key == ansi.K_DOWN: return psel.Inc(self) elif key == ansi.K_DEL: pass return False class PwDropdown(PwBlock): def __init__(self, pwParent): PwBlock.__init__(self, pwParent, None, 0) def WidthMax(self, mpksel): w = -1 for pw in self.RgpwChild(): wPw = pw.DxDyNew(1000000, 0, mpksel)[0] if wPw > w: w = wPw return w def RgpwChildToDraw(self): return PwBlock.RgpwChild(self)[1:] def PwChildFirst(self): return PwBlock.RgpwChild(self)[0] def DxDyNew(self, w, dxStart, mpksel): return self.PwChildFirst().DxDyNew(w, dxStart, mpksel) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): if not fOverlay: self.PwChildFirst().Draw(ascr, x, y, w, dxStart, mpksel, False) else: wMax = self.WidthMax(mpksel) w = w - dxStart x = x + dxStart if x + wMax > w and wMax < w: x = w - self.WidthMax(mpksel) PwBlock.Draw(self, ascr, x, y + 1, w, 0, mpksel, False) class PwList(PwContainer): def __init__(self, pwParent, scril, dxIndent): PwContainer.__init__(self, pwParent) self.scril = scril self.dxIndent = dxIndent def Value(self): return self.scril def DxDyNew(self, w, dxStart, mpksel): dx = dxStart dy = 0 for pw in self.rgpwContents: (dx, dyPw) = pw.DxDyNew(w, dx, mpksel) dy = dy + dyPw return (dx, dy) def DxIndent(self): return self.dxIndent def Draw(self, ascr, x, y, w, dx, mpksel, fOverlay): for pw in self.rgpwContents: pw.Draw(ascr, x + self.DxIndent(), y, w - self.DxIndent(), dx, mpksel, fOverlay) (dx, dy) = pw.DxDyNew(w, dx, mpksel) y = y + dy def HandleKey(self, pov, psel, key): if key == ansi.K_LEFT: return psel.Dec(self) elif key == ansi.K_RIGHT: return psel.Inc(self) return False class Ksel(object): FOTHER = 0x2000 NONE = FOTHER | 0x1000 # OR together all ksel flags and add 0x1000; largest possible ksel NAV = 0 OTHERNAV = NAV | FOTHER @staticmethod def KselBest(ksel1, ksel2): if ksel1 < ksel2: return ksel1 return ksel2 class Psel(TPrs): def InitPersistent(self, pw, ksel): assert len(pw.RgpwChild()) == 0 #leaf self.rgo_ipw = self.Rgo_ipwFromPw(pw) self.ksel = ksel def Rgo_ipwFromPw(self, pw): rgo_ipw = [] if pw != None: while pw.PwParent() != None: rgo_ipw.insert(0, (pw.Value(), pw.PwParent().RgpwChild().index(pw))) pw = pw.PwParent() return rgo_ipw def Validate(self, pwRoot): assert pwRoot.PwParent() == None #root io_ipw = 0 pwSelected = pwRoot while len(pwSelected.RgpwChild()) > 0 and io_ipw < len(self.rgo_ipw): (o, ipw) = self.rgo_ipw[io_ipw] ipwNew = 0 for pwChild in pwSelected.RgpwChild(): if o != None and pwChild.Value() == o: break ipwNew = ipwNew + 1 if ipwNew == len(pwSelected.RgpwChild()): ipwLim = ipw + 1 if ipw < len(pwSelected.RgpwChild()) else len(pwSelected.RgpwChild()) ipwNew = IoDec(pwSelected.RgpwChild(), ipwLim, self.FPwValid) if ipwNew < 0: break pwSelected = pwSelected.RgpwChild()[ipwNew] io_ipw = io_ipw + 1 # we've made our best guess as to what the closest selectable thing is -- now make sure we point at SOMEthing selectable. if not pwSelected.FSelectable(): pwT = pwSelected while pwT != None: pwSelected = pwT.PwFirstSelectable() if pwSelected != None: break pwT = pwT.PwParent() self.rgo_ipw = self.Rgo_ipwFromPw(pwSelected) def FPwValid(self, pw): if pw.FSelectable(): return True for pwChild in pw.RgpwChild(): if self.FPwValid(pwChild): return True return False def Value(self, pwRoot): self.Validate(pwRoot) if len(self.rgo_ipw) > 0: return self.rgo_ipw[-1][0] return None def PwSelected(self, pwRoot, clevel = None): self.Validate(pwRoot) return self.PwSelectedI(pwRoot, self.rgo_ipw[:clevel]) def PwSelectedI(self, pw, rgo_ipw): for (_, ipw) in rgo_ipw: pw = pw.RgpwChild()[ipw] return pw # contract: pwContainer is actually selected def Inc(self, pwContainer): return self.ChangeI(pwContainer, IoInc) def Dec(self, pwContainer): return self.ChangeI(pwContainer, IoDec) def ChangeI(self, pwContainer, fnChange): clevelChild = 1 pwRoot = pwContainer while pwRoot.PwParent() != None: clevelChild = clevelChild + 1 pwRoot = pwRoot.PwParent() self.Validate(pwRoot) assert self.PwSelected(pwRoot, clevelChild).PwParent() == pwContainer ipw = self.rgo_ipw[clevelChild - 1][1] rgpw = pwContainer.RgpwChild() ipwNew = fnChange(rgpw, ipw, self.FPwValid) self.rgo_ipw[clevelChild - 1] = (rgpw[ipwNew].Value(), ipwNew) self.Validate(pwRoot) return ipwNew != ipw # projector overlay -- handles navigation, drawing the projection, etc class Pov(TokenClient): def InitPersistent(self, owner, client, block): TokenClient.InitPersistent(self, owner, client, "drawable", "overlay") self.block = block self.pselstate = self.game.rgtoken("pselstate")[0] def PwProjected(self): return self.block.Project(None) def run(self): while True: key = self.client.evKey.receive(self) psel = self.pselstate.PselByClient(self.client) pwSel = psel.PwSelected(self.PwProjected()) while pwSel != None: if pwSel.HandleKey(self, psel, key): break pwSel = pwSel.PwParent() def draw(self, ascr, client): if client == self.client: pw = self.PwProjected() mpksel = self.pselstate.GetMpksel(pw, client) pw.Draw(ascr, 1, 1, ascr.W(), 0, mpksel, False) pw.Draw(ascr, 1, 1, ascr.W(), 0, mpksel, True) class PselState(Token): def InitPersistent(self, owner, block): Token.InitPersistent(self, owner, "pselstate") self.block = block def InitTransient(self): Token.InitTransient(self) self.mpclient_psel = {} def OnLeave(self, client): del self.mpclient_psel[client] def OnJoin(self, client): print "client joined,", client self.mpclient_psel[client] = Psel(self.block.Project(None).PwFirstSelectable(), Ksel.NAV) class Mpksel(object): def __init__(self, pwRoot, client, mpclient_psel): self.mppw_ksel = {} for clientT, psel in mpclient_psel.items(): kselNew = Ksel.NAV if clientT != client: kselNew = kselNew | Ksel.FOTHER pw = psel.PwSelected(pwRoot) self.mppw_ksel[pw] = Ksel.KselBest(kselNew, self.Get(pw)) def Get(self, pw): return self.mppw_ksel.get(pw, Ksel.NONE) def GetMpksel(self, pwRoot, client): return self.Mpksel(pwRoot, client, self.mpclient_psel) def PselByClient(self, client): return self.mpclient_psel[client] def run(self): print "pselstate running" with self.game.evLeave.oob(self, self.OnLeave): while True: self.OnJoin(self.game.evJoin.receive(self)) class GameScriptTest(Game): def InitPersistent(self): Game.InitPersistent(self) self.block = Block(None) self.block.InsertScrilAfter(None, ScmdHelloWorld) self.block.InsertScrilAfter(None, ScmdBlank) self.block.InsertScrilAfter(None, ScmdHelloWorld) self.block.InsertScrilAfter(None, ScmdIf) self.block.InsertScrilAfter(None, ScmdHelloWorld) def GetRgclsTokTrans(self): return [[PselState, self.block], [AutoJoiner, [Pov, self.block]]] class RunnerScriptTest(Runner): def InitPersistent(self): self.game = GameScriptTest() Runner.InitPersistent(self) def RunGame(self, client): client.joinGame(self.game) if __name__ == "__main__": Run(RunnerScriptTest, "script_test.marm")