Merge pull request #21 from jmdejong/ratuil

Use ratuil instead of my old hardcoded ncurses setup
This commit is contained in:
jmdejong 2019-09-20 10:25:47 +02:00 committed by GitHub
commit 10bdb33255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 343 additions and 805 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

@ -95,40 +95,40 @@ class CommandHandler:
self.client.inputHandler.startTyping(startText)
def selectWidget(self, value, relative=False, modular=False):
self.client.display.getWidget("switch").select(value, relative, modular)
self.client.display.selectMenu(value, relative, modular)
def selectItem(self, value, relative=False, modular=False):
self.client.display.getWidget("switch").getSelectedItem().getImpl().select(value, relative, modular)
self.client.display.selectItem(None, value, relative, modular)
def actWithSelected(self, action, widget):
self.input([action, self.client.display.getWidget(widget).getSelected()])
def actWithSelected(self, action, menu):
self.input([action, self.client.display.getSelectedItem(menu).getSelected()])
def useSelected(self):
widget = self.client.display.getWidget("switch").getSelectedItem()
selected = widget.getImpl().getSelected()
if widget.name in ("inventory", "equipment"):
menu = self.client.display.getSelectedMenu()
selected = self.client.display.getSelectedItem(menu)
if menu in ("inventory", "equipment"):
action = "use"
elif widget.name == "ground":
elif menu == "ground":
action = "interact",
else:
return
self.input([action, selected])
def unUseSelected(self):
widget = self.client.display.getWidget("switch").getSelectedItem()
selected = widget.getImpl().getSelected()
if widget.name == "inventory":
menu = self.client.display.getSelectedMenu()
selected = self.client.display.getSelectedItem(menu)
if menu == "inventory":
action = "drop"
elif widget.name == "equipment":
elif menu == "equipment":
action = "unequip"
else:
return
self.input([action, selected])
def takeSelected(self):
widget = self.client.display.getWidget("switch").getSelectedItem()
selected = widget.getImpl().getSelected()
if widget.name == "ground":
menu = self.client.display.getSelectedMenu()
selected = self.client.display.getSelectedItem(menu)
if menu == "ground":
action = "take"
else:
return

174
asciifarm/client/display.py Normal file
View File

