995 lines
34 KiB
Python
995 lines
34 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
|
|
|
|
# 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 = True
|
|
|
|
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 = { flagdef: flagdef.value for flagdef in defs.rgflagdef }
|
|
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]
|
|
|
|
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 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 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 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):
|
|
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 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):
|
|
Synt.InitPersistent(self)
|
|
self.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.InsertLineAfter(syntAfter, None)
|
|
psel.Inc(pcur.pwVert) # does nothing because we need to reproject
|
|
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 InsertLineAfter(self, syntAfter, syntStmt):
|
|
if syntStmt == None:
|
|
syntStmt = SyntHole()
|
|
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 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 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"], " ", RtypeFlag(), " 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 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)
|
|
synt.Project(pcur)
|
|
PwStatic(pcur.pwVert, ' ')
|
|
PwButton(pcur.pwVert, 'Save and go back to editor', defs, dgDone)
|
|
return pcur.pwSplit
|
|
|
|
def __init__(self, defs, synt):
|
|
self.defs = defs
|
|
self.pwSplit = PwSplit(None, 80)
|
|
self.pwVert = PwBlock(self.pwSplit, synt, 0)
|
|
self.pwHoriz = None
|
|
self.pwTooltip = PwTooltip(self.pwSplit)
|
|
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 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)
|
|
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 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
|
|
|
|
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, 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)
|
|
if key == ansi.K_PGDN:
|
|
self.block.Eval(Vm(self.defs))
|
|
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") |