marmots/world.py
2020-08-01 22:57:26 -04:00

294 lines
10 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 2020 Jeremy Penner
import ansi_cython as ansi
from tpers import TPrs, Partial
from scripting import Vm
from basetoken import AutoJoiner, AutoKiller, MsgScroller, Mi, MiButton, MiTypein, MiMenu, OvPopup
from basetoken import LeaveJoinToken, Selor
from util import RgstWrapped
from engine import Game, Token, TokenClient, Event
from datetime import datetime
import traceback
class Chl(object):
def __init__(self, client, st):
self.user = client.cld.user
self.st = st.rstrip()
class ChlMsg(Chl):
def StUser(self):
return "<" + self.user + ">"
def Col(self):
return ansi.WHITE
class ChlAct(Chl):
def StUser(self):
return "* " + self.user
def Col(self):
return ansi.MAGENTA
class ChlSys(Chl):
def StUser(self):
return self.user
def Col(self):
return ansi.BLUE
class Chat(TPrs):
def InitTransient(self):
TPrs.InitTransient(self)
self.cchl = 100
self.MakeTransient("game", None)
self.MakeTransient("evChat", None)
def InitPersistent(self):
TPrs.InitPersistent(self)
self.rgchl = []
def AttachToGame(self, game):
with self.SetTransiently():
self.game = game
self.evChat = Event(game)
def DetachFromGame(self):
with self.SetTransiently():
self.game = None
self.evChat = None
def Add(self, chl):
if len(chl.st) > 0:
self.rgchl.insert(0, chl)
if len(self.rgchl) > self.cchl:
self.rgchl = self.rgchl[:self.cchl]
self.evChat.fire(chl)
def SendMsg(self, client, st):
if st.lower().startswith("/me ") and len(st.rstrip()) > 3:
self.Add(ChlAct(client, st[4:]))
else:
self.Add(ChlMsg(client, st))
def CChl(self):
return len(self.rgchl)
def CUsers(self):
return len(self.game.RgclientConnected())
def DrawMsg(self, ascr, x, y, w, h, colBg=ansi.BLACK, ichl=0):
yDraw = y + h - 1
for chl in self.rgchl[ichl:]:
stUser = chl.StUser()
colFg = chl.Col()
wMsg = w - len(stUser) - 1
rgstMsg = RgstWrapped(chl.st, wMsg)
rgstMsg.reverse()
for istMsg, stMsg in enumerate(rgstMsg):
if istMsg == len(rgstMsg) - 1:
st = "{0} {1:{2}}".format(stUser, stMsg, wMsg)
else:
st = "{0:{1}} {2:{3}}".format("", len(stUser), stMsg, wMsg)
ascr.PutSt(st, x, yDraw, colFg, colBg)
yDraw = yDraw - 1
if yDraw == y - 1:
return
while yDraw >= y:
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw - 1
def DrawUsers(self, ascr, x, y, w, h, colBg=ansi.BLACK, iuser=0):
yDraw = y
for client in sorted(self.game.RgclientConnected(), lambda c1, c2: cmp(c1.Cldg(self.game).iclient, c2.Cldg(self.game).iclient))[iuser:]:
ascr.PutSt("{0:{1}}".format(str(client.Cldg(self.game).iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw + 1
if yDraw == y + h:
break
while yDraw < y + h:
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
yDraw = yDraw + 1
class MiChat(Mi):
KWNDMSG = 0
KWNDUSERS = 1
def InitPersistent(self, client, height, chat):
Mi.InitPersistent(self)
self.client = client
self.chat = chat
self.height = height
assert height > 2
self.kwnd = self.KWNDMSG
self.imsg = 0
self.iuser = 0
def StName(self):
return "Chat"
def HandleKey(self, key):
if self.kwnd == self.KWNDUSERS:
if key == ansi.K_LEFT:
self.kwnd = self.KWNDMSG
elif (key == 'a' or key == 'A') and self.iuser > 0:
self.iuser = self.iuser - 1
elif (key == 'z' or key == 'Z') and self.iuser < self.chat.CUsers() - 1:
self.iuser = self.iuser + 1
else:
return False
else: #KWNDMSG
if key == ansi.K_RIGHT:
self.kwnd = self.KWNDUSERS
elif (key == 'a' or key == 'A') and self.imsg < self.chat.CChl() - 1:
self.imsg = self.imsg + 1
elif (key == 'z' or key == 'Z') and self.imsg > 0:
self.imsg = self.imsg - 1
else:
return False
return True
def Height(self, w):
return self.height
def ColBgDefault(self):
return ansi.BLACK
def ColBgSelected(self, fPri):
return ansi.BLACK
def ColFg(self):
return ansi.WHITE
def FIgnoreParent(self):
return True
def Draw(self, ascr, x, y, w, selor):
wChat = (w / 4) * 3
wUser = (w / 4) - 1
self.chat.DrawMsg(ascr, x, y, wChat, self.height - 1, self.ColBg(selor), self.imsg)
for yT in range(y, y + self.height - 1):
ascr.PutAch(ansi.MkAch(chr(179)), x + wChat, yT)
ascr.PutSt("{0:{1}<{2}}{3}{0:{1}<{4}}".format("", chr(196), wChat, chr(193), wUser), x, y + self.height - 1)
self.chat.DrawUsers(ascr, x + wChat + 1, y, wUser, self.height - 1, self.ColBg(selor), self.iuser)
if selor.KSel() == Selor.PRI:
if self.kwnd == self.KWNDMSG:
xBar = x + wChat - 1
pct = 1 - (self.imsg / float(self.chat.CChl()))
else: # KWNDUSERS
xBar = x + w - 1
pct = self.iuser / float(self.chat.CUsers())
yBar = int(y + (pct * (self.height - 4)))
ascr.PutAch(ansi.MkAch("A", ansi.RED, ansi.WHITE), xBar, yBar)
ascr.PutAch(ansi.MkAch(chr(186), ansi.RED, ansi.WHITE), xBar, yBar + 1)
ascr.PutAch(ansi.MkAch("Z", ansi.RED, ansi.WHITE), xBar, yBar + 2)
class Joiner(LeaveJoinToken):
def InitPersistent(self, owner):
Token.InitPersistent(self, owner)
self.msgScroller = self.game.rgtoken("msgscroller")[0]
def OnJoin(self, client):
with client.Cldg(self.game).SetTransiently() as cldg:
cldg.iclient = -1
iclient = 1
rgiclient = [clientT.Cldg(self.game).iclient for clientT in self.game.RgclientConnected()]
while iclient in rgiclient:
iclient = iclient + 1
cldg.iclient = iclient
self.msgScroller.evPost.fire(client.cld.user + "(#" + str(cldg.iclient) + ") has joined! :)")
self.game.Chat().Add(ChlSys(client, "has joined"))
def OnLeave(self, client):
self.msgScroller.evPost.fire(client.cld.user + " has left! :(")
self.game.Chat().Add(ChlSys(client, "has left"))
## TODO:
## - fix lobby numbering (clients either editing or playing a game)
class Player(TokenClient):
def InitPersistent(self, owner, client):
TokenClient.InitPersistent(self, owner, client, "drawable", "player")
self.rbot = self.game.vm.AddPlayer()
self.vm = self.game.vm
chat = self.game.chat
miQuit = MiButton("Leave Game", self.client.leaveGame)
self.mi = MiMenu([None, MiChat(self.client, 13, chat), MiTypein(Partial(chat.SendMsg, self.client)), None, miQuit, None])
self.ovPopup = OvPopup(self, self.client, self.mi)
def die(self):
self.vm.RemovePlayer(self.rbot)
TokenClient.die(self)
def run(self):
while True:
key = self.EvKey().receive(self)
posNew = self.rbot.Pos().Clone()
if key == ansi.K_UP:
posNew.Up()
elif key == ansi.K_DOWN:
posNew.Down()
elif key == ansi.K_LEFT:
posNew.Left()
elif key == ansi.K_RIGHT:
posNew.Right()
elif key == ansi.K_TAB:
self.ovPopup.evShow.fire()
self.ovPopup.evDone.receive(self)
if not posNew.Equals(self.rbot.Pos()):
# is there an rbot here?
rgrbotCollide = [rbot for rbot in self.vm.Rgrbot() if rbot != self.rbot and rbot.Pos().Equals(posNew)]
if len(rgrbotCollide) > 0:
try:
rgrbotCollide[0].botdef.syntOnTouch.Eval(self)
except:
traceback.print_exc()
elif self.game.board.AchAtPos(posNew) == ansi.achBlank:
self.rbot.Move(posNew)
def draw(self, ascr, client):
if client == self.client:
self.game.board.draw(ascr)
for rbot in self.vm.Rgrbot():
ascr.PutAch(rbot.Ach(), rbot.Pos().X(), rbot.Pos().Y())
def GlobalMsg(self, stMsg):
self.game.rgtoken("msgscroller")[0].evPost.fire(stMsg)
# idea: synchronous event pair to implement "show message" w/ blocking
class GameWorld(Game):
def InitPersistent(self, board):
Game.InitPersistent(self)
self.board = board
self.vm = Vm(board.defs)
self.dtStart = datetime.now()
self.chat = Chat()
def InitTransient(self):
Game.InitTransient(self)
if not self.FDead():
self.chat.AttachToGame(self)
self.rgdgdie.append(self.chat.DetachFromGame)
def Chat(self):
return self.chat
def StName(self):
return self.dtStart.strftime("%b %d, %Y at %I:%M%p") + " [" + str(len(self.RgclientConnected())) + "]"
def GetRgclsTokTrans(self):
return [[AutoJoiner, Player], MsgScroller, Joiner, AutoKiller]
def GetRgtagDraw(self):
return ["player", "overlay"]