marmots/scripting.py

1112 lines
38 KiB
Python

# 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 <https://www.gnu.org/licenses/>.
#
# 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 "<flag>"
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")