This commit is contained in:
troido 2019-09-18 12:07:58 +02:00
parent 60997eccad
commit 3b9ce53b4e
22 changed files with 24 additions and 819 deletions

6
.editorconfig Normal file
View File

@ -0,0 +1,6 @@
[*.{py,json,hy,yaml}]
charset = utf-8
indent_style = spaces
insert_final_newline = true
indent_size = 4

3
.kateconfig Normal file
View File

@ -0,0 +1,3 @@
kate: indent-pasted-text false; indent-width 4; space-indent true;

View File

@ -1,32 +0,0 @@
import curses
class Colours:
def __init__(self):
self.colours = min(curses.COLORS, 16)
self.pairs = self.colours*self.colours
curses.use_default_colors()
for i in range(0, self.pairs):
curses.init_pair(i, i%self.colours, i//self.colours)
def get(self, fg=0, bg=0):
if self.colours == 16:
return curses.color_pair(fg + bg*self.colours)
elif self.colours == 8:
dfg = fg % 8
dbg = bg % 8
if bg == 8:
dbg = 7
if fg == 8:
dfg = 7
colour = curses.color_pair(dfg + dbg*self.colours)
if fg >= 8 and bg < 8:
colour |= curses.A_BOLD
elif fg < 8 and bg >= 8:
colour |= curses.A_DIM
return colour
else:
return curses.color_pair(0)

View File

@ -1,154 +0,0 @@
import curses
from .field import Field
from .info import Info
from .health import Health
from .inventory import Inventory
from .screen import Screen
from .colours import Colours
from .messages import Messages
from .switcher import Switcher
from .textinput import TextInput
from .widget import Widget
from asciifarm.common.utils import get
SIDEWIDTH = 20
ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
class Display:
def __init__(self, stdscr, charMap, colours=False):
if colours and curses.has_colors and curses.COLORS > 1:
self.colours = Colours()
else:
self.colours = None
self.characters = {}
def parseSprite(sprite):
if isinstance(sprite, str):
return (sprite, None, None)
char = get(sprite, 0, " ")
fg = get(sprite, 1)
bg = get(sprite, 2)
return (char, fg, bg)
for name, sprite in charMap["mapping"].items():
vals = parseSprite(sprite)
if vals:
self.characters[name] = vals
for name, colours in charMap.get("writable", {}).items():
fg = get(colours, 0)
bg = get(colours, 1)
for i in range(min(len(ALPHABET), len(charMap.get("alphabet", [])))):
self.characters[name + '-' + ALPHABET[i]] = (charMap["alphabet"][i], fg, bg)
self.defaultChar = parseSprite(charMap.get("default", "?"))
screen = Screen(self, stdscr, self.colours)
self.screen = screen
self.widgets = {}
self.addWidget(Field((1, 1), charMap.get("charwidth", 1), self.colours), "field")
self.addWidget(Info(), "info")
self.addWidget(Health(
charMap.get("healthfull", ("@",7, 2)),
charMap.get("healthempty", ("-",7, 1))
),
"health")
self.addWidget(Inventory("Inventory"), "inventory")
self.addWidget(Inventory("Ground"), "ground")
self.addWidget(Inventory("Equipment"), "equipment")
#switcher = Switcher([self.widgets["ground"], self.widgets["inventory"], self.widgets["equipment"]], 1)
self.addWidget(Inventory(""), "switch")
self.addWidget(Messages(charMap.get("msgcolours", {})), "msg")
self.addWidget(TextInput(), "textinput")
self.forced = False
def addWidget(self, w, name, winname=None):
if not winname:
winname = name
widget = Widget(w, name)
self.widgets[name] = widget
widget.setWin(winname, self.screen)
def getWidget(self, name):
if name in self.widgets:
return self.widgets[name].getImpl()
else:
return None
def resizeField(self, size):
self.getWidget("field").resize(*size)
self.forced = True
def drawFieldCells(self, cells):
field = self.getWidget("field")
for cell in cells:
(x, y), spriteNames = cell
sprites = [self.getChar(spriteName) for spriteName in spriteNames]
if not len(sprites):
sprites = [self.getChar(" ")]
field.changeCell(x, y, sprites)
def setFieldCenter(self, pos):
self.getWidget("field").setCenter(pos)
def setHealth(self, health, maxHealth):
self.getWidget("health").setHealth(health, maxHealth)
def showInfo(self, infostring):
self.getWidget("info").showString(infostring)
#def setInventory(self, items):
#self.getWidget("inventory").setInventory(items)
#def setEquipment(self, slots):
#self.getWidget("equipment").setInventory(
#sorted([
#slot + ": " + (item if item else "")
#for slot, item in slots.items()
#])
#)
#def setGround(self, items):
#self.getWidget("ground").setInventory(items)
def addMessage(self, message, type):
self.getWidget("msg").addMessage(message, type)
def scrollBack(self, amount, relative=True):
self.getWidget("msg").scroll(amount, relative)
def getChar(self, sprite):
"""This returns the character belonging to some spritename. This does not read a character"""
return self.characters.get(sprite, self.defaultChar)
def setInputString(self, string, cursor):
self.getWidget("textinput").setText(string, cursor)
def update(self):
changed = False
for widget in self.widgets.values():
if self.forced or widget.isChanged():
widget.update()
changed = True
if changed:
self.screen.update()
self.forced = False
def forceUpdate(self):
self.forced = True

View File

@ -1,70 +0,0 @@
import curses
from .widimp import WidImp
class Field(WidImp):
def __init__(self, size=(1,1), charSize=1, colours=False):
self.pad = curses.newpad(size[1]+1, (size[0]+1)*charSize)
self.size = size
self.charSize = charSize
self.center = (0, 0)
self.colours = colours
self.changed = False
self.redraw = False
def resize(self, width, height):
self.size = (width, height)
self.pad.resize(height+1, width*self.charSize)
self.redraw = True
self.change()
def changeCell(self, x, y, sprites):
""" sprites must always have at least one element """
char, colour, bgcolour = sprites[0]
if bgcolour is None:
for (ch, co, bg) in sprites:
if bg is not None:
bgcolour = bg
break
else:
bgcolour = 0
if colour is not None and self.colours:
self.pad.addstr(y, x*self.charSize, " "*self.charSize, self.colours.get(7, 0))
self.pad.addstr(y, x*self.charSize, char, self.colours.get(colour, bgcolour))
else:
self.pad.addstr(y, x*self.charSize, char)
self.change()
def setCenter(self, pos):
self.center = pos
self.change()
def getWidth(self):
return self.size[0]*self.charSize
def getHeight(self):
return self.size[1]
def _roundWidth(self, x):
return x // self.charSize * self.charSize
def update(self, win):
if self.redraw:
win.erase()
win.noutrefresh()
self.redraw = False
width, height = win.getSize()
x, y = win.getPos()
xmax = x + width
ymax = y + height
self.pad.noutrefresh(
max(0, min(self.getHeight()-height, self.center[1] - int(height/2))),
max(0, min(
self._roundWidth(self.getWidth()-width),
self._roundWidth(self.center[0]*self.charSize - int(width/2)))),
y,
x + max(0, (width - self.getWidth()) // 2),
ymax,
xmax)

View File

@ -1,27 +0,0 @@
from .widimp import WidImp
class Health(WidImp):
def __init__(self, char=None, emptyChar=None):
self.char = char or ('@',7,0)
self.emptyChar = emptyChar or ('-',7,0)
self.changed = False
self.health = 0
self.maxHealth = 0
def setHealth(self, health, maxHealth):
self.health = health or 0
self.maxHealth = maxHealth or 0
self.change()
def update(self, win):
width, height = win.getSize()
width -= 1
barEnd = round(self.health/self.maxHealth * width) if self.maxHealth > 0 else 0
win.erase()
win.addLine((0,0),"Health: {}/{}".format(self.health, self.maxHealth)[:width])
win.addLine((0, 1), self.char[0]*barEnd, self.char[1:])
win.addLine((barEnd, 1), self.emptyChar[0]*(width-barEnd), self.emptyChar[1:])
win.noutrefresh()

View File

@ -1,24 +0,0 @@
from .widimp import WidImp
class Info(WidImp):
def __init__(self):
self.changed = False
self.lines = []
self.lastString = None
def showString(self, string):
if string == self.lastString:
return
self.lines = string.split('\n')
self.change()
self.lastString = string
def update(self, win):
width, height = win.getSize()
lines = [line[:width-1] for line in self.lines][:height]
win.erase()
for i, line in enumerate(lines):
win.addLine((0, i), line)
win.noutrefresh()

View File

@ -1,73 +0,0 @@
from asciifarm.common import utils
from .widimp import WidImp
class Inventory(WidImp):
def __init__(self, title, titlebar="{}:", selectorChar="*"):
self.title = title
self.titlebar = titlebar
self.selectorChar = selectorChar
self.items = []
self.selector = 0
def getSelected(self):
return self.selector
def select(self, value, relative=False, modular=False):
invLen = len(self.items)
if relative:
value += self.selector
if modular and invLen:
value %= invLen
if value < 0:
value = 0
if value >= invLen:
value = invLen-1
if value in range(invLen):
self.doSelect(value)
def doSelect(self, value):
self.selector = value
self.change()
def setInventory(self, items):
self.items = items
self.selector = utils.clamp(self.selector, 0, len(items)-1)
self.change()
def getItem(self, num):
return self.items[num]
def getSelectedItem(self):
return self.getItem(self.getSelected())
def setTitle(self, title):
self.title = title
def getNumItems(self):
return len(self.items)
def itemName(self, item):
return item
def update(self, win):
width, height = win.getSize()
height -= 1
selected = self.selector
start = min(selected - height//2, len(self.items)-height)
start = max(start, 0)
end = start + height
win.erase()
win.addLine((0,0), (self.titlebar.format(self.title))[:width])
for i, item in enumerate(self.items[start:end]):
if i + start == selected:
win.addLine((0, i+1), self.selectorChar)
win.addLine((1, i+1), self.itemName(item))
if end < len(self.items):
win.addLine((width-1, height), "+")
if start > 0:
win.addLine((width-1, 1), "-")
win.noutrefresh()

View File

@ -1,56 +0,0 @@
import textwrap
from .widimp import WidImp
class Messages(WidImp):
def __init__(self, colours):
self.changed = False
self.messages = []
self.scrolledBack = 0
self.colours = colours
def addMessage(self, message, type=None):
self.messages.append([message, type])
if self.scrolledBack:
self.scrolledBack += 1
self.change()
def scroll(self, amount, relative=True):
if relative:
self.scrolledBack += amount
else:
self.scrolledBack = amount
self.scrolledBack = max(self.scrolledBack, 0)
self.change()
def update(self, win):
width, height = win.getSize()
if height < 1:
return
lines = []
messages = self.messages
for message, type in messages:
colour = self.colours.get(type, (7,0))
for line in textwrap.wrap(message, width):
lines.append((line, colour))
self.scrolledBack = max(min(self.scrolledBack, len(lines)-height), 0)
moreDown = False
if self.scrolledBack > 0:
lines = lines[:-self.scrolledBack]
moreDown = True
moreUp = False
if len(lines) > height:
moreUp = True
lines = lines[len(lines)-height:]
elif len(lines) < height:
lines = (height-len(lines)) * [("",)] + lines
win.erase()
for i, line in enumerate(lines):
win.addLine((0,i), *line)
if moreUp:
win.addLine((width-1, 0), '-')
if moreDown:
win.addLine((width-1, height-1), '+')
win.noutrefresh()

View File

@ -1,95 +0,0 @@
import curses
from asciifarm.common.utils import clamp
from .window import Window
import signal
class Screen:
def __init__(self, display, stdscr, colours):
self.display = display
try:
curses.curs_set(0)
self.cursorSet = False
except curses.error:
# Not all terminals support this functionality.
# When the error is ignored the screen will look a little uglier,
# A cursor will move around, but that's not terrible
# So in order to keep the game as accesible as possible to everyone, it should be safe to ignore the error.
self.cursorSet = True
# It is probably possible to make sure the cursor is only in a corner of the screen
# but I can't figure out how.
# it seems to ignore all my move commands unless I press a key
# I give up
self.stdscr = stdscr
self.colours = colours
self.setWins()
signal.signal(signal.SIGWINCH, self.updateSize)
def _limitHeight(self, h, y):
return min(h + y, self.height) - y
def setWins(self):
height, width = self.height, self.width = self.stdscr.getmaxyx()
sideW = 20
sideX = width-sideW
msgH = clamp(height // 5, 3, 5)
msgY = height - msgH-1
inputH = 1
inputY = msgY + msgH
healthY = 0
healthH = self._limitHeight(2, healthY)
indexY = healthY + healthH
indexH = self._limitHeight(4, indexY)
listY = indexY + indexH + 1
listH = self._limitHeight(12, listY)
infoY = listY + listH
infoH = self._limitHeight(20, infoY)
lists = self.makeWin(sideX, listY, sideW, listH)
self.windows = {
"field": self.makeWin(0, 0, sideX - 1, msgY),
"msg": self.makeWin(0, msgY, sideX - 1, msgH),
"textinput": self.makeWin(0, inputY, sideX - 1, inputH),
"health": self.makeWin(sideX, healthY, sideW, healthH),
"switch": self.makeWin(sideX, indexY, sideW, indexH),
"ground": lists,
"inventory": lists,
"equipment": lists,
"info": self.makeWin(sideX, infoY, sideW, infoH)
}
def makeWin(self, x, y, width, height):
if width < 1 or height < 1:
win = None
else:
win = curses.newwin(height, width, y, x)
return Window(win, self.colours)
def getWin(self, name):
return self.windows.get(name, None)
def updateSize(self, *args):
curses.endwin()
curses.initscr()
self.setWins()
self.stdscr.clear()
self.display.forceUpdate()
def update(self):
curses.doupdate()
def getWidth(self):
return self.width
def getHeight(self):
return self.height

View File

@ -1,29 +0,0 @@
from .inventory import Inventory
class Switcher(Inventory):
"""An area that can contain multiple widgets but only shows one at a time.
There is a function to switch between the displayed widgets.
"""
#def __init__(self, widgets, initial=0):
#Inventory.__init__(self, "", "", "=")
#self.setInventory(widgets)
#for wid in widgets:
#wid.hidden = True
#self.select(initial)
#def doSelect(self, value):
#self.getSelectedItem().hidden = True
#self.selector = value
#self.change()
#newWid = self.getSelectedItem()
#newWid.hidden = False
#newWid.change()
def itemName(self, item):
return item.getImpl().title

View File

@ -1,22 +0,0 @@
import curses
from .widimp import WidImp
class TextInput(WidImp):
def __init__(self):
self.text = ""
self.cursor = -1
def setText(self, text, cursor):
self.text = text
self.cursor = cursor
self.change()
def update(self, win):
width, height = win.getSize()
win.erase()
win.addLine((0, 0), self.text[:width])
if self.cursor >= 0 and self.cursor <= len(self.text):
win.setAttr((min(self.cursor, width-1), 0), curses.A_REVERSE)
win.noutrefresh()

View File

@ -1,35 +0,0 @@
class Widget:
def __init__(self, impl, name=None):
self.impl = impl
self.win = None
self.screen = None
self.changed = False
self.hidden = False
self.name = name
self.impl.setWidget(self)
def setWin(self, win, screen):
self.win = win
self.screen = screen
def getWin(self):
return self.win and self.screen and self.screen.getWin(self.win)
def getImpl(self):
return self.impl
def change(self):
self.changed = True
def isChanged(self):
return self.changed
def update(self):
if not self.getWin() or self.hidden:
return
self.impl.update(self.getWin())
self.changed = False

View File

@ -1,18 +0,0 @@
class WidImp:
"""widget implementation"""
_widget = None
def setWidget(self, widget):
self._widget = widget
self.change()
def change(self):
if self._widget is not None:
self._widget.change()
def update(self, win):
pass

View File

@ -1,74 +0,0 @@
import curses
class Window:
""" Small wrapper around curses windows """
def __init__(self, win, colours=None):
self.win = win
self.colours = colours
def getSize(self):
if not self.win:
return (0, 0)
height, width = self.win.getmaxyx()
return (width, height)
def getPos(self):
if not self.win:
return (0, 0)
y, x = self.win.getparyx()
return (x, y)
def addLine(self, pos, string, colour=(0,0)):
"""Draw a string that does not contain newlines or characters with larger width
long lines are cropped to fit in the window"""
if not self.win:
return
x, y = pos
width, height = self.getSize()
string = string[:width-x]
drawLast = None
if self.colours:
self._addstr(y, x, string, self.colours.get(*colour))
else:
self._addstr(y, x, string)
def _addstr(self, y, x, string, *args):
if not self.win:
return
width, height = self.getSize()
if y == height-1 and x+len(string) == width:
if len(string) > 1:
self.win.addstr(y, x, string[:-1], *args)
try:
self.win.addstr(height-1, width-1, string[-1], *args)
except curses.error:
# ncurses has a weird problem:
# it always raises an error when drawing to the last character in the window
# it draws first and then raises the error
# therefore to draw in the last place of the window the last character needs to be ingored
# other solutions might be possible, but are more hacky
pass
else:
self.win.addstr(y, x, string, *args)
def erase(self):
if self.win:
self.win.erase()
def noutrefresh(self):
if self.win:
self.win.noutrefresh()
def setAttr(self, pos, attr, num=1):
if self.win:
x, y = pos
self.win.chgat(y, x, num, attr)

View File

@ -17,7 +17,7 @@ from .inputhandler import InputHandler
class Client:
def __init__(self, display, name, connection, keybindings, logFile=None):
#self.stdscr = stdscr
self.display = display
self.name = name
self.keepalive = True
@ -54,13 +54,9 @@ class Client:
self.queue.put(("error", error))
def getInput(self):
#try:
while True:
key = ratuil.inputs.get_key()
#key = self.stdscr.getch()
self.queue.put(("input", key))
#except Exception as e:
#self.queue.put(("error", e))
def close(self, msg=None):
self.keepalive = False
@ -112,23 +108,14 @@ class Client:
if maxHealth is None:
self.log("You have died. Restart the client to respawn")
if msgType == "inventory":
self.display.inventory.setItems(msg[1])
#invbox = self.display.getWidget("inventory")
#invbox.setInventory(self.inventory.items)
#invbox.select(self.inventory.selector)
#self.display.setInventory(msg[1])
self.display.inventory.setItems(msg[1])
if msgType == "equipment":
#self.display.setEquipment(msg[1])
self.display.equipment.setItems(msg[1])
#eqbox = self.display.getWidget("equipment")
#eqbox.setInventory(self.inventory.items)
#eqbox.select(self.equipment.selector)
if msgType == "ground":
#self.display.setGround(msg[1])
self.display.ground.setItems(msg[1])
#grbox = self.display.getWidget("ground")
#grbox.setInventory(self.ground.items)
#grbox.select(self.ground.selector)
if msgType == "message":
self.log(*msg[1:])
if msgType == "options":

View File

@ -1,10 +1,7 @@
#import curses
#import curses.ascii
=
import string
from .commandhandler import CommandHandler, InvalidCommandException
#from .keynames import nameFromKey
import ratuil.inputs as inp
@ -23,7 +20,7 @@ class InputHandler:
def onInput(self, key):
if not self.typing:
keyName = key#nameFromKey(key)
keyName = key
if keyName in self.keybindings:
self.commandHandler.execute(self.keybindings[keyName])
else:
@ -61,36 +58,36 @@ class InputHandler:
self.client.display.setInputString(self.string, self.cursor if self.typing else -1)
def addKey(self, key):
if key in string.printable:#curses.ascii.isprint(key):
if key in string.printable:
self.string = self.string[:self.cursor] + key + self.string[self.cursor:]
self.cursor += 1
elif key == inp.BACKSPACE: #== curses.KEY_BACKSPACE or key == curses.ascii.BS or key == curses.ascii.DEL:
elif key == inp.BACKSPACE:
self.string = self.string[:self.cursor-1] + self.string[self.cursor:]
self.cursor = max(self.cursor - 1, 0)
elif key == inp.RIGHT:#curses.KEY_RIGHT:
elif key == inp.RIGHT:
self.cursor = min(self.cursor + 1, len(self.string))
elif key == inp.LEFT:#curses.KEY_LEFT:
elif key == inp.LEFT:
self.cursor = max(self.cursor - 1, 0)
elif key == inp.DELETE:#curses.KEY_DC:
elif key == inp.DELETE:
self.string = self.string[:self.cursor] + self.string[self.cursor+1:]
elif key == inp.HOME:#curses.KEY_HOME:
elif key == inp.HOME:
self.cursor = 0
elif key == inp.END:#curses.KEY_END:
elif key == inp.END:
self.cursor = len(self.string)
elif key == inp.ESCAPE:#curses.ascii.ESC or key == curses.KEY_DL:
elif key == inp.ESCAPE:
# throw away entered string and go back to game
self.typing = False
self.string = ""
self.cursor = 0
elif key == inp.ENTER:#curses.ascii.LF or key == curses.ascii.CR:
elif key == inp.ENTER:
# process entered string and reset it
message = self.string
self.string = ""
self.cursor = 0
self.typing = False
self.processString(message)
elif key == "^I":#curses.ascii.TAB:
elif key == "^I": # tab
# return to game but keep entered string
self.typing = False

View File

@ -1,15 +0,0 @@
import curses
prenamed = {
10: "NEWLINE"
}
def nameFromKey(key):
if key in prenamed:
return prenamed[key]
try:
keyname = curses.keyname(key)
except ValueError:
return None
return str(keyname, "utf-8")

View File

@ -6,6 +6,7 @@ import sys
import termios
import tty
import signal
#import os
from .connection import Connection
from .gameclient import Client
@ -34,27 +35,6 @@ def main(argv=None):
oldterm = termios.tcgetattr(fd)
try:
# Initialize curses
#stdscr = curses.initscr()
# Turn off echoing of keys, and enter cbreak mode,
# where no buffering is performed on keyboard input
#curses.noecho()
#curses.cbreak()
## In keypad mode, escape sequences for special keys
## (like the cursor keys) will be interpreted and
## a special value like curses.KEY_LEFT will be returned
#stdscr.keypad(1)
# Start color, too. Harmless if the terminal doesn't have
# color; user can test with has_color() later on. The try/catch
# works around a minor bit of over-conscientiousness in the curses
# module -- the error return from C start_color() is ignorable.
#try:
#curses.start_color()
#except:
#pass
tty.setraw(sys.stdin)
Screen.default.hide_cursor()
@ -73,11 +53,6 @@ def main(argv=None):
closeMessage = client.closeMessage
finally:
## Set everything back to normal
#if 'stdscr' in locals():
#stdscr.keypad(0)
#curses.echo()
#curses.nocbreak()
#curses.endwin()
termios.tcsetattr(fd, termios.TCSADRAIN, oldterm)
Screen.default.finalize()

View File

@ -63,7 +63,6 @@ class Display:
self.ground = ListSelector(self.getWidget("ground"))
self.switch = ListSelector(self.getWidget("switchtitles"))
# it is important that these lists have the same order!
self.switch.setItems(["inventory", "equipment", "ground"])
self.menus = {
"inventory": self.inventory,
@ -76,14 +75,9 @@ class Display:
def getWidget(self, name):
return self.layout.get(name)
#if name in self.widgets:
#return self.widgets[name].getImpl()
#else:
#return None
def resizeField(self, size):
self.getWidget("field").set_size(*size)
#self.forced = True
def drawFieldCells(self, cells):
field = self.getWidget("field")

View File

@ -1,33 +0,0 @@
from .listselector import ListSelector
class SwitchSelector(ListSelector):
#def setItems(self, items):
#super().setItems(items)
#self.updateVisibility()
#def updateVisibility(self):
#pass
##for i, (_menu, widget, _title) in enumerate(self.items):
##if i == self.selector:
##widget.hidden = False
##widget.change()
##else:
##widget.hidden = True
def doSelect(self, value):
#self.getSelectedItem().widget.hidden = True
super().doSelect(value)
#self.updateVisibility()
#self.getSelectedItem().widimp.change()
#newWid.hidden = False
#newWid.change()
def itemName(self, item):
_menu, _widget, title = item
return title