Massively improve CursesReporter.
The Slipnet itself turns out to be boring to look at.
More interest is found in the Workspace structures, such as bonds,
groups, and correspondences.
The old behavior of `curses_main.py` is still accessible via
python curses_main.py abc abd xyz --focus-on-slipnet
This commit is contained in:
@ -22,6 +22,7 @@ class Reporter(object):
|
||||
def report_workspace(self, workspace):
|
||||
pass
|
||||
|
||||
|
||||
class Copycat(object):
|
||||
def __init__(self, rng_seed=None, reporter=None):
|
||||
self.coderack = Coderack(self)
|
||||
|
||||
@ -11,6 +11,7 @@ if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--seed', type=int, default=None, help='Provide a deterministic seed for the RNG.')
|
||||
parser.add_argument('--focus-on-slipnet', action='store_true', help='Show the slipnet and coderack, instead of the workspace.')
|
||||
parser.add_argument('initial', type=str, help='A...')
|
||||
parser.add_argument('modified', type=str, help='...is to B...')
|
||||
parser.add_argument('target', type=str, help='...as C is to... what?')
|
||||
@ -18,7 +19,13 @@ if __name__ == '__main__':
|
||||
|
||||
try:
|
||||
window = curses.initscr()
|
||||
copycat = Copycat(reporter=CursesReporter(window), rng_seed=options.seed)
|
||||
copycat = Copycat(
|
||||
reporter=CursesReporter(
|
||||
window,
|
||||
focus_on_slipnet=options.focus_on_slipnet,
|
||||
),
|
||||
rng_seed=options.seed,
|
||||
)
|
||||
copycat.run_forever(options.initial, options.modified, options.target)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@ -1,24 +1,90 @@
|
||||
import curses
|
||||
import time
|
||||
|
||||
from copycat import Reporter
|
||||
from bond import Bond
|
||||
from correspondence import Correspondence
|
||||
from description import Description
|
||||
from group import Group
|
||||
from letter import Letter
|
||||
from rule import Rule
|
||||
|
||||
|
||||
class SafeSubwindow(object):
|
||||
def __init__(self, window, h, w, y, x):
|
||||
self.w = window.derwin(h, w, y, x)
|
||||
|
||||
def addnstr(self, y, x, s, n):
|
||||
self.w.addnstr(y, x, s, n)
|
||||
|
||||
def addstr(self, y, x, s, attr=curses.A_NORMAL):
|
||||
try:
|
||||
self.w.addstr(y, x, s, attr)
|
||||
except Exception as e:
|
||||
if str(e) != 'addstr() returned ERR':
|
||||
raise
|
||||
|
||||
def border(self):
|
||||
self.w.border()
|
||||
|
||||
def erase(self):
|
||||
self.w.erase()
|
||||
|
||||
def getch(self):
|
||||
self.w.nodelay(True) # make getch() non-blocking
|
||||
return self.w.getch()
|
||||
|
||||
def getmaxyx(self):
|
||||
return self.w.getmaxyx()
|
||||
|
||||
def is_vacant(self, y, x):
|
||||
ch_plus_attr = self.w.inch(y, x)
|
||||
if ch_plus_attr == -1:
|
||||
return True # it's out of bounds
|
||||
return (ch_plus_attr & 0xFF) == 0x20
|
||||
|
||||
def refresh(self):
|
||||
self.w.refresh()
|
||||
|
||||
|
||||
class CursesReporter(Reporter):
|
||||
def __init__(self, window):
|
||||
def __init__(self, window, focus_on_slipnet=False):
|
||||
curses.curs_set(0) # hide the cursor
|
||||
curses.noecho() # hide keypresses
|
||||
height, width = window.getmaxyx()
|
||||
slipnetHeight = 10
|
||||
coderackHeight = height - 15
|
||||
if focus_on_slipnet:
|
||||
upperHeight = 10
|
||||
else:
|
||||
upperHeight = 25
|
||||
answersHeight = 5
|
||||
self.temperatureWindow = window.derwin(height, 5, 0, 0)
|
||||
self.slipnetWindow = curses.newwin(slipnetHeight, width-5, 0, 5)
|
||||
self.coderackWindow = curses.newwin(coderackHeight, width-5, slipnetHeight, 5)
|
||||
self.answersWindow = curses.newwin(answersHeight, width-5, slipnetHeight + coderackHeight, 5)
|
||||
for w in [self.temperatureWindow, self.slipnetWindow, self.answersWindow]:
|
||||
w.clear()
|
||||
coderackHeight = height - upperHeight - answersHeight
|
||||
self.focusOnSlipnet = focus_on_slipnet
|
||||
self.temperatureWindow = SafeSubwindow(window, height, 5, 0, 0)
|
||||
self.upperWindow = SafeSubwindow(window, upperHeight, width-5, 0, 5)
|
||||
self.coderackWindow = SafeSubwindow(window, coderackHeight, width-5, upperHeight, 5)
|
||||
self.answersWindow = SafeSubwindow(window, answersHeight, width-5, upperHeight + coderackHeight, 5)
|
||||
for w in [self.temperatureWindow, self.upperWindow, self.answersWindow]:
|
||||
w.erase()
|
||||
w.border()
|
||||
w.refresh()
|
||||
self.answers = {}
|
||||
|
||||
def do_keyboard_shortcuts(self):
|
||||
w = self.temperatureWindow # just a random window
|
||||
ordch = w.getch()
|
||||
if ordch in [ord('P'), ord('p')]:
|
||||
w.addstr(0, 0, 'PAUSE', curses.A_STANDOUT)
|
||||
w.refresh()
|
||||
ordch = None
|
||||
while ordch not in [ord('P'), ord('p'), 27, ord('Q'), ord('q')]:
|
||||
time.sleep(0.1)
|
||||
ordch = w.getch()
|
||||
w.erase()
|
||||
w.border()
|
||||
w.refresh()
|
||||
if ordch in [27, ord('Q'), ord('q')]:
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
def report_answer(self, answer):
|
||||
d = self.answers.setdefault(answer['answer'], {
|
||||
'answer': answer['answer'],
|
||||
@ -84,22 +150,29 @@ class CursesReporter(Reporter):
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
columnWidth = (pageWidth - len('important-object-correspondence-scout (n)')) / (NUMBER_OF_BINS - 1)
|
||||
|
||||
def is_vacant(y, x):
|
||||
return (w.inch(y, x) & 0xFF) == 0x20
|
||||
|
||||
w.erase()
|
||||
for u, string in printable_entries:
|
||||
# Find the highest point on the page where we could place this entry.
|
||||
start_column = (u - 1) * columnWidth
|
||||
end_column = start_column + len(string)
|
||||
for r in range(pageHeight):
|
||||
if all(is_vacant(r, c) for c in xrange(start_column, end_column+20)):
|
||||
if all(w.is_vacant(r, c) for c in xrange(start_column, end_column+20)):
|
||||
w.addstr(r, start_column, string)
|
||||
break
|
||||
w.refresh()
|
||||
|
||||
def slipnode_name_and_attr(self, slipnode):
|
||||
if slipnode.activation == 100:
|
||||
return (slipnode.name.upper(), curses.A_STANDOUT)
|
||||
if slipnode.activation > 50:
|
||||
return (slipnode.name.upper(), curses.A_BOLD)
|
||||
else:
|
||||
return (slipnode.name.lower(), curses.A_NORMAL)
|
||||
|
||||
def report_slipnet(self, slipnet):
|
||||
w = self.slipnetWindow
|
||||
if not self.focusOnSlipnet:
|
||||
return
|
||||
w = self.upperWindow
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
w.erase()
|
||||
w.addstr(1, 2, 'Total: %d slipnodes and %d sliplinks' % (
|
||||
@ -107,25 +180,17 @@ class CursesReporter(Reporter):
|
||||
len(slipnet.sliplinks),
|
||||
))
|
||||
|
||||
def name_and_attr(node):
|
||||
if node.activation == 100:
|
||||
return (node.name.upper(), curses.A_STANDOUT)
|
||||
if node.activation > 50:
|
||||
return (node.name.upper(), curses.A_BOLD)
|
||||
else:
|
||||
return (node.name.lower(), curses.A_NORMAL)
|
||||
|
||||
for c, node in enumerate(slipnet.letters):
|
||||
s, attr = name_and_attr(node)
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
w.addstr(2, 2 * c + 2, s, attr)
|
||||
for c, node in enumerate(slipnet.numbers):
|
||||
s, attr = name_and_attr(node)
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
w.addstr(3, 2 * c + 2, s, attr)
|
||||
row = 4
|
||||
column = 2
|
||||
for node in slipnet.slipnodes:
|
||||
if node not in slipnet.letters + slipnet.numbers:
|
||||
s, attr = name_and_attr(node)
|
||||
s, attr = self.slipnode_name_and_attr(node)
|
||||
if column + len(s) > pageWidth - 1:
|
||||
row += 1
|
||||
column = 2
|
||||
@ -135,11 +200,195 @@ class CursesReporter(Reporter):
|
||||
w.refresh()
|
||||
|
||||
def report_temperature(self, temperature):
|
||||
height = self.temperatureWindow.getmaxyx()[0]
|
||||
self.do_keyboard_shortcuts()
|
||||
w = self.temperatureWindow
|
||||
height = w.getmaxyx()[0]
|
||||
max_mercury = height - 4
|
||||
mercury = max_mercury * temperature.value() / 100.0
|
||||
for i in range(max_mercury):
|
||||
ch = ' ,o%8'[int(4 * min(max(0, mercury - i), 1))]
|
||||
self.temperatureWindow.addstr(max_mercury - i, 1, 3*ch)
|
||||
self.temperatureWindow.addnstr(height - 2, 1, '%3d' % temperature.actual_value, 3)
|
||||
self.temperatureWindow.refresh()
|
||||
w.addstr(max_mercury - i, 1, 3*ch)
|
||||
w.addnstr(height - 2, 1, '%3d' % temperature.actual_value, 3)
|
||||
w.refresh()
|
||||
|
||||
def length_of_workspace_object_depiction(self, o, description_structures):
|
||||
result = len(str(o))
|
||||
if o.descriptions:
|
||||
result += 2
|
||||
result += 2 * (len(o.descriptions) - 1)
|
||||
for d in o.descriptions:
|
||||
s, _ = self.slipnode_name_and_attr(d.descriptor)
|
||||
result += len(s)
|
||||
if d not in description_structures:
|
||||
result += 2
|
||||
result += 1
|
||||
return result
|
||||
|
||||
def depict_workspace_object(self, w, row, column, o, maxImportance, description_structures):
|
||||
if maxImportance != 0.0 and o.relativeImportance == maxImportance:
|
||||
attr = curses.A_BOLD
|
||||
else:
|
||||
attr = curses.A_NORMAL
|
||||
w.addstr(row, column, str(o), attr)
|
||||
column += len(str(o))
|
||||
if o.descriptions:
|
||||
w.addstr(row, column, ' (', curses.A_NORMAL)
|
||||
column += 2
|
||||
for i, d in enumerate(o.descriptions):
|
||||
if i != 0:
|
||||
w.addstr(row, column, ', ', curses.A_NORMAL)
|
||||
column += 2
|
||||
s, attr = self.slipnode_name_and_attr(d.descriptor)
|
||||
if d not in description_structures:
|
||||
s = '[%s]' % s
|
||||
w.addstr(row, column, s, attr)
|
||||
column += len(s)
|
||||
w.addstr(row, column, ')', curses.A_NORMAL)
|
||||
column += 1
|
||||
return column
|
||||
|
||||
def depict_bond(self, w, row, column, bond):
|
||||
slipnet = bond.ctx.slipnet
|
||||
if bond.directionCategory == slipnet.right:
|
||||
s = '-- %s -->' % bond.category.name
|
||||
elif bond.directionCategory == slipnet.left:
|
||||
s = '<-- %s --' % bond.category.name
|
||||
elif bond.directionCategory is None:
|
||||
s = '<-- %s -->' % bond.category.name
|
||||
if isinstance(bond.leftObject, Group):
|
||||
s = 'G' + s
|
||||
if isinstance(bond.rightObject, Group):
|
||||
s = s + 'G'
|
||||
w.addstr(row, column, s, curses.A_NORMAL)
|
||||
return column + len(s)
|
||||
|
||||
def depict_grouping_brace(self, w, firstrow, lastrow, column):
|
||||
if firstrow == lastrow:
|
||||
w.addstr(firstrow, column, '}', curses.A_NORMAL)
|
||||
else:
|
||||
w.addstr(firstrow, column, '\\', curses.A_NORMAL)
|
||||
w.addstr(lastrow, column, '/', curses.A_NORMAL)
|
||||
for r in xrange(firstrow + 1, lastrow):
|
||||
w.addstr(r, column, '|', curses.A_NORMAL)
|
||||
|
||||
def report_workspace(self, workspace):
|
||||
if self.focusOnSlipnet:
|
||||
return
|
||||
slipnet = workspace.ctx.slipnet
|
||||
w = self.upperWindow
|
||||
pageHeight, pageWidth = w.getmaxyx()
|
||||
w.erase()
|
||||
w.addstr(1, 2, '%d objects (%d letters in %d groups), %d other structures (%d bonds, %d correspondences, %d descriptions, %d rules)' % (
|
||||
len(workspace.objects),
|
||||
len([o for o in workspace.objects if isinstance(o, Letter)]),
|
||||
len([o for o in workspace.objects if isinstance(o, Group)]),
|
||||
len(workspace.structures) - len([o for o in workspace.objects if isinstance(o, Group)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Bond)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Correspondence)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Description)]),
|
||||
len([o for o in workspace.structures if isinstance(o, Rule)]),
|
||||
))
|
||||
|
||||
group_objects = {o for o in workspace.objects if isinstance(o, Group)}
|
||||
letter_objects = {o for o in workspace.objects if isinstance(o, Letter)}
|
||||
group_and_letter_objects = group_objects | letter_objects
|
||||
assert set(workspace.objects) == group_and_letter_objects
|
||||
assert group_objects <= set(workspace.structures)
|
||||
|
||||
latent_groups = {o.group for o in workspace.objects if o.group is not None}
|
||||
assert latent_groups <= group_objects
|
||||
assert group_objects <= latent_groups
|
||||
member_groups = {o for g in group_objects for o in g.objectList if isinstance(o, Group)}
|
||||
assert member_groups <= group_objects
|
||||
|
||||
bond_structures = {o for o in workspace.structures if isinstance(o, Bond)}
|
||||
known_bonds = {o.leftBond for o in group_and_letter_objects if o.leftBond is not None}
|
||||
known_bonds |= {o.rightBond for o in group_and_letter_objects if o.rightBond is not None}
|
||||
assert known_bonds == bond_structures
|
||||
|
||||
description_structures = {o for o in workspace.structures if isinstance(o, Description)}
|
||||
latent_descriptions = {d for o in group_and_letter_objects for d in o.descriptions}
|
||||
assert description_structures <= latent_descriptions
|
||||
|
||||
current_rules = set([workspace.rule]) if workspace.rule is not None else set()
|
||||
|
||||
correspondences_between_initial_and_target = {o for o in workspace.structures if isinstance(o, Correspondence)}
|
||||
|
||||
assert set(workspace.structures) == set.union(
|
||||
group_objects,
|
||||
bond_structures,
|
||||
description_structures,
|
||||
current_rules,
|
||||
correspondences_between_initial_and_target,
|
||||
)
|
||||
for g in group_objects:
|
||||
assert g.string in [workspace.initial, workspace.modified, workspace.target]
|
||||
|
||||
row = 2
|
||||
for o in current_rules:
|
||||
w.addstr(row, 2, str(o), curses.A_BOLD)
|
||||
|
||||
for string in [workspace.initial, workspace.modified, workspace.target]:
|
||||
row += 1
|
||||
maxImportance = max(o.relativeImportance for o in group_and_letter_objects if o.string == string)
|
||||
letters_in_string = sorted(
|
||||
(o for o in letter_objects if o.string == string),
|
||||
key=lambda o: o.leftIndex,
|
||||
)
|
||||
groups_in_string = sorted(
|
||||
(o for o in group_objects if o.string == string),
|
||||
key=lambda o: o.leftIndex,
|
||||
)
|
||||
bonds_in_string = sorted(
|
||||
(b for b in bond_structures if b.string == string),
|
||||
key=lambda b: b.leftObject.rightIndex,
|
||||
)
|
||||
assert bonds_in_string == sorted(string.bonds, key=lambda b: b.leftObject.rightIndex)
|
||||
startrow_for_group = {}
|
||||
endrow_for_group = {}
|
||||
|
||||
max_column = 0
|
||||
for letter in letters_in_string:
|
||||
for g in groups_in_string:
|
||||
if g.leftIndex == letter.leftIndex:
|
||||
startrow_for_group[g] = row
|
||||
if g.rightIndex == letter.rightIndex:
|
||||
endrow_for_group[g] = row
|
||||
column = self.depict_workspace_object(w, row, 2, letter, maxImportance, description_structures)
|
||||
row += 1
|
||||
max_column = max(max_column, column)
|
||||
for b in bonds_in_string:
|
||||
if b.leftObject.rightIndex == letter.rightIndex:
|
||||
assert b.rightObject.leftIndex == letter.rightIndex + 1
|
||||
column = self.depict_bond(w, row, 4, b)
|
||||
row += 1
|
||||
max_column = max(max_column, column)
|
||||
|
||||
for group in groups_in_string:
|
||||
start = startrow_for_group[group]
|
||||
end = endrow_for_group[group]
|
||||
# Place this group's graphical depiction.
|
||||
depiction_width = 3 + self.length_of_workspace_object_depiction(group, description_structures)
|
||||
for firstcolumn in xrange(max_column, 1000):
|
||||
lastcolumn = firstcolumn + depiction_width
|
||||
okay = all(
|
||||
w.is_vacant(r, c)
|
||||
for c in xrange(firstcolumn, lastcolumn + 1)
|
||||
for r in xrange(start, end + 1)
|
||||
)
|
||||
if okay:
|
||||
self.depict_grouping_brace(w, start, end, firstcolumn + 1)
|
||||
self.depict_workspace_object(w, (start + end) / 2, firstcolumn + 3, group, maxImportance, description_structures)
|
||||
break
|
||||
|
||||
row += 1
|
||||
column = 2
|
||||
|
||||
for o in correspondences_between_initial_and_target:
|
||||
slipnet = workspace.ctx.slipnet
|
||||
w.addstr(row, column, '%s (%s)' % (str(o), str([m for m in o.conceptMappings if m.label != slipnet.identity])), curses.A_NORMAL)
|
||||
row += 1
|
||||
column = 2
|
||||
|
||||
w.border()
|
||||
w.refresh()
|
||||
|
||||
Reference in New Issue
Block a user