marmots/basetoken.py

827 lines
26 KiB
Python
Raw Normal View History

2011-03-19 00:10:02 +00:00
from engine import *
import config
from zope.interface import implements, Interface
import weakref
from util import *
class Blinker(Token):
def InitPersistent(self, owner, setter, rgVal = [True, False], interval = 500):
Token.InitPersistent(self, owner)
self.setter = setter
self.rgVal = rgVal
self.interval = interval
def run(self):
while True:
for val in self.rgVal:
self.setter(val)
self.wait(self.interval)
class StaticGraphic(Token):
def InitPersistent(self, owner, ascr, pos, *rgtag):
Token.InitPersistent(self, owner)
self.pos = pos
self.tag("drawable", *rgtag)
self.ascr = ascr
def draw(self, ascr, client):
ascr.PutAscr(self.ascr, self.pos[0], self.pos[1])
class AnimatedGraphic(StaticGraphic):
def InitPersistent(self, owner, rgascr, pos, interval, *rgtag):
StaticGraphic.InitPersistent(self, owner, rgascr[0], pos, *rgtag)
Blinker(self, self.setAscr, rgascr, interval)
def setAscr(self, ascr):
self.ascr = ascr
class Terminal(Token):
def InitPersistent(self, owner, client, w = config.W, h = config.H):
Token.InitPersistent(self, owner, "drawable", "background")
self.client = client
self.x = 0
self.y = 0
self.w = w
self.h = h
self.fWrapHoriz = False
self.fWrapVert = True
self.stLine = None
self.xLine = None
self.yLine = None
self.screen = []
self.fBlink = False
self.colBg = ansi.BLUE
self.chEcho = None
for y in range(h):
self.screen.append("")
Blinker(self, self.setBlink)
def setBlink(self, fBlink):
self.fBlink = fBlink
def getKey(self):
return self.client.evKey.receive(self)
def getPrintableKey(self):
while True:
key = self.getKey()
if ansi.FKeyPrintable(key):
return key
def deleteCh(self):
stOld = self.screen[self.y]
self.screen[self.y] = stOld[0:self.x] + stOld[self.x + 1:]
def printSt(self, st):
stOld = self.screen[self.y]
stNew = stOld[0:self.x] + st + stOld[self.x+len(st):]
self.screen[self.y] = stNew
self.moveTo(self.x + len(st), self.y)
def moveTo(self, x, y):
self.x = x
self.y = y
while (self.x >= self.w):
if (self.fWrapHoriz):
self.y = self.y + 1
self.x = self.x - self.w
else:
self.x = self.w - 1
while (self.x < 0):
self.y = self.y - 1
self.x = self.x + self.w
if (self.y < 0):
self.y = 0
while (self.fWrapVert and self.y >= self.h):
self.screen = self.screen[1:]
self.screen.append("")
self.y = self.y - 1
self.screen[self.y] = self.screen[self.y].ljust(self.x)
def type(self, st):
self.client.beep()
for iSt in range(len(st)):
self.printSt(st[iSt:iSt + 1])
self.wait(35)
def typeLn(self, st):
self.type(st)
self.newLine()
def centerLn(self, st):
self.typeLn(st.rjust((self.w / 2) + (len(st) / 2)))
def getLine(self):
self.xLine = self.x
self.yLine = self.y
self.stLine = ""
fWrapHorizStart = self.fWrapHoriz
self.fWrapHoriz = False
while True:
key = self.getKey()
fBeep = True
if (key == ansi.K_BACKSPACE and self.x > self.xLine):
self.stLine = self.stLine[:self.x - self.xLine - 1] + self.stLine[self.x - self.xLine:]
self.moveTo(self.x - 1, self.y)
elif (key == ansi.K_LEFT and self.x > self.xLine):
self.moveTo(self.x - 1, self.y)
elif (key == ansi.K_RIGHT):
self.moveTo(self.x + 1, self.y)
if (self.x - self.xLine) > len(self.stLine):
self.stLine = self.stLine + " "
elif (ansi.FKeyPrintable(key)):
self.stLine = self.stLine[:self.x - self.xLine] + key + self.stLine[(self.x + 1) - self.xLine:]
self.moveTo(self.x + 1, self.y)
elif key == ansi.K_RETURN:
break
else:
print "weird key", ansi.StrKey(key)
fBeep = False
if fBeep:
self.client.beep()
self.fWrapHoriz = fWrapHorizStart
x,y = self.x, self.y
self.moveTo(self.xLine, self.yLine)
self.printSt(self.StLineDisp())
self.moveTo(x,y)
stLine = self.stLine.strip()
self.stLine = None
self.xLine = None
self.yLine = None
return stLine
def StLineDisp(self):
return self.stLine if not self.chEcho else self.chEcho * len(self.stLine)
def draw(self, ascr, client):
if client == self.client:
y = 1
for line in self.screen:
ascr.PutSt("{0:{1}}".format(line, self.w), 1, y, ansi.WHITE | ansi.FBRIGHT, self.colBg)
y = y + 1
if self.stLine:
ascr.PutSt(self.StLineDisp(), self.xLine + 1, self.yLine + 1, ansi.WHITE | ansi.FBRIGHT, self.colBg)
if (self.fBlink):
ascr.PutAch(ansi.MkAch(" ", ansi.WHITE | ansi.FBRIGHT, ansi.WHITE), self.x + 1, self.y + 1)
def spin(self, stSpin):
self.moveTo(self.x - 1, self.y)
self.printSt(stSpin)
def newSpinner(self):
return Blinker(self.game, self.spin, ["/","-","\\", "|"], 100)
def newLine(self, count = 1, wait = 0):
for i in range(count):
self.moveTo(0, self.y + 1)
self.beep()
self.wait(wait)
def beep(self):
self.client.beep()
def setColBg(self, colBg):
self.colBg = colBg
class AutoJoiner(Token):
def InitPersistent(self, owner, *rgclsTok):
Token.InitPersistent(self, owner)
self.rgclsTok = rgclsTok
def InitTransient(self):
Token.InitTransient(self)
self.mpclient_rgtok = {}
def run(self):
def dgoobLeave(client):
for tok in self.mpclient_rgtok[client]:
print "killing ", tok
tok.die()
while True:
with self.game.evLeave.oob(self, dgoobLeave):
client = self.game.evJoin.receive(self)
rgtok = []
for clsTok in self.rgclsTok:
if FIter(clsTok):
tok = clsTok[0](self.game, client, *clsTok[1:])
else:
tok = clsTok(self.game, client)
rgtok.append(tok)
self.mpclient_rgtok[client] = rgtok
class AliveWithPlayers(Token):
def InitPersistent(self, owner, *rgclsTok):
Token.InitPersistent(self, owner)
self.rgclsTok = rgclsTok
def InitTransient(self):
Token.InitTransient(self)
self.rgclient = []
def OnLeave(self, client):
self.rgclient.remove(client)
if len(self.rgclient) == 0:
for tok in [x for x in self.rgtokOwn]:
tok.die()
def run(self):
with self.game.evLeave.oob(self, self.OnLeave):
while True:
self.rgclient.append(self.game.evJoin.receive(self))
if len(self.rgclient) == 1:
for clsTok in self.rgclsTok:
if FIter(clsTok):
tok = clsTok[0](self, clsTok[1:])
else:
tok = clsTok(self)
class AutoKiller(Token):
def InitTransient(self):
Token.InitTransient(self)
self.rgclient = []
def OnLeave(self, client):
self.rgclient.remove(client)
if len(self.rgclient) == 0:
self.game.finish()
def run(self):
with self.game.evLeave.oob(self, self.OnLeave):
while True:
self.rgclient.append(self.game.evJoin.receive(self))
#selection oracle
class Selor(TPrs):
NONE = 0
PRI = 1
SEC = 2
def __init__(self, mi, selorParent, rgmiSec, fPri):
self.selorParent = selorParent
self.mi = mi
self.rgmiSec = rgmiSec
self.fPri = fPri
def MiParent(self):
if self.selorParent != None:
return self.selorParent.mi
return None
def KSel(self):
if self.fPri:
return self.PRI
elif self.mi in self.rgmiSec:
return self.SEC
else:
return self.NONE
def SelorChild(self, miChild, fPri):
return Selor(miChild, self, self.rgmiSec, self.fPri and fPri)
def SelorDeselect(self):
return Selor(self.mi, self.selorParent, self.rgmiSec, False)
class Mi(TPrs):
def InitPersistent(self):
self.miParent = None
self.fNotice = False
self.rgmiNotice = [self]
def Height(self, w):
return 1
def StName(self):
return "Unnamed"
def StKey(self):
return self.StName()
def HandleKey(self, key):
return False
def FIgnoreParent(self):
return False
def DrawWithParent(self, selorParent, ascr, x, y, w, fPri):
self.Draw(ascr, x, y, w, selorParent.SelorChild(self, fPri))
def ColBgDefault(self):
return ansi.BLUE
def ColBgSelected(self, fPri):
return ansi.YELLOW if fPri else ansi.CYAN
def ColBgNotice(self):
return ansi.CYAN
def ColBg(self, selor):
if selor != None and selor.KSel() != Selor.NONE:
if selor.KSel() == Selor.PRI:
for mi in self.rgmiNotice:
if mi != self and mi.fNotice:
print "Notice cleared for ", mi.StName(), "by ", self.StName()
mi.fNotice = False
return self.ColBgSelected(True)
else:
return self.ColBgSelected(False)
elif self.FNoticeFlagged():
return self.ColBgNotice()
elif not self.FIgnoreParent() and selor != None and selor.MiParent() != None:
return selor.MiParent().ColBg(selor.selorParent.SelorDeselect())
else:
return self.ColBgDefault()
def ColFg(self):
return ansi.WHITE | ansi.FBRIGHT
def FlagNotice(self):
self.fNotice = True
def ListenForNotice(self, mi):
self.rgmiNotice.append(mi)
def FNoticeFlagged(self):
for mi in self.rgmiNotice:
if mi.fNotice:
return True
return False
def FSelectable(self):
return True
class MiInvisible(Mi):
def Height(self, w):
return 0
def Draw(self, *rgarg):
pass
def FSelectable(self):
return False
miInvisible = MiInvisible()
class MiStatic(Mi):
def InitPersistent(self, stText):
Mi.InitPersistent(self)
self.stText = stText
def StName(self):
return self.stText
def FSelectable(self):
return False
def Height(self, w):
return len(RgstWrapped(self.StName(), w, 0))
def Draw(self, ascr, x, y, w, selor):
for st in RgstWrapped(self.StName(), w, 0):
ascr.PutSt("{0:^{1}}".format(st, w), x, y, self.ColFg(), self.ColBg(selor))
y = y + 1
class MiDgText(MiStatic):
def InitPersistent(self, dgText):
MiStatic.InitPersistent(self, None)
self.dgText = dgText
def StName(self):
return self.dgText()
class MiButton(MiStatic):
def InitPersistent(self, stName, dgCmd, value = None):
MiStatic.InitPersistent(self, stName)
self.dgCmd = dgCmd
self.value = value
def StName(self):
if self.stText == None and self.value != None:
return self.value.StName()
return self.stText
def Exec(self):
return self.dgCmd()
def FSelectable(self):
return True
def HandleKey(self, key):
if ansi.FEnter(key) or key == ' ':
return self.Exec()
return False
def Value(self):
return self.value
class MiToggle(MiButton):
def InitPersistent(self, stName, fInitial = False):
MiButton.InitPersistent(self, stName, None, fInitial)
def Exec(self):
self.value = not self.value
return True
def StName(self):
stPre = "[X] " if self.value else "[ ] "
return stPre + self.stText
def StKey(self):
return self.stText
class MiList(Mi):
def InitPersistent(self, rgval, ival = 0):
Mi.InitPersistent(self)
self.rgval = rgval
self.ival = ival
def Value(self):
return self.rgval[self.ival]
def Inc(self):
if self.ival < len(self.rgval) - 1:
self.ival = self.ival + 1
def Dec(self):
if self.ival > 0:
self.ival = self.ival - 1
def HandleKey(self, key):
if key == ansi.K_LEFT:
self.Dec()
elif key == ansi.K_RIGHT:
self.Inc()
else:
return False
return True
def Draw(self, ascr, x, y, w, selor):
xText = w / 4
xVal = w / 2
ascr.PutSt(self.StName() + ":", xText, y, self.ColFg(), self.ColBg(selor))
self.DrawValue(ascr, xVal, y, selor.SelorDeselect())
class MiTypein(Mi):
def InitPersistent(self, dgEnter, stName = "Typein", pctW = 100):
Mi.InitPersistent(self)
self.stedit = Stedit(dgEnter)
self.stName = stName
self.pctW = pctW
assert pctW > 0 and pctW <= 100
def StName(self):
return self.stName
def Value(self):
return self.stedit.GetValue()
def SetValue(self, st):
self.stedit.SetValue(st)
def HandleKey(self, key):
return self.stedit.HandleKey(key)
def ColBgDefault(self):
return ansi.BLACK
def ColBgSelected(self, fPri):
return ansi.WHITE if fPri else ansi.BLUE
def ColFg(self):
return ansi.WHITE
def FIgnoreParent(self):
return True
def Height(self, wT):
return len(RgstWrapped(self.GetStToWrap(), self.GetW(wT)))
def GetW(self, wT):
return int(wT * (self.pctW / 100.0))
def GetStToWrap(self):
return self.stedit.StForSize()
def Draw(self, ascr, xT, y, wT, selor):
w = self.GetW(wT)
x = xT + ((wT - w) / 2)
if selor.KSel() != Selor.NONE:
stToWrap = self.GetStToWrap()
else:
stToWrap = self.stedit.GetValue()
for snippet in RgSnippetWrapped(stToWrap, w):
ascr.PutSt("{0:{1}}".format(snippet.st, w), x, y, self.ColFg(), self.ColBg(selor.SelorDeselect()))
if selor.KSel() != Selor.NONE:
self.stedit.DrawCursor(ascr, x, y, self.ColFg(), self.ColBg(selor), snippet)
y = y + 1
class MiTab(MiStatic):
def InitPersistent(self, stText, mi):
MiStatic.InitPersistent(self, stText)
self.mi = mi
self.ListenForNotice(mi)
def Value(self):
return self.mi
def FSelectable(self):
return True
@staticmethod
def MiTabbed(*rgstTab_mi):
miTabRow = MiMenuHoriz([MiTab(*stTab_mi) for stTab_mi in rgstTab_mi])
return MiMenu([miTabRow, MiTabBody(miTabRow)])
class MiTabBody(Mi):
def InitPersistent(self, miMenu):
Mi.InitPersistent(self)
self.miMenu = miMenu
def Mi(self):
return self.miMenu.Value()
def HandleKey(self, key):
return self.Mi().HandleKey(key)
def Draw(self, ascr, x, y, w, selor):
self.Mi().Draw(ascr, x, y, w, selor)
def Height(self,w):
return self.Mi().Height(w)
class MiValue(Mi):
def InitPersistent(self, mi):
Mi.InitPersistent(self)
self.mi = mi
def StName(self):
return self.mi.StName()
def Value(self):
return self.mi.Value()
def Height(self,w):
return self.mi.Height(w)
def Draw(self, ascr, x, y, w, selor):
self.mi.DrawValue(ascr, x, y, selor.SelorChild(self.mi, False))
class Selection(TPrs):
def InitPersistent(self, rgo, o = None, io = 0):
self.rgo = rgo
self.o = o
self.io = -1 if o == None else io
self.Validate()
def Invalidate(self):
self.o = None
self.io = -1
def Validate(self):
# aw hell, selection only handles inserts, not deletes :(
if self.o == None or self.io < 0:
self.Invalidate()
elif len(self.rgo) <= self.io or self.rgo[self.io] != self.o:
try:
self.io = self.rgo.index(self.o)
except Exception:
self.Invalidate()
if self.o == None and not self.FEmpty():
self.io = IoInc(self.rgo, self.io, self.FSelectableI)
self.o = self.rgo[self.io]
def GetSelected(self):
self.Validate()
return self.o
def SetSelected(self, o):
self.o = o
self.Validate()
def ITopScroll(self, h, dgHeight):
# requirements:
# selection must be visible
# if possible, selection should be centered
if self.io < 0:
return 0
iotop = self.io
iobot = self.io
hCurr = dgHeight(self.o)
yobot = hCurr
fGrowUp = False
while hCurr < h:
fDoneUp = iotop == 0
fDoneDown = iobot == len(self.rgo) - 1
if (fGrowUp or fDoneDown) and not fDoneUp:
ho = dgHeight(self.rgo[iotop - 1])
if (yobot + ho >= h): # handle case where thing above selected thing is huge and pushes the selection off the screen
break
iotop = iotop - 1
yobot = yobot + ho
elif not fDoneDown:
ho = dgHeight(self.rgo[iobot + 1])
iobot = iobot + 1
else:
break
hCurr = hCurr + ho
fGrowUp = not fGrowUp
return iotop
def Inc(self):
return self.ChangeI(IoInc)
def Dec(self):
return self.ChangeI(IoDec)
def FEmpty(self):
for o in self.rgo:
if self.FSelectableI(o):
return False
return True
def ChangeI(self, fnChange):
self.Validate()
if self.o != None:
ioT = self.io
self.io = fnChange(self.rgo, self.io, self.FSelectableI)
self.o = self.rgo[self.io]
return ioT != self.io
def FSelectableI(self, o):
return o != None and o.FSelectable()
class MiMenuI(Mi):
def InitPersistent(self, rgmi):
Mi.InitPersistent(self)
self.selMi = Selection(rgmi)
self.mpstName_mi = {}
for mi in rgmi:
if mi != None:
self.mpstName_mi[mi.StKey().lower()] = mi
def Value(self, stKey = None):
if stKey == None:
return self.selMi.GetSelected().Value()
return self.mpstName_mi[stKey.lower()].Value()
def MiByName(self, stKey):
return self.mpstName_mi[stKey.lower()]
def HeightMi(self, mi, w):
if mi == None:
return 1
return mi.Height(w)
def ColBgSelected(self, fPri):
return self.ColBgDefault()
class MiMenu(MiMenuI):
def InitPersistent(self, rgmi, hMax = -1):
MiMenuI.InitPersistent(self, rgmi)
self.hMax = hMax
def HandleKey(self, key):
mi = self.selMi.GetSelected()
if mi == None or not mi.HandleKey(key):
if key == ansi.K_DOWN:
return self.selMi.Inc()
elif key == ansi.K_UP:
return self.selMi.Dec()
return False
return True
def HMax(self, ascr, y):
if self.hMax < 0:
return (ascr.H() - y) + 1
return self.hMax
def Height(self, w):
return sum([self.HeightMi(mi,w) for mi in self.selMi.rgo])
def Draw(self, ascr, x, y, w, selor):
ascr.Fill(ansi.MkAch(' ', self.ColFg(), self.ColBg(selor)), w, self.Height(w), x, y)
miSelected = self.selMi.GetSelected()
h = 0
hMax = self.HMax(ascr, y)
itop = self.selMi.ITopScroll(hMax, lambda mi: self.HeightMi(mi, w))
for imi, mi in enumerate(self.selMi.rgo):
if imi < itop:
continue
if mi != None:
mi.DrawWithParent(selor, ascr, x, y + h, w, miSelected == mi)
h = h + self.HeightMi(mi, w)
if h >= hMax:
break
class MiMenuHoriz(MiMenuI):
def InitPersistent(self, rgmi, rgsize = None):
MiMenuI.InitPersistent(self, rgmi)
if rgsize == None:
rgsize = [1 for mi in rgmi]
self.rgsize = rgsize
self.sizeMax = float(sum(rgsize))
def Width(self, imi, w):
return int((self.rgsize[imi] / self.sizeMax) * w)
def HandleKey(self, key):
mi = self.selMi.GetSelected()
if mi == None or not mi.HandleKey(key):
if key == ansi.K_RIGHT:
return self.selMi.Inc()
elif key == ansi.K_LEFT:
return self.selMi.Dec()
return False
return True
def Height(self, w):
return max([self.HeightMi(mi, w) for mi in self.selMi.rgo])
def Draw(self, ascr, x, y, w, selor):
ascr.Fill(ansi.MkAch(' ', self.ColFg(), self.ColBg(selor)), w, self.Height(w), x, y)
miSelected = self.selMi.GetSelected()
for imi, mi in enumerate(self.selMi.rgo):
wT = self.Width(imi, w)
if mi != None:
mi.DrawWithParent(selor, ascr, x, y, wT, miSelected == mi)
x = x + wT
def DgProjectMiButton(dgExec):
def DgProject(value):
return MiButton(None, lambda: dgExec(value), value)
return DgProject
class RgmiProjected(TPrs):
def InitPersistent(self, rgvalue, dgProject):
self.rgvalue = rgvalue
self.dgProject = dgProject
def InitTransient(self):
self.mpvalue_wrmi = weakref.WeakKeyDictionary()
def __getitem__(self, i):
return self.MiFromValue(self.rgvalue[i])
def MiFromValue(self, value):
if value in self.mpvalue_wrmi:
mi = self.mpvalue_wrmi[value]()
if mi != None:
return mi
mi = self.dgProject(value)
mi._valueProjected = value
self.mpvalue_wrmi[value] = weakref.ref(mi) # this probably gc's much more than is necessary ... :/
return mi
def __len__(self):
return len(self.rgvalue)
def index(self, mi):
return self.rgvalue.index(mi._valueProjected)
class OvStatic(TokenClient):
TOP = 1
MIDDLE = 2
BOTTOM = 3
FILL = 4
def InitPersistent(self, owner, client, miMenu, kplace, *rgtag):
TokenClient.InitPersistent(self, owner, client, "drawable", "menu", *rgtag)
self.miMenu = miMenu
self.kplace = kplace
self.mpst_miSel = {}
def SetSelection(self, stSel, mi):
self.mpst_miSel[stSel] = mi
def GetSelection(self, stSel):
try:
return self.mpst_miSel[stSel]
except KeyError:
return None
def draw(self, ascr, client):
self.drawI(ascr, client, False)
def drawI(self, ascr, client, fSelected):
if client == self.client:
selor = Selor(self.miMenu, None, self.mpst_miSel.values(), fSelected)
if self.kplace & self.FILL:
h = ascr.H()
y = 1
dh = h - self.miMenu.Height(ascr.W())
if dh > 0:
ascr.Fill(ansi.MkAch(' ', self.miMenu.ColFg(), self.miMenu.ColBg(selor)), ascr.W(), dh, 1, ascr.H() - dh + 1)
else:
h = self.miMenu.Height(ascr.W())
if self.kplace == self.TOP:
y = 1
elif self.kplace == self.MIDDLE:
y = (ascr.H() / 2) - (h / 2)
else: # BOTTOM
y = ascr.H() - h + 1
self.miMenu.Draw(ascr, 1, y, ascr.W(), selor)
def Value(self, stName = None):
return self.miMenu.Value(stName)
class OvMenu(OvStatic):
def run(self):
while True:
self.miMenu.HandleKey(self.client.evKey.receive(self))
def draw(self, ascr, client):
self.drawI(ascr, client, True)
class OvPopup(OvStatic):
def InitPersistent(self, owner, client, miMenu):
OvStatic.InitPersistent(self, owner, client, miMenu, self.MIDDLE, "drawable", "menu")
self.evShow = Event(self.game)
self.evDone = Event(self.game)
self.fAwake = False
def run(self):
while True:
self.evShow.receive(self)
self.fAwake = True
while self.fAwake:
key = self.client.evKey.receive(self)
if not self.miMenu.HandleKey(key):
if key == ansi.K_TAB or key == ansi.K_DEL or key == ' ' or ansi.FEnter(key):
self.fAwake = False
self.evDone.fire()
def draw(self, ascr, client):
if self.fAwake:
self.drawI(ascr, client, True)
class MsgScroller(Token):
def InitPersistent(self, owner, colFg=ansi.BLUE, colBg=ansi.BLACK, y=config.H):
Token.InitPersistent(self, owner, "drawable", "msgscroller", "overlay")
self.colFg = colFg
self.colBg = colBg
self.rgmsg = []
self.msg = None
self.evPost = Event(self.game)
self.y = y
def postI(self, msg):
self.rgmsg.append(msg)
def run(self):
while True:
self.postI(self.evPost.receive(self))
with self.evPost.oob(self, self.postI):
while len(self.rgmsg) > 0:
self.msg = self.rgmsg[0]
self.rgmsg = self.rgmsg[1:]
self.x = config.W
while self.x > -len(self.msg):
self.x = self.x - 1
self.wait(30)
self.msg = None
def draw(self, ascr, client):
if self.msg:
ascr.PutSt(self.msg, self.x, self.y, self.colFg, self.colBg)