# This file is part of MarMOTS. # # MarMOTS is free software: you can redistribute it and/or modify it under the terms of the GNU Affero # General Public License as published by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # MarMOTS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General # Public License for more details. # # You should have received a copy of the GNU Affero General Public License along with MarMOTS. If not, # see . # # Copyright 2009, 2010, 2011, 2020 Jeremy Penner from engine import * from basetoken import * from tpers import * from util import * from contextlib import contextmanager import itertools import random import inspect # 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): StypeStmt.rgclsSynt.append(clsStmt) return clsStmt class Botdef(TPrs): def InitPersistent(self, stName, ach, x, y): self.stName = stName self.ach = ach self.x = x self.y = y self.fPlayer = False self.syntOnTouch = SyntBlock() class Flagdef(TPrs): def InitPersistent(self, stName, value = True): self.stName = stName self.value = value class Rbot(TPrs): def InitPersistent(self, botdef): self.botdef = botdef self.pos = ansi.Pos(x=botdef.x, y=botdef.y) def Ach(self): return self.botdef.ach def Pos(self): return self.pos def Move(self, pos): self.pos = pos class Defs(TPrs): def InitPersistent(self, rgflagdef = None, rgbotdef = None): self.rgflagdef = rgflagdef or [] self.rgbotdef = rgbotdef or [] def AddFlag(self, stName, fValue): self.rgflagdef.append(Flagdef(stName, fValue)) class Vm(TPrs): def InitPersistent(self, defs): self.defs = defs self.mpflagdef = {} self.mpbotdef = { botdef: Rbot(botdef) for botdef in defs.rgbotdef if not botdef.fPlayer } self.rgrbotPlayer = [] def SetFlag(self, flagdef, fValue): self.mpflagdef[flagdef] = fValue def FFlagSet(self, flagdef): return self.mpflagdef[flagdef] if flagdef in self.mpflagdef else flagdef.value def AddPlayer(self): rgbotdefPlayer = [botdef for botdef in self.defs.rgbotdef if botdef.fPlayer] botdef = random.choice(rgbotdefPlayer) rbotPlayer = Rbot(botdef) self.rgrbotPlayer.append(rbotPlayer) return rbotPlayer def RemovePlayer(self, rbotPlayer): self.rgrbotPlayer.remove(rbotPlayer) def Rgrbot(self): for botdef in self.defs.rgbotdef: if not botdef.fPlayer and botdef not in self.mpbotdef: self.mpbotdef[botdef] = Rbot(botdef) return itertools.chain(self.mpbotdef.values(), self.rgrbotPlayer) def Fail(self, stMessage, synt): self.Log(Fail(stMessage, synt)) def Log(self, fail): print(fail.stFailure, fail.synt) class Stype(TPrs): "Syntax type" def RgsyntFromSt(self, defs, st): "Return syntax objects whose rtypes match this that match a (partially entered) string." return [] class StypeEnum(Stype): @classmethod def RgclsSynt(cls): return cls.rgclsSynt def RgsyntFromSt(self, defs, st): for clsStmt in self.rgclsSynt: if clsStmt.StForTypein().lower().startswith(st.lower()): yield clsStmt() class StypeStmt(StypeEnum): rgclsSynt = [] class StypeNewFlag(Stype): def RgsyntFromSt(self, defs, st): if len(st) > 0: for flagdef in defs.rgflagdef: if flagdef.stName == st: return yield SyntMakeFlagDef(defs, st, False) yield SyntMakeFlagDef(defs, st, True) class Rtype(Stype): "Runtime type" 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) class RtypeBool(Rtype): pythontype = bool def RgsyntFromSt(self, defs, st): if "true".startswith(st.lower()): yield SyntLit(True, self) if "false".startswith(st.lower()): yield SyntLit(False, self) class RtypeFlag(Rtype): pythontype = Flagdef def RgsyntFromSt(self, defs, st): return (SyntFlagRef(flagdef) for flagdef in defs.rgflagdef if flagdef.stName.lower().startswith(st.lower())) class RtypeUnion(Rtype): def InitPersistent(self, *rgrtype): self.rgrtype = rgrtype def RgsyntFromSt(self, defs, st): for rtype in self.rgrtype: for synt in rtype.RgsyntFromSt(defs, st): yield synt def StForSobj(self, sobj): for rtype in self.rgrtype: if rtype.FMember(sobj): return rtype.StForSobj(sobj) return str(sobj) def FMember(self, sobj): return any((rtype.FMember(sobj) for rtype in self.rgrtype)) 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) class Synt(Typeable): "A piece of syntax" def InitPersistent(self): self.syntParent = None self.rgsynt = [] self.Populate() @classmethod def SyntDefault(cls): return cls() def WithParent(self, syntParent): assert self.syntParent == None or syntParent == None self.syntParent = syntParent return self def Populate(self): "Populate rgsynt with useful default values." pass def Project(self, pcur): "Project the current synt into its corresponding pws." pass def Eval(self, env): "Execute yourself, in the context of vm." return env.vm.Fail("Missing information", self) def StypeForChild(self, syntChild): "Return the stype that should be applied to the given child." return None def Clone(self): synt = self.ShallowClone() synt.rgsynt = [child.Clone().WithParent(synt) for child in self.rgsynt] return synt def ShallowClone(self): # assumes constructor takes named arguments and assigns them to attributes with the same name # if a more complex case comes up the subclass should override ShallowClone to allow cut/paste # to work rgarg = [] for arg in inspect.getargspec(self.InitPersistent).args[1:]: rgarg.append(getattr(self, arg)) return self.__class__(*rgarg) def ProjectTypein(self, pcur): return self.syntParent.ProjectTypeinForChild(pcur, self, self.StForTypein()) def ProjectTypeinForChild(self, pcur, syntChild, st): pwParent = pcur.PwHoriz(syntChild) def OnSelect(synt, psel): self.Replace(syntChild, synt) syntChild.SetStTypein(None) if syntChild.GetStTypein() != None: pwParent = PwDropdown(pwParent) pwTypein = PwTypein(pwParent, st, syntChild) stype = self.StypeForChild(syntChild) if stype: def Tooltip(): pw = PwBlock(None, None, 0) for synt in stype.RgsyntFromSt(pcur.defs, ''): PwStatic(pw, synt.StForTypein()) return pw if syntChild.GetStTypein() != None: for synt in stype.RgsyntFromSt(pcur.defs, syntChild.GetStTypein()): PwButton(pwParent, synt.StForTypein(), synt, OnSelect) pcur.pwTooltip.AddTip(pwParent, Tooltip) else: pcur.pwTooltip.AddTip(pwTypein, Tooltip) def Replace(self, syntOld, syntNew): self.rgsynt[self.Isynt(syntOld)] = syntNew.WithParent(self) 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 Stype(self): return self.syntParent.stypeForChild(self) if self.syntParent else None # desc - list of desces # desce: # string: "foo" -- literal uneditable text # list: ["foo"] -- text to edit the current synt # synt: synt - insert an empty instance of this synt # stype: stype - insert a SyntHole, use stype to determine what can go there # tuple: (stype, synt) - insert a Synt that can replace itself, use stype to determine what can go there 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 hasattr(desce, "SyntDefault"): self.Append(desce.SyntDefault()) elif isinstance(desce, tuple): self.Append(desce[1].SyntDefault()) elif isinstance(desce, Stype): self.Append(SyntHole.SyntDefault()) def DesceChild(self, isyntChild): isyntDesce = 0 for desce in self.Desc(): if isinstance(desce, Stype) or hasattr(desce, "SyntDefault") or isinstance(desce, tuple): if isyntDesce == isyntChild: return desce isyntDesce += 1 return None def StypeForChild(self, syntChild): isyntChild = self.Isynt(syntChild) desce = self.DesceChild(isyntChild) if isinstance(desce, Stype): return desce elif isinstance(desce, tuple): return desce[0] 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.ProjectTypein(pcur) elif isinstance(desce, Stype) or hasattr(desce, "SyntDefault") or isinstance(desce, tuple): # embedded syntax self.rgsynt[isynt].Project(pcur) isynt += 1 class SyntText(Synt): def InitPersistent(self, st=""): Synt.InitPersistent(self) self.st = st def InitTransient(self): Synt.InitTransient(self) self.SetStTypein(self.st) def Project(self, pcur): PwTypein(pcur.PwHoriz(self), self.St(), self, ansi.GREEN) def St(self): return self.GetStTypein() def HandleTypeinKey(self, key): val = Synt.HandleTypeinKey(self, key) self.st = self.GetStTypein() return val def Eval(self, env): return self.st class SyntHole(SyntDesc): desc = [[" "]] def Eval(self, env): pass class SyntBlock(Synt): def Project(self, pcur): with pcur.Indent(2 if pcur.pwHoriz != None else 0): pcur.EndLine() def OnInsertNewLine(syntAfter, psel): self.InsertLineAt(0) psel.Inc(pcur.Reset().Project().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 StypeForChild(self, syntChild): return StypeStmt() def InsertLineAt(self, isynt, syntStmt = None): if syntStmt == None: syntStmt = SyntHole() syntStmt = syntStmt.WithParent(self) self.rgsynt.insert(isynt, syntStmt) return syntStmt def RemoveLine(self, synt, psel): psel.syntClipboard = synt.WithParent(None) self.rgsynt.remove(synt) def HandleKey(self, pwKey, pov, psel, key): synt = pwKey.pwChild.Value() if ansi.FEnter(key): isyntInsert = self.rgsynt.index(synt) + 1 self.InsertLineAt(isyntInsert) clevelVert = psel.CLevelChild(pwKey.PwParent()) pwVert = psel.PwSelected(pov.PwProjected(), clevelVert - 1) psel.Inc(pwVert) elif (key == ansi.K_DEL or key == ansi.K_CTRL('x')) and pwKey.pwChild.RgpwChild()[0] == psel.PwSelected(pwKey): self.RemoveLine(synt, psel) elif key == ansi.K_CTRL('c') and pwKey.pwChild.RgpwChild()[0] == psel.PwSelected(pwKey): psel.syntClipboard = synt.Clone().WithParent(None) elif key == ansi.K_CTRL('v') and psel.syntClipboard: isyntInsert = self.rgsynt.index(synt) self.InsertLineAt(isyntInsert, psel.syntClipboard.Clone()) else: return False return True def Eval(self, env): for synt in self.rgsynt: # todo: log failures synt.Eval(env) 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.ProjectTypein(pcur) def Eval(self, env): return self.value class SyntFlagRef(Synt): def InitPersistent(self, flagdef = None): Synt.InitPersistent(self) self.flagdef = flagdef def StForTypein(self): if self.flagdef: return self.flagdef.stName return "" def Project(self, pcur): self.ProjectTypein(pcur) def Eval(self, env): try: return env.vm.FFlagSet(self.flagdef) except: return env.vm.Fail("No such flag", self) class SyntMakeFlagDef(Synt): def InitPersistent(self, defs, stName, fInit): Synt.InitPersistent(self) self.defs = defs self.stName = stName self.fInit = fInit def StForTypein(self): return "New flag: " + self.stName + " (initially " + str(self.fInit) + ")" def WithParent(self, syntParent): flagdef = Flagdef(self.stName, self.fInit) self.defs.rgflagdef.append(flagdef) return SyntFlagRef(flagdef).WithParent(syntParent) class SyntIsFlagSet(SyntDesc): desc = [["set"]] def Eval(self, env, val): return val class SyntIsFlagUnset(SyntDesc): desc = [["not set"]] def Eval(self, env, val): return not val class SyntIsFlagEqualTo(SyntDesc): desc = [["equal to"], " ", RtypeFlag()] flag = SyntDesc.DefProp(0) def Eval(self, env, val): return val == self.flag.Eval(env) class SyntIsFlagNotEqualTo(SyntDesc): desc = [["not equal to"], " ", RtypeFlag()] flag = SyntDesc.DefProp(0) def Eval(self, env, val): return val != self.flag.Eval(env) class StypeFlagTest(StypeEnum): rgclsSynt = [SyntIsFlagSet, SyntIsFlagUnset, SyntIsFlagEqualTo, SyntIsFlagNotEqualTo] @RegStmt class SyntIf(SyntDesc): desc = [["If"], " ", RtypeFlag(), " is ", (StypeFlagTest(), SyntIsFlagSet), ", then:", SyntBlock, "or else:", SyntBlock] expr = SyntDesc.DefProp(0) test = SyntDesc.DefProp(1) blockIfTrue = SyntDesc.DefProp(2) blockIfFalse = SyntDesc.DefProp(3) def Eval(self, env): if self.test.Eval(env, self.expr.Eval(env)): return self.blockIfTrue.Eval(env) else: return self.blockIfFalse.Eval(env) @RegStmt class SyntSet(SyntDesc): desc = [["Set flag"], " ", RtypeUnion(RtypeFlag(), StypeNewFlag()), " to ", RtypeUnion(RtypeBool(), RtypeFlag())] lvalue = SyntDesc.DefProp(0) expr = SyntDesc.DefProp(1) def Eval(self, env): # todo: fail if flagdef or expr isn't set properly? env.vm.SetFlag(self.lvalue.flagdef, self.expr.Eval(env)) @RegStmt class SyntPrint(SyntDesc): desc = [["Broadcast"], " message to all players: \"", SyntText, "\""] expr = SyntDesc.DefProp(0) def Eval(self, env): env.GlobalMsg(self.expr.Eval(env)) class SyntDialogBlock(SyntBlock): def StypeForChild(self, syntChild): return StypeDialogElement() def Eval(self, env): rgmi = [] for synt in self.rgsynt: rgmiChild = synt.Eval(env) if rgmiChild != None: rgmi += rgmiChild else: rgmi.append(None) return rgmi class SyntDialogIf(SyntIf): desc = [["If"], " ", RtypeFlag(), " is ", (StypeFlagTest(), SyntIsFlagSet), ", then:", SyntDialogBlock, "or else:", SyntDialogBlock] class SyntDialogText(SyntDesc): desc = [["Text"], ": \"", SyntText, "\""] text = SyntDesc.DefProp(0) def StButtonText(self, env): return self.text.Eval(env) def Eval(self, env): return [MiDgText(Partial(self.StButtonText, env))] class SyntDialogOption(SyntDesc): desc = [["Option"], " \"", SyntText, "\" is selected:", SyntBlock] text = SyntDesc.DefProp(0) exprOnSelect = SyntDesc.DefProp(1) def Eval(self, env): return [MiButton(self.text.Eval(env), Partial(env.OnDialogOptionSelected, self.exprOnSelect))] class StypeDialogElement(StypeEnum): rgclsSynt = [SyntDialogText, SyntDialogOption, SyntDialogIf] @RegStmt class SyntDialog(SyntDesc): desc = [["Show dialog"], ":", SyntDialogBlock] dialog = SyntDesc.DefProp(0) def Eval(self, env): rgmi = self.dialog.Eval(env) env.ShowDialog(MiMenu([None] + rgmi + [None])) 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 # projection cursor class Pcur(object): @staticmethod def PwProjected(defs, synt, dgDone): pcur = Pcur(defs, synt) pcur.Project(dgDone) return pcur.pwSplit def __init__(self, defs, synt): self.defs = defs self.synt = synt self.Reset() def Reset(self): self.pwSplit = PwSplit(None, 80) self.pwVert = PwBlock(self.pwSplit, self.synt, 0) self.pwHoriz = None self.pwTooltip = PwTooltip(self.pwSplit) self.dxindent = 0 return self def Project(self, dgDone = None): self.synt.Project(self) PwStatic(self.pwVert, ' ') PwButton(self.pwVert, 'Save and go back to editor', self.defs, dgDone) return self @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 PwTooltip(Pw): def __init__(self, pwParent): Pw.__init__(self, pwParent) self.mppw_tip = {} def AddTip(self, pw, tip): self.mppw_tip[pw] = tip def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): pwNav = mpksel.GetPwNav() while pwNav: tip = self.mppw_tip.get(pwNav) if tip: tip().Draw(ascr, x, y, w, dxStart, mpksel, fOverlay) break else: pwNav = pwNav.pwParent class PwSplit(PwContainer): def __init__(self, pwParent, pctLeft): PwContainer.__init__(self, pwParent) self.pctLeft = pctLeft def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay): assert len(self.RgpwChild()) == 2 wLeft = (self.pctLeft * w) / 100 wRight = w - wLeft - 1 self.RgpwChild()[0].Draw(ascr, x, y, wLeft, dxStart, mpksel, fOverlay) self.RgpwChild()[1].Draw(ascr, x + wLeft + 1, y, wRight, 0, mpksel, fOverlay) if not fOverlay: ascr.Fill(ansi.MkAch(chr(186), ansi.WHITE | ansi.FBRIGHT, ansi.BLACK), 1, ascr.H() - y + 1, x + wLeft, y) 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, colBgDefault = ansi.BLUE): if typeable.GetStTypein() == None: stDisplay = stSobj else: stDisplay = None PwStatic.__init__(self, pwParent, stDisplay) self.typeable = typeable self.colBgDefault = colBgDefault 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 self.colBgDefault 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 or key == ansi.K_NEWLINE: 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) 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 HandleKey(self, pov, psel, key): if (key == ansi.K_RETURN or key == ansi.K_NEWLINE) and len(self.RgpwChild()) > 1: return self.RgpwChild()[1].HandleKey(pov, psel, key) return PwBlock.HandleKey(self, pov, psel, key) 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 self.syntClipboard = None 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 CLevelChild_PwRoot(self, pw): clevelChild = 1 while pw.PwParent() != None: clevelChild = clevelChild + 1 pw = pw.PwParent() return (clevelChild, pw) def CLevelChild(self, pw): return self.CLevelChild_PwRoot(pw)[0] def PwRoot(self, pw): return self.CLevelChild_PwRoot(pw)[1] def Value(self, pwRoot): pwRoot = self.PwRoot(pwRoot) self.Validate(pwRoot) if len(self.rgo_ipw) > 0: return self.rgo_ipw[-1][0] return None def PwSelected(self, pw, clevel = None): clevelChild, pwRoot = self.CLevelChild_PwRoot(pw) if clevel is not None: clevel = clevel + clevelChild - 1 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, pwRoot = self.CLevelChild_PwRoot(pwContainer) 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, defs, block): TokenClient.InitPersistent(self, owner, client, "drawable", "overlay") self.defs = defs self.block = block self.pselstate = self.game.rgtoken("pselstate")[0] def PwProjected(self): return Pcur.PwProjected(self.defs, self.block, self.Back) def Back(self, val, pw): self.client.leaveGame() def run(self): while True: key = self.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(LeaveJoinToken): def InitPersistent(self, owner, defs, block): Token.InitPersistent(self, owner, "pselstate") self.defs = defs 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.defs, self.block, None).PwFirstSelectable(), Ksel.NAV) class Mpksel(object): def __init__(self, pwRoot, client, mpclient_psel): self.mppw_ksel = {} self.pwNav = None for clientT, psel in mpclient_psel.items(): kselNew = Ksel.NAV pw = psel.PwSelected(pwRoot) if clientT != client: kselNew = kselNew | Ksel.FOTHER else: self.pwNav = pw self.mppw_ksel[pw] = Ksel.KselBest(kselNew, self.Get(pw)) def Get(self, pw): return self.mppw_ksel.get(pw, Ksel.NONE) def GetPwNav(self): return self.pwNav def GetMpksel(self, pwRoot, client): return self.Mpksel(pwRoot, client, self.mpclient_psel) def PselByClient(self, client): return self.mpclient_psel[client] class GameScriptTest(Game): def InitPersistent(self): Game.InitPersistent(self) self.defs = Defs() self.defs.AddFlag("flag1", True) self.defs.AddFlag("flag2", False) self.block = SyntBlock() def GetRgclsTokTrans(self): return [[PselState, self.defs, self.block], [AutoJoiner, [Pov, self.defs, 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")