@ -0,0 +1,174 @@
import os
from ratuil.layout import Layout
from ratuil.bufferedscreen import BufferedScreen as Screen
#from ratuil.screen import Screen
from ratuil.textstyle import TextStyle
from asciifarm.common.utils import get
from .listselector import ListSelector
SIDEWIDTH = 20
ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
class Display:
def __init__(self, charMap):
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", "?"))
self.messageColours = charMap.get("msgcolours", {})
fname = os.path.join(os.path.dirname(__file__), "layout.xml")
self.layout = Layout.from_xml_file(fname)
self.layout.get("field").set_char_size(charMap.get("charwidth", 1))
self.screen = Screen()
self.screen.clear()
self.layout.set_target(self.screen)
self.layout.update()
# temporary, until these have a better place
self.inventory = ListSelector(self.getWidget("inventory"))
self.inventory._debug_name = "inventory"
self.equipment = ListSelector(self.getWidget("equipment"))
self.equipment._debug_name = "equipment"
self.ground = ListSelector(self.getWidget("ground"))
self.ground._debug_name = "ground"
self.switch = ListSelector(self.getWidget("switchtitles"))
self.switch._debug_name = "switch"
self.switch.setItems(["inventory", "equipment", "ground"])
self.menus = {
"inventory": self.inventory,
"equipment": self.equipment,
"ground": self.ground
}
self.layout.get("switch").select(0)
def getWidget(self, name):
return self.layout.get(name)
def resizeField(self, size):
self.getWidget("field").set_size(*size)
def drawFieldCells(self, cells):
field = self.getWidget("field")
for cell in cells:
(x, y), spriteNames = cell
if not len(spriteNames):
char, fg, bg = self.getChar(' ')
else:
char, fg, bg = self.getChar(spriteNames[0])
for spriteName in spriteNames[1:]:
if bg is not None:
break
_char, _fg, bg = self.getChar(spriteName)
field.change_cell(x, y, char, TextStyle(fg, bg))
def setFieldCenter(self, pos):
self.getWidget("field").set_center(*pos)
def setHealth(self, health, maxHealth):
if health is None:
health = 0
if maxHealth is None:
maxHealth = 0
self.getWidget("health").set_total(maxHealth)
self.getWidget("health").set_filled(health)
def showInfo(self, infostring):
self.getWidget("info").set_text(infostring)
def selectMenu(self, *args, **kwargs):
self.switch.select(*args, **kwargs)
self.layout.get("switch").select(self.getSelectedMenu())
def getSelectedMenu(self):
return self.switch.getSelectedItem()
def getSelectedItem(self, menu=None):
return self._getMenu(menu).getSelected()
def selectItem(self, menu=None, *args, **kwargs):
self._getMenu(menu).select(*args, **kwargs)
def _getMenu(self, name=None):
if name is None:
name = self.getSelectedMenu()
name = name.casefold()
return self.menus[name]
def setInventory(self, items):
self.inventory.setItems(items)
def setEquipment(self, slots):
self.equipment.setItems(
sorted([
slot + ": " + (item if item else "")
for slot, item in slots.items()
])
)
def setGround(self, items):
self.ground.setItems(items)
def addMessage(self, message, msgtype=None):
if msgtype is not None:
style = TextStyle(*self.messageColours.get(msgtype, (7,0)))
else:
style = None
self.getWidget("msg").add_message(message, style)
def log(self, message):
self.addMessage(str(message))
def scrollBack(self, amount, relative=True):
self.getWidget("msg").scroll(amount, relative)
def setInputString(self, string, cursor):
self.getWidget("textinput").set_text(string, cursor)
def update(self):
self.layout.update()
self.screen.update()
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 update_size(self):
self.screen.reset()

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(switcher, "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

@ -1,4 +1,4 @@
#! /usr/bin/python3
import os
import sys
@ -10,13 +10,14 @@ import argparse
import string
from queue import Queue
import ratuil.inputs
from .inputhandler import InputHandler
class Client:
def __init__(self, stdscr, display, name, connection, keybindings, logFile=None):
self.stdscr = stdscr
def __init__(self, display, name, connection, keybindings, logFile=None):
self.display = display
self.name = name
self.keepalive = True
@ -54,7 +55,7 @@ class Client:
def getInput(self):
while True:
key = self.stdscr.getch()
key = ratuil.inputs.get_key()
self.queue.put(("input", key))
def close(self, msg=None):
@ -138,12 +139,19 @@ class Client:
if action[0] == "message":
self.update(action[1])
elif action[0] == "input":
if action[1] == "^C":
raise KeyboardInterrupt
self.inputHandler.onInput(action[1])
elif action[0] == "error":
raise action[1]
elif action[0] == "sigwinch":
self.display.update_size()
else:
raise Exception("invalid action in queue")
def onSigwinch(self, signum, frame):
self.queue.put(("sigwinch", (signum, frame)))

View File

@ -1,9 +1,9 @@
import curses
import curses.ascii
import string
from .commandhandler import CommandHandler, InvalidCommandException
from .keynames import nameFromKey
import ratuil.inputs as inp
class InputHandler:
@ -20,7 +20,7 @@ class InputHandler:
def onInput(self, key):
if not self.typing:
keyName = nameFromKey(key)
keyName = key
if keyName in self.keybindings:
self.commandHandler.execute(self.keybindings[keyName])
else:
@ -58,36 +58,36 @@ class InputHandler:
self.client.display.setInputString(self.string, self.cursor if self.typing else -1)
def addKey(self, key):
if curses.ascii.isprint(key):
self.string = self.string[:self.cursor] + chr(key) + self.string[self.cursor:]
if key in string.printable:
self.string = self.string[:self.cursor] + key + self.string[self.cursor:]
self.cursor += 1
elif key == 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 == curses.KEY_RIGHT:
elif key == inp.RIGHT:
self.cursor = min(self.cursor + 1, len(self.string))
elif key == curses.KEY_LEFT:
elif key == inp.LEFT:
self.cursor = max(self.cursor - 1, 0)
elif key == curses.KEY_DC:
elif key == inp.DELETE:
self.string = self.string[:self.cursor] + self.string[self.cursor+1:]
elif key == curses.KEY_HOME:
elif key == inp.HOME:
self.cursor = 0
elif key == curses.KEY_END:
elif key == inp.END:
self.cursor = len(self.string)
elif key == 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 == 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 == 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

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<hbox>
<vbox width="20" align="right">
<bar id="health" height="2" full-char="#" empty-char="_" full-style="fg:2; bg:2" empty-style="fg:1; bg: 1;"></bar>
<listing id="switchtitles" height="0"></listing>
<switchbox id="switch" height="50%">
<vbox key="inventory">
<textbox height="1">Inventory:</textbox>
<listing id="inventory">
milk
eggs
bread
</listing>
</vbox>
<vbox key="equipment">
<textbox height="1">Equipment:</textbox>
<listing id="equipment">
cotton underwear
cotton shirt
jeans
friendship bracelet
</listing>
</vbox>
<vbox key="ground">
<textbox height="1">Ground:</textbox>
<listing id="ground">
concrete
</listing>
</vbox>
</switchbox>
<textbox id="info"></textbox>
</vbox>
<vbox>
<textinput id="textinput" align="bottom" height="1">hello</textinput>
<log id="msg" align="bottom" height="20%%">
Welcome to asciifarm
</log>
<field id="field" char-size="1"></field>
</vbox>
</hbox>

View File

@ -0,0 +1,49 @@
from asciifarm.common import utils
class ListSelector:
def __init__(self, widget):
self.widget = widget
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.widget.select(value)
def setItems(self, items):
self.items = items
self.selector = utils.clamp(self.selector, 0, len(items)-1)
self.widget.set_items([self.itemName(item) for item in self.items])
self.widget.select(self.selector)
def getItem(self, num):
return self.items[num]
def getSelectedItem(self):
return self.getItem(self.getSelected())
def getNumItems(self):
return len(self.items)
def itemName(self, item):
return item

View File

@ -61,8 +61,6 @@ def loadCharmap(name):
writable = {}
default = None
charwidth = 1
healthfull = None
healthempty = None
alphabet = ""
msgcolours = {}
@ -71,8 +69,6 @@ def loadCharmap(name):
writable.update(template.get("writable", {}))
default = template.get("default", default)
charwidth = template.get("charwidth", charwidth)
healthfull = template.get("healthfull", healthfull)
healthempty = template.get("healthempty", healthempty)
alphabet = template.get("alphabet", alphabet)
msgcolours.update(template.get("msgcolours", {}))
return {
@ -80,8 +76,6 @@ def loadCharmap(name):
"writable": writable,
"default": default,
"charwidth": charwidth,
"healthfull": healthfull,
"healthempty": healthempty,
"alphabet": alphabet,
"msgcolours": msgcolours
}

View File

@ -1,13 +1,18 @@
#! /usr/bin/python3
import curses
import json
import os
import getpass
import sys
import termios
import tty
import signal
#import os
from .connection import Connection
from .gameclient import Client
from .display.display import Display
from .display import Display
from .parseargs import parse_args
from ratuil.screen import Screen
def main(argv=None):
@ -24,33 +29,19 @@ def main(argv=None):
error = None
closeMessage = None
os.environ.setdefault("ESCDELAY", "25")
#os.environ.setdefault("ESCDELAY", "25")
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
try:
# Initialize curses
stdscr = curses.initscr()
tty.setraw(sys.stdin)
Screen.default.hide_cursor()
# 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
display = Display(stdscr, characters, colours)
client = Client(stdscr, display, name, connection, keybindings, logfile)
display = Display(characters)
client = Client(display, name, connection, keybindings, logfile)
signal.signal(signal.SIGWINCH, client.onSigwinch)
try:
client.start()
except KeyboardInterrupt:
@ -61,12 +52,9 @@ def main(argv=None):
error = e
closeMessage = client.closeMessage
finally:
# Set everything back to normal
if 'stdscr' in locals():
stdscr.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin()
## Set everything back to normal
termios.tcsetattr(fd, termios.TCSADRAIN, oldterm)
Screen.default.finalize()
if error is not None:

View File

@ -4,10 +4,10 @@
"s": ["move", "south"],
"d": ["move", "east"],
"a": ["move", "west"],
"KEY_UP": ["move", "north"],
"KEY_DOWN": ["move", "south"],
"KEY_RIGHT": ["move", "east"],
"KEY_LEFT": ["move", "west"],
"up": ["move", "north"],
"down": ["move", "south"],
"right": ["move", "east"],
"left": ["move", "west"],
"k": ["move", "north"],
"j": ["move", "south"],
"l": ["move", "east"],
@ -33,9 +33,9 @@
"D": ["input", ["attack", "east"]],
"A": ["input", ["attack", "west"]],
"t": ["runinput"],
"NEWLINE": ["runinput"],
"KEY_PPAGE": ["scrollchat", 1],
"KEY_NPAGE": ["scrollchat", -1],
"enter": ["runinput"],
"pageup": ["scrollchat", 1],
"pagedown": ["scrollchat", -1],
"/": ["runinput", "/"]
},
"help": "Controls:\n wasd or arrows:\n Move around\n e: Grab\n q: Drop/unequip\n selected\n r: Interact\n f: Attack\n t: Chat\n E: Use selected\n Q: Take selected\n xc: select item\n vb: select menu\n ctrl-c: close client"