diff options
author | Jeff Quast <jquast@io.com> | 2015-04-15 00:32:57 -0700 |
---|---|---|
committer | Jeff Quast <jquast@io.com> | 2015-04-15 00:32:57 -0700 |
commit | 244e24ff414312403f49eba1ef92d1b308e2c092 (patch) | |
tree | 7e18b098ef0969313f27719ed9a0b98f5aa78ac7 | |
parent | 0ba17756a2292efde716e60255fea23e1d45a4d0 (diff) | |
download | blessings-244e24ff414312403f49eba1ef92d1b308e2c092.tar.gz |
Fix all bin/*.py and add docs/examples.rst
-rw-r--r-- | .prospector.yaml | 1 | ||||
-rwxr-xr-x | bin/editor.py | 256 | ||||
-rwxr-xr-x | bin/keymatrix.py | 173 | ||||
-rwxr-xr-x | bin/on_resize.py | 60 | ||||
-rwxr-xr-x | bin/progress_bar.py | 14 | ||||
-rwxr-xr-x | bin/tprint.py | 25 | ||||
-rwxr-xr-x | bin/worms.py | 216 | ||||
-rw-r--r-- | docs/examples.rst | 68 | ||||
-rw-r--r-- | docs/index.rst | 1 |
9 files changed, 491 insertions, 323 deletions
diff --git a/.prospector.yaml b/.prospector.yaml index c32f978..e49da65 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -6,7 +6,6 @@ ignore-patterns: - ^docs/ - ^build/ # ignore these, their quality does not so much matter. - - ^bin/ - ^blessings/tests/ - ^tools/ # not maintained diff --git a/bin/editor.py b/bin/editor.py index dc8cde2..f3bebbd 100755 --- a/bin/editor.py +++ b/bin/editor.py @@ -1,152 +1,86 @@ -#!/usr/bin/env python3 -# Dumb full-screen editor. It doesn't save anything but to the screen. -# -# "Why wont python let me read memory -# from screen like assembler? That's dumb." -hellbeard -# -# This program makes example how to deal with a keypad for directional -# movement, with both numlock on and off. +#!/usr/bin/env python +""" +A Dumb full-screen editor. + +This program demonstrates how to deal with the keypad for directional movement. + +It is a very dumb and naive full-screen editor. It can be saved to a file +using ^S, which demonstrates using keystroke_input with parameter raw=True. +""" +# My favorite quote, from a former assembly programmer learning +# python with blessings, "Why wont python let me read memory +# from screen like assembler? That's dumb." -xzip!impure + from __future__ import division, print_function import collections import functools +import string from blessings import Terminal - -echo = lambda text: ( - functools.partial(print, end='', flush=True)(text)) - -echo_yx = lambda cursor, text: ( - echo(cursor.term.move(cursor.y, cursor.x) + text)) +# python 2/3 compatibility, provide 'echo' function as an +# alias for "print without newline and flush" +try: + # pylint: disable=invalid-name + # Invalid constant name "echo" + echo = functools.partial(print, end='', flush=True) + echo(u'') +except TypeError: + # TypeError: 'flush' is an invalid keyword argument for this function + import sys + + def echo(text): + """Display ``text`` and flush output.""" + sys.stdout.write(u'{}'.format(text)) + sys.stdout.flush() + +def echo_yx(cursor, text): + """Move to ``cursor`` and display ``text``.""" + echo(cursor.term.move(cursor.y, cursor.x) + text) Cursor = collections.namedtuple('Point', ('y', 'x', 'term')) -above = lambda csr, n: ( - Cursor(y=max(0, csr.y - n), - x=csr.x, - term=csr.term)) - -below = lambda csr, n: ( - Cursor(y=min(csr.term.height - 1, csr.y + n), - x=csr.x, - term=csr.term)) - -right_of = lambda csr, n: ( - Cursor(y=csr.y, - x=min(csr.term.width - 1, csr.x + n), - term=csr.term)) - -left_of = lambda csr, n: ( - Cursor(y=csr.y, - x=max(0, csr.x - n), - term=csr.term)) - -home = lambda csr: ( - Cursor(y=csr.y, - x=0, - term=csr.term)) - -end = lambda csr: ( - Cursor(y=csr.y, - x=csr.term.width - 1, - term=csr.term)) - -bottom = lambda csr: ( - Cursor(y=csr.term.height - 1, - x=csr.x, - term=csr.term)) - -top = lambda csr: ( - Cursor(y=1, - x=csr.x, - term=csr.term)) - -center = lambda csr: Cursor( - csr.term.height // 2, - csr.term.width // 2, - csr.term) - - -lookup_move = lambda inp_code, csr, term: { - # arrows, including angled directionals - csr.term.KEY_END: below(left_of(csr, 1), 1), - csr.term.KEY_KP_1: below(left_of(csr, 1), 1), - - csr.term.KEY_DOWN: below(csr, 1), - csr.term.KEY_KP_2: below(csr, 1), - - csr.term.KEY_PGDOWN: below(right_of(csr, 1), 1), - csr.term.KEY_LR: below(right_of(csr, 1), 1), - csr.term.KEY_KP_3: below(right_of(csr, 1), 1), - - csr.term.KEY_LEFT: left_of(csr, 1), - csr.term.KEY_KP_4: left_of(csr, 1), - - csr.term.KEY_CENTER: center(csr), - csr.term.KEY_KP_5: center(csr), - - csr.term.KEY_RIGHT: right_of(csr, 1), - csr.term.KEY_KP_6: right_of(csr, 1), - - csr.term.KEY_HOME: above(left_of(csr, 1), 1), - csr.term.KEY_KP_7: above(left_of(csr, 1), 1), - - csr.term.KEY_UP: above(csr, 1), - csr.term.KEY_KP_8: above(csr, 1), - - csr.term.KEY_PGUP: above(right_of(csr, 1), 1), - csr.term.KEY_KP_9: above(right_of(csr, 1), 1), - - # shift + arrows - csr.term.KEY_SLEFT: left_of(csr, 10), - csr.term.KEY_SRIGHT: right_of(csr, 10), - csr.term.KEY_SDOWN: below(csr, 10), - csr.term.KEY_SUP: above(csr, 10), - - # carriage return - csr.term.KEY_ENTER: home(below(csr, 1)), -}.get(inp_code, csr) - - def readline(term, width=20): - # a rudimentary readline function - string = u'' + """A rudimentary readline implementation.""" + text = u'' while True: - inp = term.inkey() + inp = term.keystroke() if inp.code == term.KEY_ENTER: break elif inp.code == term.KEY_ESCAPE or inp == chr(3): - string = None + text = None break - elif not inp.is_sequence and len(string) < width: - string += inp + elif not inp.is_sequence and len(text) < width: + text += inp echo(inp) elif inp.code in (term.KEY_BACKSPACE, term.KEY_DELETE): - string = string[:-1] - echo('\b \b') - return string + text = text[:-1] + echo(u'\b \b') + return text def save(screen, fname): + """Save screen contents to file.""" if not fname: return - with open(fname, 'w') as fp: + with open(fname, 'w') as fout: cur_row = cur_col = 0 for (row, col) in sorted(screen): char = screen[(row, col)] while row != cur_row: cur_row += 1 cur_col = 0 - fp.write(u'\n') + fout.write(u'\n') while col > cur_col: cur_col += 1 - fp.write(u' ') - fp.write(char) + fout.write(u' ') + fout.write(char) cur_col += 1 - fp.write(u'\n') + fout.write(u'\n') def redraw(term, screen, start=None, end=None): + """Redraw the screen.""" if start is None and end is None: echo(term.clear) start, end = (Cursor(y=min([y for (y, x) in screen or [(0, 0)]]), @@ -169,20 +103,100 @@ def redraw(term, screen, start=None, end=None): # just write past last one echo(screen[row, col]) - def main(): + """Program entry point.""" + above = lambda csr, n: ( + Cursor(y=max(0, csr.y - n), + x=csr.x, + term=csr.term)) + + below = lambda csr, n: ( + Cursor(y=min(csr.term.height - 1, csr.y + n), + x=csr.x, + term=csr.term)) + + right_of = lambda csr, n: ( + Cursor(y=csr.y, + x=min(csr.term.width - 1, csr.x + n), + term=csr.term)) + + left_of = lambda csr, n: ( + Cursor(y=csr.y, + x=max(0, csr.x - n), + term=csr.term)) + + home = lambda csr: ( + Cursor(y=csr.y, + x=0, + term=csr.term)) + + end = lambda csr: ( + Cursor(y=csr.y, + x=csr.term.width - 1, + term=csr.term)) + + bottom = lambda csr: ( + Cursor(y=csr.term.height - 1, + x=csr.x, + term=csr.term)) + + center = lambda csr: Cursor( + csr.term.height // 2, + csr.term.width // 2, + csr.term) + + lookup_move = lambda inp_code, csr, term: { + # arrows, including angled directionals + csr.term.KEY_END: below(left_of(csr, 1), 1), + csr.term.KEY_KP_1: below(left_of(csr, 1), 1), + + csr.term.KEY_DOWN: below(csr, 1), + csr.term.KEY_KP_2: below(csr, 1), + + csr.term.KEY_PGDOWN: below(right_of(csr, 1), 1), + csr.term.KEY_LR: below(right_of(csr, 1), 1), + csr.term.KEY_KP_3: below(right_of(csr, 1), 1), + + csr.term.KEY_LEFT: left_of(csr, 1), + csr.term.KEY_KP_4: left_of(csr, 1), + + csr.term.KEY_CENTER: center(csr), + csr.term.KEY_KP_5: center(csr), + + csr.term.KEY_RIGHT: right_of(csr, 1), + csr.term.KEY_KP_6: right_of(csr, 1), + + csr.term.KEY_HOME: above(left_of(csr, 1), 1), + csr.term.KEY_KP_7: above(left_of(csr, 1), 1), + + csr.term.KEY_UP: above(csr, 1), + csr.term.KEY_KP_8: above(csr, 1), + + csr.term.KEY_PGUP: above(right_of(csr, 1), 1), + csr.term.KEY_KP_9: above(right_of(csr, 1), 1), + + # shift + arrows + csr.term.KEY_SLEFT: left_of(csr, 10), + csr.term.KEY_SRIGHT: right_of(csr, 10), + csr.term.KEY_SDOWN: below(csr, 10), + csr.term.KEY_SUP: above(csr, 10), + + # carriage return + csr.term.KEY_ENTER: home(below(csr, 1)), + }.get(inp_code, csr) + term = Terminal() csr = Cursor(0, 0, term) screen = {} with term.hidden_cursor(), \ - term.raw(), \ + term.keystroke_input(raw=True), \ term.location(), \ term.fullscreen(), \ term.keypad(): inp = None while True: echo_yx(csr, term.reverse(screen.get((csr.y, csr.x), u' '))) - inp = term.inkey() + inp = term.keystroke() if inp == chr(3): # ^c exits @@ -191,8 +205,8 @@ def main(): elif inp == chr(19): # ^s saves echo_yx(home(bottom(csr)), - term.ljust(term.bold_white('Filename: '))) - echo_yx(right_of(home(bottom(csr)), len('Filename: ')), u'') + term.ljust(term.bold_white(u'Filename: '))) + echo_yx(right_of(home(bottom(csr)), len(u'Filename: ')), u'') save(screen, readline(term)) echo_yx(home(bottom(csr)), term.clear_eol) redraw(term=term, screen=screen, @@ -210,7 +224,7 @@ def main(): echo_yx(csr, screen.get((csr.y, csr.x), u' ')) csr = n_csr - elif not inp.is_sequence and inp.isprintable(): + elif not inp.is_sequence and inp in string.printable: echo_yx(csr, inp) screen[(csr.y, csr.x)] = inp.__str__() n_csr = right_of(csr, 1) diff --git a/bin/keymatrix.py b/bin/keymatrix.py index f4e99f6..2306641 100755 --- a/bin/keymatrix.py +++ b/bin/keymatrix.py @@ -1,91 +1,116 @@ #!/usr/bin/env python -from __future__ import division +""" +A simple "game": hit all application keys to win. + +Display all known key capabilities that may match the terminal. +As each key is pressed on input, it is lit up and points are scored. +""" +from __future__ import division, print_function +import functools import sys from blessings import Terminal +# python 2/3 compatibility, provide 'echo' function as an +# alias for "print without newline and flush" +try: + # pylint: disable=invalid-name + # Invalid constant name "echo" + echo = functools.partial(print, end='', flush=True) + echo(u'') +except TypeError: + # TypeError: 'flush' is an invalid keyword argument for this function + + def echo(text): + """Display ``text`` and flush output.""" + sys.stdout.write(u'{}'.format(text)) + sys.stdout.flush() + +def refresh(term, board, level, score, inps): + """Refresh the game screen.""" + echo(term.home + term.clear) + level_color = level % 7 + if level_color == 0: + level_color = 4 + bottom = 0 + for keycode, attr in board.items(): + echo(u''.join(( + term.move(attr['row'], attr['column']), + term.color(level_color), + (term.reverse if attr['hit'] else term.bold), + keycode, + term.normal))) + bottom = max(bottom, attr['row']) + echo(term.move(term.height, 0) + + 'level: %s score: %s' % (level, score,)) + if bottom >= (term.height - 5): + sys.stderr.write( + ('\n' * (term.height // 2)) + + term.center(term.red_underline('cheater!')) + '\n') + sys.stderr.write( + term.center("(use a larger screen)") + + ('\n' * (term.height // 2))) + sys.exit(1) + echo(term.move(bottom + 1, 0)) + echo('Press ^C to exit.') + for row, inp in enumerate(inps[(term.height - (bottom + 3)) * -1:], 1): + echo(term.move(bottom + row + 1, 0)) + echo('{0!r}, {1}, {2}'.format( + inp.__str__() if inp.is_sequence else inp, + inp.code, + inp.name)) + echo(term.clear_eol) + +def build_gameboard(term): + """Build the gameboard layout.""" + column, row = 0, 0 + board = dict() + spacing = 2 + for keycode in sorted(term._keycodes.values()): + if (keycode.startswith('KEY_F') + and keycode[-1].isdigit() + and int(keycode[len('KEY_F'):]) > 24): + continue + if column + len(keycode) + (spacing * 2) >= term.width: + column = 0 + row += 1 + board[keycode] = {'column': column, + 'row': row, + 'hit': 0, + } + column += len(keycode) + (spacing * 2) + return board + +def add_score(score, pts, level): + """Add points to score, determine and return new score and level.""" + lvl_multiplier = 10 + score += pts + if 0 == (score % (pts * lvl_multiplier)): + level += 1 + return score, level + def main(): - """ - Displays all known key capabilities that may match the terminal. - As each key is pressed on input, it is lit up and points are scored. - """ + """Program entry point.""" term = Terminal() score = level = hit_highbit = hit_unicode = 0 dirty = True - def refresh(term, board, level, score, inp): - sys.stdout.write(term.home + term.clear) - level_color = level % 7 - if level_color == 0: - level_color = 4 - bottom = 0 - for keycode, attr in board.items(): - sys.stdout.write(u''.join(( - term.move(attr['row'], attr['column']), - term.color(level_color), - (term.reverse if attr['hit'] else term.bold), - keycode, - term.normal))) - bottom = max(bottom, attr['row']) - sys.stdout.write(term.move(term.height, 0) - + 'level: %s score: %s' % (level, score,)) - sys.stdout.flush() - if bottom >= (term.height - 5): - sys.stderr.write( - ('\n' * (term.height // 2)) + - term.center(term.red_underline('cheater!')) + '\n') - sys.stderr.write( - term.center("(use a larger screen)") + - ('\n' * (term.height // 2))) - sys.exit(1) - for row, inp in enumerate(inps[(term.height - (bottom + 2)) * -1:]): - sys.stdout.write(term.move(bottom + row+1)) - sys.stdout.write('%r, %s, %s' % (inp.__str__() if inp.is_sequence - else inp, inp.code, inp.name, )) - sys.stdout.flush() - - def build_gameboard(term): - column, row = 0, 0 - board = dict() - spacing = 2 - for keycode in sorted(term._keycodes.values()): - if (keycode.startswith('KEY_F') - and keycode[-1].isdigit() - and int(keycode[len('KEY_F'):]) > 24): - continue - if column + len(keycode) + (spacing * 2) >= term.width: - column = 0 - row += 1 - board[keycode] = {'column': column, - 'row': row, - 'hit': 0, - } - column += len(keycode) + (spacing * 2) - return board - - def add_score(score, pts, level): - lvl_multiplier = 10 - score += pts - if 0 == (score % (pts * lvl_multiplier)): - level += 1 - return score, level - - gb = build_gameboard(term) + gameboard = build_gameboard(term) inps = [] - with term.raw(), term.keypad(), term.location(): - inp = term.inkey(timeout=0) + with term.keystroke_input(raw=True), term.keypad(), term.location(): + inp = term.keystroke(timeout=0) while inp != chr(3): if dirty: - refresh(term, gb, level, score, inps) + refresh(term, gameboard, level, score, inps) dirty = False - inp = term.inkey(timeout=5.0) + inp = term.keystroke(timeout=5.0) dirty = True if (inp.is_sequence and - inp.name in gb and - 0 == gb[inp.name]['hit']): - gb[inp.name]['hit'] = 1 + inp.name in gameboard and + 0 == gameboard[inp.name]['hit']): + gameboard[inp.name]['hit'] = 1 score, level = add_score(score, 100, level) elif inp and not inp.is_sequence and 128 <= ord(inp) <= 255: hit_highbit += 1 @@ -97,9 +122,9 @@ def main(): score, level = add_score(score, 100, level) inps.append(inp) - with term.cbreak(): - sys.stdout.write(term.move(term.height)) - sys.stdout.write( + with term.keystroke_input(): + echo(term.move(term.height)) + echo( u'{term.clear_eol}Your final score was {score} ' u'at level {level}{term.clear_eol}\n' u'{term.clear_eol}\n' @@ -113,7 +138,7 @@ def main(): hit_highbit=hit_highbit, hit_unicode=hit_unicode) ) - term.inkey() + term.keystroke() if __name__ == '__main__': main() diff --git a/bin/on_resize.py b/bin/on_resize.py index 4140748..c81efe6 100755 --- a/bin/on_resize.py +++ b/bin/on_resize.py @@ -5,33 +5,43 @@ This is an example application for the 'blessings' Terminal library for python. Window size changes are caught by the 'on_resize' function using a traditional signal handler. Meanwhile, blocking keyboard input is displayed to stdout. If a resize event is discovered, an empty string is returned by -term.keystroke() when interruptable is False, as it is here. +term.keystroke(). """ +from __future__ import print_function import signal from blessings import Terminal - -term = Terminal() - - -def on_resize(sig, action): - # Its generally not a good idea to put blocking functions (such as print) - # within a signal handler -- if another SIGWINCH is recieved while this - # function blocks, an error will occur. In most programs, you'll want to - # set some kind of 'dirty' flag, perhaps by a Semaphore or global variable. - print('height={t.height}, width={t.width}\r'.format(t=term)) - -signal.signal(signal.SIGWINCH, on_resize) - -# note that, a terminal driver actually writes '\r\n' when '\n' is found, but -# in raw mode, we are allowed to write directly to the terminal without the -# interference of such driver -- so we must write \r\n ourselves; as python -# will append '\n' to our print statements, we simply end our statements with -# \r. -with term.raw(): - print("press 'X' to stop.\r") - inp = None - while inp != 'X': - inp = term.inkey(interruptable=False) - print(repr(inp) + u'\r') +def main(): + """Program entry point.""" + term = Terminal() + + def on_resize(*args): + # pylint: disable=unused-argument + # Unused argument 'args' + + # Its generally not a good idea to put blocking functions (such as + # print) within a signal handler -- if another SIGWINCH is received + # while this function blocks, an error will occur. + + # In most programs, you'll want to set some kind of 'dirty' flag, + # perhaps by a Semaphore like threading.Event or (thanks to the GIL) + # a simple global variable will suffice. + print('height={t.height}, width={t.width}\r'.format(t=term)) + + signal.signal(signal.SIGWINCH, on_resize) + + # note that, a terminal driver actually writes '\r\n' when '\n' is found, + # but in raw mode, we are allowed to write directly to the terminal without + # the interference of such driver -- so we must write \r\n ourselves; as + # python will append '\n' to our print statements, we simply end our + # statements with \r. + with term.keystroke_input(): + print("press 'X' to stop.\r") + inp = None + while inp != 'X': + inp = term.keystroke() + print(repr(inp) + u'\r') + +if __name__ == '__main__': + main() diff --git a/bin/progress_bar.py b/bin/progress_bar.py index 5257e95..cf9f09c 100755 --- a/bin/progress_bar.py +++ b/bin/progress_bar.py @@ -15,12 +15,13 @@ from blessings import Terminal def main(): + """Program entry point.""" term = Terminal() assert term.hpa(1) != u'', ( 'Terminal does not support hpa (Horizontal position absolute)') col, offset = 1, 1 - with term.cbreak(): + with term.keystroke_input(): inp = None print("press 'X' to stop.") sys.stderr.write(term.move(term.height, 0) + u'[') @@ -30,11 +31,16 @@ def main(): offset = -1 elif col <= 1: offset = 1 - sys.stderr.write(term.move_x(col) + u'.' if offset == -1 else '=') + sys.stderr.write(term.move_x(col)) + if offset == -1: + sys.stderr.write(u'.') + else: + sys.stderr.write(u'=') col += offset - sys.stderr.write(term.move_x(col) + u'|\b') + sys.stderr.write(term.move_x(col)) + sys.stderr.write(u'|\b') sys.stderr.flush() - inp = term.inkey(0.04) + inp = term.keystroke(0.04) print() if __name__ == '__main__': diff --git a/bin/tprint.py b/bin/tprint.py index c00c486..984febc 100755 --- a/bin/tprint.py +++ b/bin/tprint.py @@ -1,20 +1,25 @@ #!/usr/bin/env python - +"""A simple cmd-line tool for displaying FormattingString capabilities.""" +from __future__ import print_function import argparse -from blessings import Terminal +def main(): + """Program entry point.""" + from blessings import Terminal + parser = argparse.ArgumentParser( + description='displays argument as specified style') -parser = argparse.ArgumentParser( - description='displays argument as specified style') + parser.add_argument('style', type=str, help='style formatter') + parser.add_argument('text', type=str, nargs='+') -parser.add_argument('style', type=str, help='style formatter') -parser.add_argument('text', type=str, nargs='+') + term = Terminal() + args = parser.parse_args() -term = Terminal() -args = parser.parse_args() + style = getattr(term, args.style) -style = getattr(term, args.style) + print(style(' '.join(args.text))) -print(style(' '.join(args.text))) +if __name__ == '__main__': + main() diff --git a/bin/worms.py b/bin/worms.py index be46d3d..0c51a6e 100755 --- a/bin/worms.py +++ b/bin/worms.py @@ -16,14 +16,17 @@ from blessings import Terminal # python 2/3 compatibility, provide 'echo' function as an # alias for "print without newline and flush" try: + # pylint: disable=invalid-name + # Invalid constant name "echo" echo = partial(print, end='', flush=True) - echo('begin.') + echo(u'') except TypeError: # TypeError: 'flush' is an invalid keyword argument for this function import sys - def echo(object): - sys.stdout.write(u'{}'.format(object)) + def echo(text): + """python 2 version of print(end='', flush=True).""" + sys.stdout.write(u'{0}'.format(text)) sys.stdout.flush() # a worm is a list of (y, x) segments Locations @@ -40,97 +43,132 @@ Direction = namedtuple('Direction', ('y', 'x',)) # these functions return a new Location instance, given # the direction indicated by their name. LEFT = (0, -1) -left_of = lambda segment, term: Location( - y=segment.y, - x=max(0, segment.x - 1)) - RIGHT = (0, 1) -right_of = lambda segment, term: Location( - y=segment.y, - x=min(term.width - 1, segment.x + 1)) - UP = (-1, 0) -above = lambda segment, term: Location( - y=max(0, segment.y - 1), - x=segment.x) - DOWN = (1, 0) -below = lambda segment, term: Location( - y=min(term.height - 1, segment.y + 1), - x=segment.x) - -# return a direction function that defines the new bearing for any matching -# keyboard code of inp_code; otherwise, the function for the current bearing. -next_bearing = lambda term, inp_code, bearing: { - term.KEY_LEFT: left_of, - term.KEY_RIGHT: right_of, - term.KEY_UP: above, - term.KEY_DOWN: below, -}.get(inp_code, - # direction function given the current bearing - {LEFT: left_of, - RIGHT: right_of, - UP: above, - DOWN: below}[(bearing.y, bearing.x)]) - - -# return new bearing given the movement f(x). -change_bearing = lambda f_mov, segment, term: Direction( - f_mov(segment, term).y - segment.y, - f_mov(segment, term).x - segment.x) - -# direction-flipped check, reject traveling in opposite direction. -bearing_flipped = lambda dir1, dir2: ( - (0, 0) == (dir1.y + dir2.y, dir1.x + dir2.x) -) - -# returns True if `loc' matches any (y, x) coordinates, -# within list `segments' -- such as a list composing a worm. -hit_any = lambda loc, segments: loc in segments - -# same as above, but `locations' is also an array of (y, x) coordinates. -hit_vany = lambda locations, segments: any( - hit_any(loc, segments) for loc in locations) - -# returns True if segments are same position (hit detection) -hit = lambda src, dst: src.x == dst.x and src.y == dst.y - -# returns new worm_length if current nibble is hit, -next_wormlength = lambda nibble, head, worm_length: ( - worm_length + nibble.value if hit(head, nibble.location) - else worm_length) - -# returns new speed if current nibble is hit, -next_speed = lambda nibble, head, speed, modifier: ( - speed * modifier if hit(head, nibble.location) - else speed) - -# when displaying worm head, show a different glyph for horizontal/vertical -head_glyph = lambda direction: (u':' if direction in (left_of, right_of) - else u'"') - - -# provide the next nibble -- continuously generate a random new nibble so -# long as the current nibble hits any location of the worm, otherwise -# return a nibble of the same location and value as provided. + +def left_of(segment, term): + """Return Location left-of given segment.""" + # pylint: disable=unused-argument + # Unused argument 'term' + return Location(y=segment.y, + x=max(0, segment.x - 1)) + +def right_of(segment, term): + """Return Location right-of given segment.""" + return Location(y=segment.y, + x=min(term.width - 1, segment.x + 1)) + +def above(segment, term): + """Return Location above given segment.""" + # pylint: disable=unused-argument + # Unused argument 'term' + return Location( + y=max(0, segment.y - 1), + x=segment.x) + +def below(segment, term): + """Return Location below given segment.""" + return Location( + y=min(term.height - 1, segment.y + 1), + x=segment.x) + +def next_bearing(term, inp_code, bearing): + """ + Return direction function for new bearing by inp_code. + + If no inp_code matches a bearing direction, return + a function for the current bearing. + """ + return { + term.KEY_LEFT: left_of, + term.KEY_RIGHT: right_of, + term.KEY_UP: above, + term.KEY_DOWN: below, + }.get(inp_code, + # direction function given the current bearing + {LEFT: left_of, + RIGHT: right_of, + UP: above, + DOWN: below}[(bearing.y, bearing.x)]) + + +def change_bearing(f_mov, segment, term): + """Return new bearing given the movement f(x).""" + return Direction( + f_mov(segment, term).y - segment.y, + f_mov(segment, term).x - segment.x) + +def bearing_flipped(dir1, dir2): + """ + direction-flipped check. + + Return true if dir2 travels in opposite direction of dir1. + """ + return (0, 0) == (dir1.y + dir2.y, dir1.x + dir2.x) + +def hit_any(loc, segments): + """Return True if `loc' matches any (y, x) coordinates within segments.""" + # `segments' -- a list composing a worm. + return loc in segments + +def hit_vany(locations, segments): + """Return True if any locations are found within any segments.""" + return any(hit_any(loc, segments) + for loc in locations) + +def hit(src, dst): + """Return True if segments are same position (hit detection).""" + return src.x == dst.x and src.y == dst.y + +def next_wormlength(nibble, head, worm_length): + """Return new worm_length if current nibble is hit.""" + if hit(head, nibble.location): + return worm_length + nibble.value + return worm_length + +def next_speed(nibble, head, speed, modifier): + """Return new speed if current nibble is hit.""" + if hit(head, nibble.location): + return speed * modifier + return speed + +def head_glyph(direction): + """Return character for worm head depending on horiz/vert orientation.""" + if direction in (left_of, right_of): + return u':' + return u'"' + + def next_nibble(term, nibble, head, worm): - l, v = nibble.location, nibble.value - while hit_vany([head] + worm, nibble_locations(l, v)): - l = Location(x=randrange(1, term.width - 1), + """ + Provide the next nibble. + + continuously generate a random new nibble so long as the current nibble + hits any location of the worm. Otherwise, return a nibble of the same + location and value as provided. + """ + loc, val = nibble.location, nibble.value + while hit_vany([head] + worm, nibble_locations(loc, val)): + loc = Location(x=randrange(1, term.width - 1), y=randrange(1, term.height - 1)) - v = nibble.value + 1 - return Nibble(l, v) + val = nibble.value + 1 + return Nibble(loc, val) -# generate an array of locations for the current nibble's location -- a digit -# such as '123' may be hit at 3 different (y, x) coordinates. def nibble_locations(nibble_location, nibble_value): + """Return array of locations for the current "nibble".""" + # generate an array of locations for the current nibble's location + # -- a digit such as '123' may be hit at 3 different (y, x) coordinates. return [Location(x=nibble_location.x + offset, y=nibble_location.y) for offset in range(0, 1 + len('{}'.format(nibble_value)) - 1)] def main(): + """Program entry point.""" + # pylint: disable=too-many-locals + # Too many local variables (20/15) term = Terminal() worm = [Location(x=term.width // 2, y=term.height // 2)] worm_length = 2 @@ -149,7 +187,7 @@ def main(): modifier = 0.93 inp = None - with term.hidden_cursor(), term.raw(): + with term.hidden_cursor(), term.keystroke_input(): while inp not in (u'q', u'Q'): # delete the tail of the worm at worm_length @@ -176,10 +214,12 @@ def main(): if n_nibble != nibble: # erase the old one, careful to redraw the nibble contents # with a worm color for those portions that overlay. - for (y, x) in nibble_locations(*nibble): - echo(term.move(y, x) + (color_worm if (y, x) == head - else color_bg)(u' ')) - echo(term.normal) + for (yloc, xloc) in nibble_locations(*nibble): + echo(u''.join(( + term.move(yloc, xloc), + (color_worm if (yloc, xloc) == head + else color_bg)(u' '), + term.normal))) # and draw the new, echo(term.move(*n_nibble.location) + ( color_nibble('{}'.format(n_nibble.value)))) @@ -195,7 +235,7 @@ def main(): # wait for keyboard input, which may indicate # a new direction (up/down/left/right) - inp = term.inkey(speed) + inp = term.keystroke(timeout=speed) # discover new direction, given keyboard input and/or bearing. nxt_direction = next_bearing(term, inp.code, bearing) @@ -203,8 +243,8 @@ def main(): # discover new bearing, given new direction compared to prev nxt_bearing = change_bearing(nxt_direction, head, term) - # disallow new bearing/direction when flipped (running into - # oneself, fe. travelling left while traveling right) + # disallow new bearing/direction when flipped: running into + # oneself, for example traveling left while traveling right. if not bearing_flipped(bearing, nxt_bearing): direction = nxt_direction bearing = nxt_bearing diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..a0a46cb --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,68 @@ +Examples +======== + +A few programs are provided with blessings to help interactively +test the various API features, but also serve as examples of using +blessings to develop various kinds of applications. + +These examples are not distributed with the package -- they are +only available in the github repository. You can retrieve them +by cloning the repository, or simply downloading the "raw" file +link. + +editor.py +--------- +https://github.com/erikrose/blessings/blob/master/bin/editor.py + +This program demonstrates using the directional keys and noecho input +mode. It acts as a (very dumb) fullscreen editor, with support for +saving a file, which demonstrates how to provide a line-editor +rudimentary line-editor as well. + +keymatrix.py +------------ +https://github.com/erikrose/blessings/blob/master/bin/editor.py + +This program displays a "gameboard" of all known special KEY_NAME +constants. When the key is depressed, it is highlighted, as well +as displaying the unicode sequence, integer code, and friendly-name +of any key pressed. + +on_resize.py +------------ +https://github.com/erikrose/blessings/blob/master/bin/on_resize.py + +This program installs a SIGWINCH signal handler, which detects +screen resizes while also polling for input, displaying keypresses. + +This demonstrates how a program can react to screen resize events. + +progress_bar.py +--------------- +https://github.com/erikrose/blessings/blob/master/bin/progress_bar.py + +This program demonstrates a simple progress bar. All text is written +to stderr, to avoid the need to "flush" or emit newlines, and makes +use of the move_x (hpa) capability to "overstrike" the display a +scrolling progress bar. + +tprint.py +--------- +https://github.com/erikrose/blessings/blob/master/bin/tprint.py + +This program demonstrates how users may customize FormattingString +styles. Accepting a string style, such as "bold" or "bright_red" +as the first argument, all subsequent arguments are displayed by +the given style. This shows how a program could provide +user-customizable compound formatting names to configure a program's +styling. + +worms.py +-------- +https://github.com/erikrose/blessings/blob/master/blessings/editor.py + +This program demonstrates how an interactive game could be made +with blessings. It is designed after the class game of "worms" +that was made available for free with QBASIC, and later more +popularly known as "snake" as it was named on early mobile +platforms. diff --git a/docs/index.rst b/docs/index.rst index 37794b3..17e46ab 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ Contents: intro overview + examples further pains api |