diff options
Diffstat (limited to 'src/miinaharava/tui')
-rw-r--r-- | src/miinaharava/tui/__init__.py | 3 | ||||
-rw-r--r-- | src/miinaharava/tui/ansi.py | 46 | ||||
-rw-r--r-- | src/miinaharava/tui/ansi_draw.py | 58 | ||||
-rw-r--r-- | src/miinaharava/tui/kbd.py | 86 | ||||
-rw-r--r-- | src/miinaharava/tui/static.py | 77 | ||||
-rw-r--r-- | src/miinaharava/tui/tui.py | 109 |
6 files changed, 379 insertions, 0 deletions
diff --git a/src/miinaharava/tui/__init__.py b/src/miinaharava/tui/__init__.py new file mode 100644 index 0000000..0c8d632 --- /dev/null +++ b/src/miinaharava/tui/__init__.py @@ -0,0 +1,3 @@ +""" tui - hoitaa käyttäjälle katseltavaa ja havaitsee syötteet """ +from .tui import Tui +from .static import Action, KEY_DESCRIPTIONS diff --git a/src/miinaharava/tui/ansi.py b/src/miinaharava/tui/ansi.py new file mode 100644 index 0000000..c25ff6c --- /dev/null +++ b/src/miinaharava/tui/ansi.py @@ -0,0 +1,46 @@ +""" ansi.py - ansi ohjauskomentoja. värit jne """ + +class Ansi: + """ Ansi - Luokallinen staattisia metodeja ansi komennoille """ + + BLACK = 0 + RED = 1 + GREEN = 2 + YELLOW = 3 + BLUE = 4 + MAGENTA = 5 + CYAN = 6 + WHITE = 7 + GRAY = 8 + BRIGHT_RED = 9 + BRIGHT_GREEN = 0xA + BRIGHT_YELLOW = 0xB + BRIGHT_BLUE = 0xC + BRIGHT_MAGENTA = 0xD + BRIGHT_CYAN = 0xE + BRIGHT_WHITE = 0xF + + @staticmethod + def color(color): + """ asettaa tekstin värin """ + if color in range(16): + print(end=f"\033[{'1;' if color//8 else ''}3{color%8}m") + + + @staticmethod + def bg(color): + """ asettaa tekstin taustan värin""" + if color in range(8): + print(end=f"\033[4{color}m") + + + @staticmethod + def cup(lines): + """ liikuttaa kursoria ylöspäin""" + print(end=f"\033[{lines}F") + + + @staticmethod + def reset(): + """ resetoi tekstin värin ja muut attribuutit perusarvoille """ + print(end="\033[0m") diff --git a/src/miinaharava/tui/ansi_draw.py b/src/miinaharava/tui/ansi_draw.py new file mode 100644 index 0000000..ba71fdb --- /dev/null +++ b/src/miinaharava/tui/ansi_draw.py @@ -0,0 +1,58 @@ +""" tui/ansi_draw.py - perustukset ansi tulostelulle """ +# pylint: disable = multiple-imports +from .ansi import Ansi +from .static import TileTypes + +class AnsiDraw(): + """ AnsiDraw - "piirtelee" näytölle kirjailmilla """ + def __init__(self, height = 9, name = ""): + print(end='\n'*height+name+": Peli alkaa.") + + def __del__(self): + print() + + def __tile(self, tile, hilighted): + """ "piirtää" yhden ruudun """ + for ch, colors in zip(TileTypes[tile].text, TileTypes[tile].colors): + color, bg = colors + Ansi.color(Ansi.BLACK if hilighted else color) + Ansi.bg(Ansi.CYAN if hilighted else bg) + print(end=ch) + Ansi.reset() + + + def matrix(self, matrix, hx, hy): + """ "piirtää" ruudukon """ + Ansi.cup(len(matrix[0])) + # pylint: disable=consider-using-enumerate + for y in range(len(matrix[0])): + for x in range(len(matrix)): + hilight = matrix[x][y] != 9 and x == hx and y == hy + self.__tile(matrix[x][y], hilight) + print() + + + def status_line(self, text): + """ draw_status_line - tulostaa pelitietorivin""" + print(end=text+'\r') + +class SuppressDraw(): + """ SuppressDraw - vain status """ + # pylint: disable = unused-argument + + def matrix(self, matrix, hx, hy): + """ "piirtää" ruudukon """ + + def status_line(self, text): + """ draw_status_line - tulostaa pelitietorivin""" + print(end=text+'\r') + +class NoDraw(): + """ NoDraw - ei mitään """ + # pylint: disable = unused-argument + + def matrix(self, matrix, hx, hy): + """ "piirtää" ruudukon """ + + def status_line(self, text): + """ draw_status_line - tulostaa pelitietorivin""" diff --git a/src/miinaharava/tui/kbd.py b/src/miinaharava/tui/kbd.py new file mode 100644 index 0000000..a31e56f --- /dev/null +++ b/src/miinaharava/tui/kbd.py @@ -0,0 +1,86 @@ +""" tui/kbd.py - näppäimistön käsittellijä """ +# pylint: disable = multiple-imports +import termios, fcntl, sys, os, io +from time import sleep +from .static import ActionKeys, Action + +class NoKbd(): + """ NoKbd - näppis-ei-käsittelijä """ + # pylint: disable = unused-argument + def read_action(self): + """ read_action - ilman näppistä -> loppu """ + return Action.QUIT + + def read_matrix_action(self, w, h, x, y): + """ read_matrix_action - ilman näppistä -> loppu """ + return Action.QUIT, 0, 0 + +class Kbd(): + """ Kbd - näppiskäsittelijä """ + def __init__(self): + # Vaatii hieman terminaaliasetusten muokkaamista jotta yksittäiset + # napin painallukset voidaan lukea + # https://stackoverflow.com/questions/983354/how-do-i-wait-for-a-pressed-key + try: + fd = sys.stdin.fileno() + self.oldterm = termios.tcgetattr(fd) + + newattr = termios.tcgetattr(fd) + newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO + termios.tcsetattr(fd, termios.TCSANOW, newattr) + + self.oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK) + # Testeissä ei voi mukata termilaalia + except io.UnsupportedOperation: + pass + + def __del__(self): + # palautetaan terminaali takaisin alkupetäiseen uskoon + try: + fd = sys.stdin.fileno() + termios.tcsetattr(fd, termios.TCSAFLUSH, self.oldterm) + fcntl.fcntl(fd, fcntl.F_SETFL, self.oldflags) + # Testeissä ei voi mukata termilaalia + except io.UnsupportedOperation: + pass + + def read_action(self): + """ lukee näppäimistölä käyttäjän toiminnon """ + while True: + # Ehkä riittää jos näppäimiä luetaan 50x sekunnissa + sleep(0.02) + try: + keycode = sys.stdin.read(16) + except KeyboardInterrupt: + return Action.QUIT + if keycode: + for key, action in ActionKeys.items(): + if keycode.startswith(key): + return action + + def read_matrix_action(self, w, h, x, y): + """ read_matrix_action - lukee actionit ja pitää huolen koordinaat""" + action = self.read_action() + match action: + case Action.QUIT | Action.HINT: + return (action, x, y) + case Action.OPEN | Action.FLAG | Action.MINE | Action.SAFE: + return (action, x, y) + case Action.UP: + y = y-1 if y > 0 else 0 + case Action.LEFT: + x = x-1 if x > 0 else 0 + case Action.DOWN: + y = y+1 if y < h-1 else y + case Action.RIGHT: + x = x+1 if x < w-1 else x + case Action.TOP: + y = 0 + case Action.BOTTOM: + y = h-1 + case Action.BEGIN: + x = 0 + case Action.END: + x = w-1 + return (Action.NOOP, x, y) diff --git a/src/miinaharava/tui/static.py b/src/miinaharava/tui/static.py new file mode 100644 index 0000000..50dbb44 --- /dev/null +++ b/src/miinaharava/tui/static.py @@ -0,0 +1,77 @@ +""" tui/static.py - Staattiset määritykset tui:ssa tarvittaville jutuille. """ +from enum import Enum +from dataclasses import dataclass + +from board import Tile + +class Action(Enum): + """ tominnot, joita voidaan saada palautusrvona """ + QUIT = 0 # Pelin lopetus + OPEN = 1 # Ruudun avaaminen + FLAG = 2 # Ruudun liputus + HINT = 3 # Anna vihjeet + AUTO = 4 # Pelaa automaattisesti + LEFT = 5 # Liikkumiset... + RIGHT = 6 + UP = 7 + DOWN = 8 + TOP = 9 + BOTTOM = 10 + BEGIN = 11 + END = 12 + NOOP = 13 # ei mitään - tarvitaan, ettei mätsää ansikoodeja esciin + MINE = 14 # merkkaa pommi + SAFE = 15 # merkkaa turvallinen + +# ActionKeys - Ohjelma vertaa syötteen alkua näihin ja palauttaa ekan +ActionKeys = { + "\033[A": Action.UP, "\033[D": Action.LEFT, + "\033[C": Action.RIGHT, '\033[B': Action.DOWN, "\033[5~": Action.TOP, + "\033[6~": Action.BOTTOM, "\033[7~": Action.BEGIN,"\033[8~": Action.END, + "\033[": Action.NOOP, "\033": Action.QUIT, "t": Action.SAFE, + "w": Action.UP, "a": Action.LEFT, "s": Action.DOWN, + "d": Action.RIGHT, " ": Action.OPEN, "\n": Action.OPEN, + "l": Action.QUIT, "?": Action.HINT, "b": Action.HINT, + "f": Action.FLAG, "q": Action.QUIT, "m": Action.MINE, + "\t": Action.FLAG, "9": Action.MINE, "0": Action.SAFE +} + +KEY_DESCRIPTIONS = """Näppäinasettelu: + + YLÖS, ALAS, VASEN, OIKEA, PGDN, PGUP, HOME, END, w, a, s, d + Kursorin liikuttaminen pelilaudalla + + ENTER, SPACE Avaa laatta + + f, TAB Vaihda laatan merkintää + m, 9 Merkitse miinaksi + t, 0 Merkitse turvalliseksi + + ?, b Vihje tekoälyltä + + l, q, ESC Pelin lopetus +""" + +@dataclass +class TileType: + """ ruututyyppien tallennusmuotojen kuvaus""" + text: str # Teksti + colors: [] # Lista (väri, tausta) pareja tekstin kaunistamiseen + + +TileTypes = { + Tile.BLANK: TileType( "[ ]", [(0x7,0), (0x7,0), (0x7,0)] ), + Tile.ONE: TileType( "[1]", [(0xA,0), (0xA,0), (0xA,0)] ), + Tile.TWO: TileType( "[2]", [(0xB,0), (0xB,0), (0xB,0)] ), + Tile.THREE: TileType( "[3]", [(0xD,0), (0xD,0), (0xD,0)] ), + Tile.FOUR: TileType( "[4]", [(0x9,0), (0x9,0), (0x9,0)] ), + Tile.FIVE: TileType( "[5]", [(0x9,0), (0x9,0), (0x9,0)] ), + Tile.SIX: TileType( "[6]", [(0x9,0), (0x9,0), (0x9,0)] ), + Tile.SEVEN: TileType( "[7]", [(0x9,0), (0x9,0), (0x9,0)] ), + Tile.EIGHT: TileType( "[8]", [(0x9,0), (0x9,0), (0x9,0)] ), + Tile.MINE: TileType( "[@]", [(0xF,1), (0xF,1), (0xF,1)] ), + Tile.FLAG_MINE: TileType( "[×]", [(0x8,7), (0x1,7), (0x8,7)] ), + Tile.FLAG_FREE: TileType( "[•]", [(0x8,7), (0x2,7), (0x8,7)] ), + Tile.UNOPENED: TileType( "[#]", [(0x8,7), (0x8,7), (0x8,7)] ), + Tile.FLAG_UNKNOWN: TileType( "[?]", [(0x8,7), (0x0,7), (0x8,7)] ) +} diff --git a/src/miinaharava/tui/tui.py b/src/miinaharava/tui/tui.py new file mode 100644 index 0000000..d7f7fb3 --- /dev/null +++ b/src/miinaharava/tui/tui.py @@ -0,0 +1,109 @@ +""" tui/tui.py - runko käyttöliittymälle """ +import time +from .static import Action +from .kbd import Kbd, NoKbd +from .ansi_draw import AnsiDraw, SuppressDraw + + +class Tui(): + """ Tui - Luokka käyttäjän interaktiota varten """ + # pylint: disable = too-many-arguments, too-many-instance-attributes + def __init__(self, + bot = None, + autoplay = False, + interactive = True, + suppress = False, + height = 9, + level_name = "outo lauta", + delay = 0): + + # jos ei ole bottia pitää olla interaktiivinen + if bot is None: + autoplay = False + interactive = True + suppress = False + + # jos ei mitään näytetä ei voi olla interaktiivinen + if suppress: + interactive = False + + # automaattipeli pitää olla päällä jos ei interaktiivinen + if not interactive: + autoplay = True + + if delay and delay not in range(0,500): + delay = 50 + + self.autoplay = autoplay + self.interactive = interactive + self.suppress = suppress + self.height = height + self.level_name = level_name + self.delay = delay + + self.bot = bot(uncertain=not self.interactive) if bot else None + + self.kbd = Kbd() if self.interactive else NoKbd() + + if self.suppress: + self.draw = SuppressDraw() + else: + self.draw = AnsiDraw(height=self.height, name=self.level_name) + + def matrix_selector(self, matrix, x, y): + """ valinta matriisita """ + + # automaattipeli avaa botin vinkit heti + if self.autoplay: + action, x, y = self.bot.hint(matrix, x, y) + if action != Action.NOOP: + if self.delay: + self.draw.matrix(matrix, x, y) + time.sleep(self.delay/100) + return Action.OPEN if action==Action.SAFE else action, x, y + + + # ilman näppiskäsittelijää voidaan lopettaa + if not self.interactive: + return Action.QUIT, 0, 0 + + w, h = len(matrix), len(matrix[0]) + while True: + self.draw.matrix(matrix, x, y) + action, x, y = self.kbd.read_matrix_action(w, h, x, y) + match action: + case Action.QUIT: + return (action, x, y) + case Action.OPEN | Action.FLAG | Action.MINE | Action.SAFE: + if matrix[x][y] >= 10: + return (action, x, y) + case Action.HINT: + if self.bot is not None: + return self.bot.hint(matrix, x, y) + + def game_over(self, matrix, x, y): + """ tehtävät kun kuolee """ + self.draw.matrix(matrix, x, y) + self.draw.status_line( + f"{self.level_name}: " + + ("K " if self.suppress else f"{'Kuolit!':<30}") + ) + self.kbd.read_action() + + def game_win(self, matrix, x, y): + """ tehtävät kun voittaa """ + self.draw.matrix(matrix, x, y) + self.draw.status_line( + f"{self.level_name}: " + + ("V " if self.suppress else f"{'Voitit!':<30}") + ) + self.kbd.read_action() + + def game_end(self, matrix): + """ tehtävät ihan pelin lopuksi """ + if self.interactive: + self.draw.matrix(matrix, -1, -1) + self.draw.status_line( + f"{self.level_name}: " + + f"{'Kiitos!':<30}" + ) |