from engine import * from basetoken import * from tpers import * from util import * from contextlib import contextmanager from vm import * # sobj -- generic hungarian for any object that is usable in scripts # stmt -- statement; a synt that can be used as a line in a SyntBlock def RegStmt(clsStmt): SyntLine.rgclsStmt.append(clsStmt) return clsStmt def RegRtype(clsRtype): Rtype.rgclsRtype.append(clsRtype) return clsRtype class Rtype(TPrs): "Runtime type" rgclsRtype = [] @classmethod def RtypeMax(cls): "Return an instance of this rtype which will match anything that any instance of this rtype matches." return cls() def RglitFromSt(self, st): "Return literal objects that match a (partially entered) string." return [] def RgsyntFromSt(self, syntBlock, st): "Return syntax objects whose rtypes match this that match a (partially entered) string." for lit in self.RglitFromSt(st): yield SyntLit(lit, self) for synt in self.RgsyntVarRefFromSt(syntBlock, st): yield synt def RgsyntVarRefFromSt(self, syntBlock, st): "Return variable references that match a (partially entered) string." while syntBlock != None: if isinstance(syntBlock, SyntBlock): for synt in self.RgsyntFromSyntBlock(syntBlock, st): yield synt syntBlock = syntBlock.syntParent # helper for RgsyntVarRefFromSt def RgsyntFromSyntBlock(self, syntBlock, st): # yield all vars in scope for syntVar in syntBlock.rgsynt: if SyntVar.FVarOfType(syntVar, self) and syntVar.name.St().lower().startswith(st.lower()): yield SyntVarRef(syntVar) def StForSobj(self, sobj): "Return a user-visible description of a sobj." return str(sobj) def FMember(self, sobj): "Returns true if sobj is a member of this type." return isinstance(sobj, self.pythontype) def FOverlap(self, rtype): "Returns true if there is some overlap between this type and rtype." return isinstance(rtype, self.__class__) @RegRtype class RtypeBool(Rtype): pythontype = bool def RglitFromSt(self, st): if "true".startswith(st.lower()): yield True if "false".startswith(st.lower()): yield False @RegRtype class RtypeNum(Rtype): pythontype = float def RglitFromSt(self, st): try: yield float(st) except: pass @RegRtype class RtypeString(Rtype): pythontype = str def RglitFromSt(self, st): if st.startswith('"') or st.startswith("'"): yield st[1:] class RtypeAny(Rtype): def RgsyntFromSt(self, syntBlock, st): for clsRtype in self.rgclsRtype: rtype = clsRtype.RtypeMax() for synt in rtype.RgsyntFromSt(syntBlock, st): yield synt def FMember(self, sobj): return True def FOverlap(self, rtype): return True class Typeable(TPrs): def InitTransient(self): self.steditTypein = Stedit() def PersistStedit(self): assert self.FPersist() try: del self.steditTypein except: pass 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) class Synt(Typeable): "A piece of syntax" def InitPersistent(self): self.syntParent = None self.rgsynt = [] self.Populate() @classmethod def SyntDefault(cls): return cls() @classmethod def RgsyntReplace(cls, syntChild, st, rtype): return [] def ProjectTypeinForChild(self, pwParent, syntChild, st): def OnSelect(synt, psel): self.Replace(syntChild, synt) syntChild.SetStTypein(None) if syntChild.GetStTypein() != None: pwParent = PwDropdown(pwParent) PwTypein(pwParent, st, syntChild) if syntChild.GetStTypein() != None: for synt in self.RgsyntForChild(syntChild, syntChild.GetStTypein()): PwButton(pwParent, synt.StForTypein(), synt, OnSelect) def RgsyntForChild(self, syntChild, st): return [] def Replace(self, syntOld, syntNew): assert syntNew.syntParent == None syntNew.syntParent = self self.rgsynt[self.Isynt(syntOld)] = syntNew def Append(self, syntNew): assert syntNew.syntParent == None syntNew.syntParent = self self.rgsynt.append(syntNew) def Isynt(self, syntChild): if isinstance(syntChild, int): return syntChild return self.rgsynt.index(syntChild) def Rtype(self): return None def RtypeTopForChild(self, syntChild): return None def Populate(self): "Populate rgsynt with useful default values." pass def Project(self, pcur): "Project the current synt into its corresponding pws." pass def Compile(self): "Return a list of instrs to be interpreted. See vm.py for details on what an instr can be." return [self.Eval] # desc - list of desces # desce: # string: "foo" -- literal uneditable text # list: ["foo"] -- text to edit the current synt # tuple: (clsSynt, type) -- child description # clsSynt: class of child synt # type: number - same as another child expression with that number # None - no rtype specified / possible # tuple (number, rtypeTop) - number == same as another child expression with that number, rtypeTop = required type class SyntDesc(Synt): @classmethod def StForTypein(cls): for desce in cls.Desc(): if isinstance(desce, list) and len(desce) == 1: return desce[0] @staticmethod def DefProp(isynt): "Create an alias for rgsynt[isynt]." def getI(self): return self.rgsynt[isynt] def setI(self, syntNew): self.Replace(isynt, syntNew) return property(getI, setI) @classmethod def Desc(cls): return cls.desc def Populate(self): for desce in self.Desc(): if isinstance(desce, tuple): synt = desce[0].SyntDefault() self.Append(synt) # for synt in self.rgsynt: # synt.Populate() def DesceChild(self, isyntChild): isyntDesce = 0; for desce in self.Desc(): if isinstance(desce, tuple): if isyntDesce == isyntChild: return desce isyntDesce += 1 return None def RtypeTopForChild(self, syntChild): isyntChild = self.Isynt(syntChild) desce = self.DesceChild(isyntChild) if desce != None and isinstance(desce[1], tuple): return desce[1][0] return None def RgsyntForChild(self, syntChild, st): isyntChild = self.Isynt(syntChild) desce = self.DesceChild(isyntChild) if desce != None: rtype = None if isinstance(desce[1], tuple): rtype = desce[1][1] return desce[0].RgsyntReplace(syntChild, st, rtype) return [] def Project(self, pcur): "adds more horizontal pws to a horizontal pw" isynt = 0 for desce in self.Desc(): if isinstance(desce, str): PwStatic(pcur.PwHoriz(self), desce) elif isinstance(desce, list): # selection for the synt itself self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, desce[0]) elif isinstance(desce, tuple): # embedded syntax self.rgsynt[isynt].Project(pcur) isynt += 1 class SyntName(Synt): def InitPersistent(self): Synt.InitPersistent(self) self.PersistStedit() def Project(self, pcur): PwTypein(pcur.PwHoriz(self), self.St(), self) def St(self): return self.GetStTypein() class SyntExpr(Synt): rgclsSyntOp = [] @classmethod def RgsyntReplace(cls, syntChild, st, rtype): for synt in RtypeAny().RgsyntFromSt(syntChild.syntParent, st): yield synt for clsSyntOp in cls.rgclsSyntOp: if clsSyntOp.StForTypein().lower().startswith(st.lower()): yield clsSyntOp.SyntDefault() def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ") def Eval(self, vm): Fail.Push(vm, "Empty expression", self) class SyntLine(SyntDesc): rgclsStmt = [] desc = [[" "]] @classmethod def RgsyntReplace(cls, syntChild, st, rtype): for clsStmt in cls.rgclsStmt: if clsStmt.StForTypein().lower().startswith(st.lower()): yield clsStmt() def Compile(self): return [] class SyntBlock(Synt): def Project(self, pcur): with pcur.Indent(2 if pcur.pwHoriz != None else 0): pcur.EndLine() if pcur.pwVert == None: pcur.pwVert = PwBlock(None, self, pcur.dxindent) def OnInsertNewLine(syntAfter, psel): self.InsertLineAfter(syntAfter, None) psel.Inc(pcur.pwVert) PwButtonHidden(pcur.pwVert, "[insert new line]", None, OnInsertNewLine, pcur.dxindent) for syntLine in self.rgsynt: pwKey = PwKeyHandler(pcur.pwVert, self.HandleKey) with pcur.ProjectHoriz(pwKey, syntLine): syntLine.Project(pcur) def RgsyntForChild(self, syntChild, st): return SyntLine.RgsyntReplace(syntChild, st, None) def InsertLineAfter(self, syntAfter, syntStmt): if syntStmt == None: syntStmt = SyntLine() if syntAfter == None: self.rgsynt.insert(0, syntStmt) else: self.rgsynt.insert(self.rgsynt.index(syntAfter) + 1, syntStmt) syntStmt.syntParent = self return syntStmt def HandleKey(self, pwKey, pov, psel, key): if ansi.FEnter(key): self.InsertLineAfter(pwKey.pwChild.Value(), None) psel.Inc(pwKey.PwParent()) return True return False def Compile(self): return self.rgsynt class SyntLit(Synt): def InitPersistent(self, value, rtype): Synt.InitPersistent(self) self.value = value self.rtype = rtype def StForTypein(self): return self.rtype.StForSobj(self.value) def Rtype(self): return self.rtype def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein()) def Eval(self, vm): vm.Push(self.value) @RegStmt class SyntVar(SyntDesc): desc = [["Define"], " ", (SyntName, None), " to be ", (SyntExpr, 1)] name = SyntDesc.DefProp(0) expr = SyntDesc.DefProp(1) @classmethod def FVarOfType(cls, synt, rtype): return isinstance(synt, cls) and synt.expr != None and rtype.FOverlap(synt.expr.Rtype()) def Compile(self): return [self.expr, self.Eval] def Eval(self, vm): vm.vars.Define(self, vm.Pop()) class SyntVarRef(Synt): def InitPersistent(self, syntVar = None): Synt.InitPersistent(self) self.syntVar = syntVar @classmethod def RgsyntReplace(cls, syntChild, st, rtype): rtype = rtype or RtypeAny() return rtype.RgsyntVarRefFromSt(syntChild, st) def Rtype(self): return self.syntVar.Rtype() def StForTypein(self): if self.syntVar: return self.syntVar.name.St() return "" def Project(self, pcur): self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein()) def Eval(self, vm): vm.Push(vm.vars.Get(self.syntVar, self)) class SyntBinOp(SyntDesc): @classmethod def Desc(cls): return [(SyntExpr, None), " ", [cls.op], " ", (SyntExpr, None)] left = SyntDesc.DefProp(0) right = SyntDesc.DefProp(1) @classmethod def Create(cls, op, rtype, dgEval = None, stNameI = None): def Decorate(dgEval): stName = stNameI or dgEval.__name__ def Eval(self, vm): right = vm.Pop() left = vm.Pop() try: vm.Push(dgEval(left, right)) except Exception as e: Fail.Push(vm, "Failed to " + op + ": " + str(e), self) def Compile(self): return [self.left, self.right, self.Eval] clsNew = type(stName, (cls,), {"Compile": Compile, "Eval": Eval, "op": op}) SyntExpr.rgclsSyntOp.append(clsNew) return clsNew if dgEval == None: # used as a decorator return Decorate clsNew = Decorate(dgEval) if stNameI != None: globals()[stNameI] = clsNew return clsNew SyntBinOp.Create("+", RtypeNum(), lambda l,r: l + r, "SyntPlus") SyntBinOp.Create("-", RtypeNum(), lambda l,r: l - r, "SyntMinus") SyntBinOp.Create("*", RtypeNum(), lambda l,r: l * r, "SyntMult") SyntBinOp.Create("/", RtypeNum(), lambda l,r: l / r, "SyntDiv") # use ansi? SyntBinOp.Create("=", RtypeBool(), lambda l,r: l == r, "SyntEq") SyntBinOp.Create(">", RtypeBool(), lambda l,r: l > r, "SyntGt") SyntBinOp.Create(">=", RtypeBool(), lambda l,r: l >= r, "SyntGte") # use ansi? SyntBinOp.Create("<", RtypeBool(), lambda l,r: l < r, "SyntLt") SyntBinOp.Create("<=", RtypeBool(), lambda l,r: l <= r, "SyntLte") # use ansi? SyntBinOp.Create("and", RtypeBool(), lambda l,r: l and r, "SyntAnd") SyntBinOp.Create("or", RtypeBool(), lambda l,r: l or r, "SyntOr") @RegStmt class SyntIf(SyntDesc): desc = [["If"], " ", (SyntExpr, (1, RtypeBool)), ", then:", (SyntBlock, None), "or else:", (SyntBlock, None)] expr = SyntDesc.DefProp(0) blockIfTrue = SyntDesc.DefProp(1) blockIfFalse = SyntDesc.DefProp(2) def Compile(self): return [self.expr, self.Compare, self.PopCtx] def Compare(self, vm): val = vm.Pop() vm.vars.Push() # lexical scoping, bitches if val: return self.blockIfTrue else: return self.blockIfFalse def PopCtx(self, vm): vm.vars.Pop() @RegStmt class SyntSet(SyntDesc): desc = [["Set"], " ", (SyntVarRef, 1), " to ", (SyntExpr, 1)] lvalue = SyntDesc.DefProp(0) expr = SyntDesc.DefProp(1) def Compile(self): return [self.expr, self.Eval] def Eval(self, vm): val = vm.Pop() if not isinstance(val, Fail): vm.Log(vm.vars.Set(self.lvalue.syntVar, vm.Pop(), self)) #debug @RegStmt class SyntPrint(SyntDesc): desc = [["Print"], " ", (SyntExpr, 1)] expr = SyntDesc.DefProp(0) def Compile(self): return [self.expr, self.Eval] def Eval(self, vm): print vm.Pop() class Fail(TPrs): """ Failure object. Our scripting language has no exceptions; instead, if a failure object is involved in a calculation, that calculation fails as well. Inspired by Icon. I would like this model to have the property that the designer can 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, stFailure, synt): self.stFailure = stFailure self.synt = synt @staticmethod def Push(vm, stFailure, synt): fail = Fail(stFailure, synt) vm.Push(fail) vm.Log(fail) @staticmethod def Log(vm, stFailure, synt): vm.Log(Fail(stFailure, synt)) # projection cursor class Pcur(object): @staticmethod def PwProjected(synt): pcur = Pcur() synt.Project(pcur) return pcur.pwVert def __init__(self): self.pwVert = None self.pwHoriz = None self.dxindent = 0 @contextmanager def Indent(self, dxindent = 2): self.dxindent += dxindent yield self.dxindent -= dxindent @contextmanager def ProjectHoriz(self, pwParent, synt): self.ProjectHorizI(pwParent, synt) yield self.EndLine() def PwHoriz(self, synt): if self.pwHoriz == None: self.ProjectHorizI(self.pwVert, synt) return self.pwHoriz def EndLine(self): self.pwHoriz = None def ProjectHorizI(self, pwParent, synt): self.pwHoriz = PwList(pwParent, synt, self.dxindent) # 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 PrintTree(self, dxindent = 0): print (" " * dxindent) + self.StDebug() for pwChild in self.RgpwChild(): pwChild.PrintTree(dxindent + 2) def StDebug(self): return self.__class__.__name__ 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 PwContainOne(Pw): def AddChild(self, pw): self.pwChild = pw def RgpwChild(self): return [self.pwChild] def FContainer(self): return True class PwExpand(PwContainOne): def DxDyNew(self, w, dxStart, mpksel): dxdy = self.pwChild.DxDyNew(w, dxStart, mpksel) if dxdy[0] == 0 and dxdy[1] > 0: return dxdy return (0, dxdy[0] + 1) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): if not fOverlay: ascr.PutSt(" " * w, x, y, self.pwChild.ColFg(), self.pwChild.ColBg(mpksel.Get(self.pwChild))) self.pwChild.Draw(ascr, x, y, w, dxStart, mpksel, fOverlay) class PwStatic(Pw): def __init__(self, pwParent, stText): Pw.__init__(self, pwParent) self.stText = stText def StDebug(self): return Pw.StDebug(self) + " '" + self.StTextDisplay(Ksel.NAV) + "'" 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(), psel) 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(PwContainOne): def __init__(self, pwParent, dgHandleKey): PwContainOne.__init__(self, pwParent) self.dgHandleKey = dgHandleKey def DxDyNew(self, w, dxStart, mpksel): return self.pwChild.DxDyNew(w, dxStart, mpksel) def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): self.pwChild.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, synt, dxIndent): PwContainer.__init__(self, pwParent) self.synt = synt self.dxIndent = dxIndent def Value(self): return self.synt 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 Pcur.PwProjected(self.block) def run(self): while True: key = self.client.evKey.receive(self) if key == ansi.K_PGDN: Vm().RunSynt(self.block) continue 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(Pcur.PwProjected(self.block).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 = SyntBlock() 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")