# 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 import config cdef extern from "stdio.h": int sprintf(char *, char *,...) cdef extern from "stdlib.h": ctypedef unsigned long size_t void free(void *ptr) void *malloc(size_t size) void *calloc(size_t nelem, size_t elsize) void *realloc(void *ptr, size_t size) size_t strlen(char *s) char *strcpy(char *dest, char *src) esc = '%s['%chr(27) reset = '%s0m'%esc cls = '%s2j'%esc BLACK = 0 RED = 1 GREEN = 2 YELLOW = 3 BLUE = 4 MAGENTA = 5 CYAN = 6 WHITE = 7 FBRIGHT = 8 FBLINK = 16 rgcolorBg = range(8) rgcolorFg = range(8) rgcolorFg.extend([col | FBRIGHT for col in range(8)]) K_BACKSPACE = chr(8) K_TAB = chr(9) K_RETURN = chr(10) K_NEWLINE = chr(13) K_DEL = chr(127) K_LEFT = 256 K_RIGHT = 257 K_UP = 258 K_DOWN = 259 K_HOME = 260 K_END = 261 K_PGUP = 262 K_PGDN = 263 def K_CTRL(ch): return chr(ord(ch.upper()) - ord('@')) def StrKey(ch): if type(ch) == str: return ord(ch) return str(ch) def Ctrl(ch): ch = ch.upper() assert ch != 'M' and ch != 'J' and ch != 'H' return chr(ord(ch - 'A' + 1)) def FEnter(key): return key == "\n" or key == "\r" cdef struct SAch: unsigned char ch char fgCol char bgCol cdef union UAch: int ach SAch sach cpdef MkAch(char *ch, char fgcol=WHITE, char bgcol=BLACK): cdef UAch uach uach.sach.ch = ch[0] uach.sach.fgCol = fgcol uach.sach.bgCol = bgcol return uach.ach # the class must be named Ach for backwards compatibility, but the hungarian name is OAch class Ach(object): def __init__(self, int ach): cdef UAch uach uach.ach = ach self.ch = chr(uach.sach.ch) self.fgCol = uach.sach.fgCol self.bgCol = uach.sach.bgCol def ToAch(self): return MkAch(self.ch, self.fgCol, self.bgCol) cdef AchFromOach(oach): if oach == None: return achBlankI return MkAch(oach.ch, oach.fgCol, oach.bgCol) cdef OachFromAch(int ach): if ach == achBlankI: return None assert ach != achInvdI return Ach(ach) def ChFromAch(int ach): cdef UAch uach uach.ach = ach return chr(uach.sach.ch) achBlank = 0 achInvd = -1 cdef enum: achBlankI = 0 achInvdI = -1 chesc = 27 FBRIGHTI = 8 FBLINKI = 16 cdef AstFromAch(int ach, int achPrev = achInvdI): cdef UAch uach, uachPrev uach.ach = ach uachPrev.ach = achPrev cdef char ast[60] cdef char sgr[4] cdef int csgr = 0 if (achPrev == achInvdI) or (uachPrev.sach.fgCol != uach.sach.fgCol): fBright = uach.sach.fgCol & FBRIGHTI if (achPrev == achInvdI) or (fBright != (uachPrev.sach.fgCol & FBRIGHTI)): if fBright: sgr[csgr] = 1 else: sgr[csgr] = 0 achPrev = achInvdI csgr = csgr + 1 sgr[csgr] = (uach.sach.fgCol & 7) + 30 csgr = csgr + 1 # fBlink = uach.sach.fgCol & FBLINKI # if (achPrev == achInvdI) or (fBlink != uachPrev.sach.fgCol & FBLINKI): # if fBlink: # sgr[csgr] = 5 # else: # sgr[csgr] = 25 # csgr = csgr + 1 if (achPrev == achInvdI) or (uachPrev.sach.bgCol != uach.sach.bgCol): sgr[csgr] = uach.sach.bgCol + 40 csgr = csgr + 1 if csgr == 0: sprintf(ast, "%c", uach.sach.ch) elif csgr == 1: sprintf(ast, "%c[%dm%c", chesc, sgr[0], uach.sach.ch) elif csgr == 2: sprintf(ast, "%c[%d;%dm%c", chesc, sgr[0], sgr[1], uach.sach.ch) elif csgr == 3: sprintf(ast, "%c[%d;%d;%dm%c", chesc, sgr[0], sgr[1], sgr[2], uach.sach.ch) elif csgr == 4: sprintf(ast, "%c[%d;%d;%d;%dm%c", chesc, sgr[0], sgr[1], sgr[2], sgr[3], uach.sach.ch) else: assert csgr >= 0 and csgr <= 4, "more sgr possibilities, please handle" return ast mpch_entity = [0, 9786, 9787, 9829, 9830, 9827, 9824, 8226, 9688, 9675, 9689, 9794, 9792, 9834, 9835, 9788, 9658, 9668, 8597, 8252, 182, 167, 9644, 8616, 8593, 8595, 8594, 8592, 8735, 8596, 9650, 9660, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 8962, 199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, 239, 238, 236, 196, 197, 201, 230, 198, 244, 246, 242, 251, 249, 255, 214, 220, 162, 163, 165, 8359, 402, 225, 237, 243, 250, 241, 209, 170, 186, 191, 8976, 172, 189, 188, 161, 171, 187, 9617, 9618, 9619, 9474, 9508, 9569, 9570, 9558, 9557, 9571, 9553, 9559, 9565, 9564, 9563, 9488, 9492, 9524, 9516, 9500, 9472, 9532, 9566, 9567, 9562, 9556, 9577, 9574, 9568, 9552, 9580, 9575, 9576, 9572, 9573, 9561, 9560, 9554, 9555, 9579, 9578, 9496, 9484, 9608, 9604, 9612, 9616, 9600, 945, 223, 915, 960, 931, 963, 181, 964, 934, 920, 937, 948, 8734, 966, 949, 8745, 8801, 177, 8805, 8804, 8992, 8993, 247, 8776, 176, 8729, 183, 8730, 8319, 178, 9632, 160] setch_unsafe = set([ord('&'), ord('<'), ord('>'), ord('"'), ord("'")]) mpcol_hcol = ["#000000", "#AA0000", "#00AA00", "#AA5500", "#0000AA", "#AA00AA", "#00AAAA", "#AAAAAA", "#555555", "#FF5555", "#55FF55", "#FFFF55", "#5555FF", "#FF55FF", "#55FFFF", "#FFFFFF"] cdef HstFromAch(int ach, int achPrev = achInvdI): cdef UAch uach, uachPrev uach.ach = ach uachPrev.ach = achPrev stStyle = None if achPrev == achInvdI or uachPrev.sach.fgCol != uach.sach.fgCol or uachPrev.sach.bgCol != uach.sach.bgCol: stStyle = "color:" + mpcol_hcol[uach.sach.fgCol & 15] + ";background-color:" + mpcol_hcol[uach.sach.bgCol & 15] rgst = [] if stStyle != None and achPrev != achInvdI: rgst.append("") if stStyle != None: rgst.append("") if ach == achBlank: rgst.append(' ') else: entity = mpch_entity[uach.sach.ch] if entity == uach.sach.ch and not uach.sach.ch in setch_unsafe: rgst.append(chr(uach.sach.ch)) else: rgst.append("&#" + str(entity) + ";") return ''.join(rgst) class Pos(object): def __init__(self, int w=config.W, int h=config.H, int x=1, int y=1): self.i = PosI(w, h, x, y) def X(self): cdef PosI posI = self.i return posI.X() def Y(self): cdef PosI posI = self.i return posI.Y() def Xz(self): cdef PosI posI = self.i return posI.xz def Yz(self): cdef PosI posI = self.i return posI.yz def Inc(self): cdef PosI posI = self.i posI.Inc() def Up(self): cdef PosI posI = self.i return posI.Up() def Down(self): cdef PosI posI = self.i return posI.Down() def Left(self): cdef PosI posI = self.i return posI.Left() def Right(self): cdef PosI posI = self.i return posI.Right() def Clone(self): cdef PosI posI = self.i return posI.ToPos() def Equals(self, other): cdef PosI posIL = self.i cdef PosI posIR = other.i return posIL.xz == posIR.xz and posIL.yz == posIR.yz and posIL.w == posIR.w and posIL.h == posIR.h def __setstate__(self, state): self.i = PosI(state["w"], state["h"], state["xz"] + 1, state["yz"] + 1) def __getstate__(self): cdef PosI posI = self.i return {"w": posI.w, "h": posI.h, "xz": posI.xz, "yz": posI.yz} cdef class PosI(object): cdef int w, h, xz, yz def __init__(self, int w=config.W, int h=config.H, int x=1, int y=1): self.w = w self.h = h self.xz = x - 1 self.yz = y - 1 cdef inline X(self): return self.xz + 1 cdef inline Y(self): return self.yz + 1 cdef inline Inc(self): if self.xz == self.w - 1: if self.yz < self.h: self.xz = 0 self.yz = self.yz + 1 else: self.xz = self.xz + 1 cdef inline Up(self): if self.yz > 0: self.yz = self.yz - 1 return True cdef inline Down(self): if self.yz < self.h - 1: self.yz = self.yz + 1 return True cdef inline Left(self): if self.xz > 0: self.xz = self.xz - 1 return True cdef inline Right(self): if self.xz < self.w - 1: self.xz = self.xz + 1 return True cdef ToPos(self): return Pos(self.w, self.h, self.X(), self.Y()) class Ascr(object): def __init__(self, int w=config.W, int h=config.H, int achFill = achBlankI): cdef AscrI ascrI = AscrI(w,h) self.i = ascrI if achFill != achBlankI: ascrI.Fill(achFill, w, h) def __setstate__(self, state): cdef AscrI ascrI = AscrI(state["w"], state["h"]) self.i = ascrI cdef int iach = 0 for mpxz_oach in state["mpyzxz_ach"]: for oach in mpxz_oach: ascrI.rgach[iach] = AchFromOach(oach) iach = iach + 1 def __getstate__(self): cdef AscrI ascrI = self.i cdef int iach = 0 mpyzxz_oach = [] for yz in range(ascrI.h): mpxz_oach = [] for xz in range(ascrI.w): mpxz_oach.append(OachFromAch(ascrI.rgach[iach])) iach = iach + 1 mpyzxz_oach.append(mpxz_oach) return {"w":ascrI.w, "h":ascrI.h, "mpyzxz_ach": mpyzxz_oach} def W(self): cdef AscrI ascrI = self.i return ascrI.w def H(self): cdef AscrI ascrI = self.i return ascrI.h def ZPos(self, int xz, int yz): cdef AscrI ascrI = self.i return ascrI.ZPos(xz, yz) def PutAch(self, int ach, int x, int y): cdef AscrI ascrI = self.i ascrI.PutAch(ach, x, y) def GetAch(self, int x, int y): cdef AscrI ascrI = self.i return ascrI.rgach[ascrI.IAch(x - 1, y - 1)] def PutRgach(self, rgach, int x, int y): cdef AscrI ascrI = self.i ascrI.PutRgach(rgach, x, y) def PutSt(self, char *st, int x, int y, int colFg = WHITE, int colBg=BLACK): cdef AscrI ascrI = self.i ascrI.PutSt(st, x, y, colFg, colBg) def Fill(self, int ach, int wIn, int hIn, int x = 1, int y = 1): cdef AscrI ascrI = self.i ascrI.Fill(ach, wIn, hIn, x, y) def PutAscr(self, ascr, int x=1, int y=1): cdef AscrI ascrI = self.i ascrI.PutAscr(ascr.i, x, y) def AstDiff(self, ascr): cdef AscrI ascrI = self.i return ascrI.AstDiff(ascr.i) def Hst(self): cdef AscrI ascrI = self.i return ascrI.Hst() def Ast(self): return Ascr(self.W(), self.H(), achInvdI).AstDiff(self) #stupid implementation # ANSI Screen cdef class AscrI(object): cdef int w, h cdef int *rgach def __cinit__(self, int w=config.W, int h=config.H, int achFill = achBlankI): self.rgach = calloc(w * h, sizeof(int)) if achFill != achBlankI: for iach in range(w * h): self.rgach[iach] = achFill self.w = w self.h = h def __dealloc__(self): free(self.rgach) cdef Zpos(self, int xz, int yz): return Pos(self.w, self.h, xz + 1, yz + 1) cdef inline IAch(self, int xz, int yz): return xz + (self.w * yz) cdef PutAch(self, int ach, int x, int y): if not (x < 1 or y < 1 or x > self.w or y > self.h): #clip self.rgach[self.IAch(x - 1, y - 1)] = ach #self.mpyzxz_ach[y-1][x-1] = ach cdef inline GetAch(self, int x, int y): return self.rgach[self.IAch(x - 1, y - 1)] cdef PutRgach(self, rgach, int x, int y): cdef int xz = x - 1 cdef int iach if xz < self.w: if xz < 0: rgach = rgach[min(-xz, len(rgach)):] xz = 0 if xz + len(rgach) >= self.w: rgach = rgach[:self.w - xz] iach = self.IAch(xz, y - 1) for ach in rgach: if ach != achBlankI: self.rgach[iach] = ach iach = iach + 1 #self.mpyzxz_ach[y-1][xz:xz + len(rgach)] = rgach cdef PutSt(self, char *st, int x, int y, int colFg = WHITE, int colBg=BLACK): if y < 1 or y > self.h: return cdef int cch = len(st) cdef int ichStart = 0 cdef int xz = x - 1 if xz < 0: ichStart = (-xz) cch = cch - ichStart xz = 0 cch = min(cch, self.w - xz) cdef UAch uach uach.sach.fgCol = colFg uach.sach.bgCol = colBg cdef int iach = self.IAch(xz, y - 1) for ich in range(ichStart, ichStart + cch): uach.sach.ch = st[ich] self.rgach[iach] = uach.ach iach = iach + 1 #self.PutRgach([MkAch(ch, colFg, colBg) for ch in st], x, y) cdef Fill(self, int ach, int wIn, int hIn, int x = 1, int y = 1): if x > self.w or y > self.h or ach == achBlankI: return cdef int xz = max(x - 1, 0) cdef int yz = max(y - 1, 0) cdef int w = min(self.w - xz, wIn) cdef int h = min(self.h - yz, hIn) cdef int iach for yzT in range(yz, yz + h): iach = self.IAch(xz, yzT) for xzT in range(w): self.rgach[iach] = ach iach = iach + 1 #self.mpyzxz_ach[yzT][xz:xz + w] = ach cdef PutAscr(self, AscrI ascr, int x=1, int y=1): if x > self.w or y > self.h: return cdef int xz = max(x - 1, 0) cdef int yz = max(y - 1, 0) cdef int w = min(self.w - xz, ascr.w) cdef int h = min(self.h - yz, ascr.h) cdef int yzOther = 0 cdef int iach, iachOther, ach for yzT in range(yz, yz + h): iach = self.IAch(xz, yzT) iachOther = ascr.IAch(0, yzOther) for xzT in range(w): ach = ascr.rgach[iachOther] if ach != achBlankI: self.rgach[iach] = ach iach = iach + 1 iachOther = iachOther + 1 yzOther = yzOther + 1 cdef AstDiff(self, AscrI ascr): assert self.w == ascr.w and self.h == ascr.h cdef int xz = 0 cdef int yz = 0 cdef int xzPred = -1 cdef int achPrev = achInvdI cdef int achBeforeEOL = achInvdI cdef int achOld, achNew, achNewRaw cdef int achSpace = MkAch(' ') rgast = [] for iach in range(self.w * self.h): achOld = self.rgach[iach] achNewRaw = ascr.rgach[iach] if achNewRaw == achBlankI: achNew = achSpace else: achNew = achNewRaw if xz == self.w - 2: achBeforeEOL = achNew if achOld != achNewRaw: if xz == self.w - 1: # Linewrap avoidance algorithm: # when drawing the last character on a line, draw it in the space occupied by the character before. rgast.append("%s%d;%dH" % (esc, yz + 1, xz)) rgast.append(AstFromAch(achNew, achInvdI)) # then, move the cursor back onto the character we just drew, and perform an insert. rgast.append("%sD%s@" % (esc, esc)) # finally, draw the character before the last character in the line. rgast.append(AstFromAch(achBeforeEOL, achNew)) achPrev = achBeforeEOL else: if xz != xzPred: xzPred = xz rgast.append("%s%d;%dH" % (esc, yz + 1, xz + 1)) achPrev = achInvdI rgast.append(AstFromAch(achNew, achPrev)) achPrev = achNew xzPred = xzPred + 1 xz = xz + 1 if xz == self.w: xz = 0 xzPred = -1 yz = yz + 1 return "".join(rgast) cdef Hst(self): rgst = ["
"]
        cdef int achPrev = achInvdI
        cdef int xz = 0
        cdef int ach
        for iach in range(self.w * self.h):
            ach = self.rgach[iach]
            rgst.append(HstFromAch(ach, achPrev))
            achPrev = ach
            xz = xz + 1
            if xz == self.w:
                xz = 0
                rgst.append("
") rgst.append("
") return ''.join(rgst) def FKeyPrintable(key): return type(key) == str and (ord(key) >= 32 and ord(key) <= 126)