From e591d15abc4943a74c39f0fcab065213828a1514 Mon Sep 17 00:00:00 2001 From: Aineopintojen-harjoitustyo-Algoritmit-j Date: Mon, 29 Jan 2024 02:34:32 +0200 Subject: Updating too much. Renewed bots and tui. --- README.md | 5 +- __main__.py | 40 ++++++++-- app.py | 32 +++++--- batch/100-run-beginner-bad.sh | 9 --- batch/1000-run-beginner-bad.sh | 9 --- board/static.py | 2 +- bots/__init__.py | 4 +- bots/bad.py | 73 ----------------- bots/bot.py | 133 +++++++++++++++++++++++++++---- bots/dssp.py | 80 +++++++++++++++++++ bots/idiot.py | 18 ----- bots/simple.py | 29 +++++++ doc/viikkoraportti3.pdf | Bin 0 -> 54141 bytes game/game.py | 5 +- tui/__init__.py | 1 - tui/ansi_draw.py | 61 +++++++++++++++ tui/autotui.py | 27 ------- tui/kbd.py | 78 +++++++++++++++++++ tui/tui.py | 173 +++++++++++++++-------------------------- 19 files changed, 493 insertions(+), 286 deletions(-) delete mode 100644 batch/100-run-beginner-bad.sh delete mode 100644 batch/1000-run-beginner-bad.sh delete mode 100644 bots/bad.py create mode 100644 bots/dssp.py delete mode 100644 bots/idiot.py create mode 100644 bots/simple.py create mode 100644 doc/viikkoraportti3.pdf create mode 100644 tui/ansi_draw.py delete mode 100644 tui/autotui.py create mode 100644 tui/kbd.py diff --git a/README.md b/README.md index 3be4ff0..b080a17 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Miinaharava ratkaisijalla ### Viikkoraportit - [viikko 1](doc/viikkoraportti1.pdf) - [viikko 2](doc/viikkoraportti2.pdf) +- [viikko 3](doc/viikkoraportti3.pdf) ## Ohjeet: @@ -21,5 +22,5 @@ Miinaharava ratkaisijalla ### Käyttöohjeet `python3 miinaharava -h` -### Skripti joka pelaa automaattisesti 100 peliä aloittelijan vaikeustasolla -`bash miinaharava/batch/100-run-beginner-bad.sh` +### Automaattipelaa 10 peliä +`python3 miinaharava -w 10` diff --git a/__main__.py b/__main__.py index 627f724..0ac24de 100644 --- a/__main__.py +++ b/__main__.py @@ -22,6 +22,11 @@ parser.add_argument( help='Asettaa edistyneen vaikeustason (vaatii 100 merkkiä leveän terminaalin)', action='store_true' ) +parser.add_argument( + '-s', '--simple', + help='Käytä yksinkertaisempaa vain yhtä pistettä tutkivaa bottia', + action='store_true' +) parser.add_argument( '-a', '--auto', help='Antaa botin pelata automaattisesti', @@ -29,14 +34,39 @@ parser.add_argument( ) parser.add_argument( '-u', '--uncertain', - help='Antaa botille luvan tehdä myös epävarmoja valintoja', + help='Antaa botille luvan tehdä myös epävarmoja valintoja (asettaa myös -a asetuksen)', + action='store_true' +) +parser.add_argument( + '-q', '--quiet', + help='Tulostaa minimaalisesti (asettaa myös -a ja -u asetukset)', action='store_true' ) +parser.add_argument( + '-w', + metavar='COUNT', + type=int, + help='Suorittaa ohelmaa COUNT kertaa ja tulostaa voitto-osuuden', +) args = parser.parse_args() -app = App(args) -is_win = app.run() -del app +if args.w is None: + app = App(args) + is_win = app.run() + del app + sys.exit(not is_win) # Exit koodeissa 0 on onnistunut suoritus + + +win_count = 0 +args.uncertain=True +for i in range(args.w): + print(end=f" \rSuoritus {i+1:>6}/{args.w} ") + print(end=f"({100*win_count/(i if i else 1):.1f}%)..") + if not args.quiet: + print() + app = App(args) + win_count+=app.run() + del app -sys.exit(not is_win) # Exit koodeissa 0 on onnistunut suoritus +print(f"\n## Voittoja {win_count}/{args.w} ({100*win_count/args.w:.1f}%)") diff --git a/app.py b/app.py index a38c897..8ca19ab 100644 --- a/app.py +++ b/app.py @@ -1,26 +1,40 @@ """ app.py - pääohjelma """ -from board import Board, Level -from tui import Tui, AutoTui +from board import Board, Level, LevelSpecs +from tui import Tui from game import Game -from bots import BadBot +from bots import SimpleBot, DSSPBot # pylint: disable = too-few-public-methods class App: """ App - Luokka pääohjelmalle""" def __init__(self, args=None): level = Level.BEGINNER - ui_class = Tui - uncertain = False + auto, uncertain, quiet = False, False, False if args: level = Level.EXPERT if args.expert else level level = Level.INTERMEDIATE if args.intermediate else level level = Level.BEGINNER if args.beginner else level - ui_class = AutoTui if args.auto else ui_class - uncertain = args.uncertain + width, height, bombs = LevelSpecs[level] + auto = args.auto + auto, uncertain = (True, True) if args.uncertain else (auto, False) + auto, uncertain, quiet = (True, True, True) \ + if args.quiet else (auto, uncertain, False) + self.bot = SimpleBot(uncertain=uncertain) if args.simple \ + else DSSPBot(uncertain=uncertain) + self.ui = Tui ( + bot=self.bot, + autoplay=auto, + interact=not uncertain, + suppress=quiet, + width=width, + height=height, + bombs=bombs + ) self.board = Board(level=level) - self.bot = BadBot(uncertain=uncertain) - self.ui = ui_class(self.bot) + self.bot = DSSPBot(uncertain=uncertain) if self.bot is None \ + else self.bot + self.ui = Tui(bot=self.bot) if self.ui is None else self.ui self.game = Game(self.board,self.ui) def run(self): diff --git a/batch/100-run-beginner-bad.sh b/batch/100-run-beginner-bad.sh deleted file mode 100644 index ab62e2e..0000000 --- a/batch/100-run-beginner-bad.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -OPTIONS="-b -a -u" -let COUNT=100 -let WINS=0 -for ((i=0;i0: + continue + if tile[0] in range(1,self.w-1): + heatmap[tile]+=0.05 + if tile[1] in range(1,self.h-1): + heatmap[tile]+=0.05 + + best = min((x for _, x in heatmap.items())) + best_tiles = [x for x,y in heatmap.items() if y == best] + + if best_tiles: + self.safe_tiles.add(sample(best_tiles,1)[0]) + return True + return False diff --git a/bots/idiot.py b/bots/idiot.py deleted file mode 100644 index 933eee9..0000000 --- a/bots/idiot.py +++ /dev/null @@ -1,18 +0,0 @@ -""" bots/idiot.py - se ensimmäinen botti joka tekee kaiken väärin """ -from tui import Action -from .bot import Bot - -class IdiotBot(Bot): - """ IdiotBot - merkistsee kaikki turvallisiksi avata """ - # pylint: disable = too-few-public-methods - - def hint(self, matrix, cursor_x, cursor_y): - """ merkitsee jonkin ruudun """ - super().hint(matrix, cursor_x, cursor_y) - # pylint: disable = consider-using-enumerate - for ty in range(len(matrix[0])): - for tx in range(len(matrix)): - if matrix[tx][ty]==12: - return(Action.SAFE, tx, ty) - return (Action.NOOP, cursor_x, cursor_y) - \ No newline at end of file diff --git a/bots/simple.py b/bots/simple.py new file mode 100644 index 0000000..cb3d25c --- /dev/null +++ b/bots/simple.py @@ -0,0 +1,29 @@ +""" bots/simple.py - yksinkertainen botti joka etsii vain yhdeltä laatalta """ +from random import sample +from .bot import Bot + +class SimpleBot(Bot): + """ SimpleBot - perustyhmä botti """ + + def search(self): + """ simple_search - jos viereisten avaamattomien määrä tästmää """ + tiles = self.get_interesting_tiles() + for tile in tiles: + c = self.get_value(tile) + n = self.get_neighbours(tile) + self.remove_number_tiles(n) + c -= self.remove_bomb_tiles(n) + if c == 0: + for safe in n: + self.safe_tiles.add(safe) + if c == len(n): + for bomb in n: + self.bomb_tiles.add(bomb) + return self.saved_hints() + + def lucky_guess(self): + tiles = self.get_unknown_tiles() + if tiles: + self.safe_tiles.add(sample(sorted(tiles),1)[0]) + return True + return False diff --git a/doc/viikkoraportti3.pdf b/doc/viikkoraportti3.pdf new file mode 100644 index 0000000..ea16a4d Binary files /dev/null and b/doc/viikkoraportti3.pdf differ diff --git a/game/game.py b/game/game.py index 76ee382..2a4efad 100644 --- a/game/game.py +++ b/game/game.py @@ -6,10 +6,7 @@ class Game: def __init__(self, board, ui): self.board = board self.ui = ui - self.x, self.y = self.ui.game_begin( - self.board.get_width(), - self.board.get_height() - ) + self.x, self.y = board.get_width()//2, board.get_height()//2 def __del__(self): diff --git a/tui/__init__.py b/tui/__init__.py index f39cd45..c1c5b91 100644 --- a/tui/__init__.py +++ b/tui/__init__.py @@ -1,4 +1,3 @@ """ tui - hoitaa käyttäjälle katseltavaa ja havaitsee syötteet """ from .tui import Tui -from .autotui import AutoTui from .static import Action diff --git a/tui/ansi_draw.py b/tui/ansi_draw.py new file mode 100644 index 0000000..dd304d0 --- /dev/null +++ b/tui/ansi_draw.py @@ -0,0 +1,61 @@ +""" 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 = 15): + print(end="\n"*height) + + 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 """ + return True + + 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 """ + return True + + def status_line(self, text): + """ draw_status_line - tulostaa pelitietorivin""" + return True diff --git a/tui/autotui.py b/tui/autotui.py deleted file mode 100644 index ae5d58c..0000000 --- a/tui/autotui.py +++ /dev/null @@ -1,27 +0,0 @@ -""" autotui - pelaa botin antamat vinkit jonka jälkeen käyttäjän """ -from .tui import Tui -from .static import Action -from .ansi import Ansi - -class AutoTui(Tui): - """ Tui - Luokka joka tekee botin vinkit ensin """ - def matrix_selector(self, matrix, x, y): - """ yritetään pyydellä botilta vinkkiä ensin """ - if self.bot is not None: - action, x, y = self.bot.hint(matrix, x, y) - if action != Action.NOOP: - self.draw_matrix(matrix, -1, -1) - if action==Action.SAFE: - action = Action.OPEN - return action, x, y - - return super().matrix_selector(matrix, x, y) - - def show_board_with_text(self, matrix, x, y, text): - """ näyttää laudan, tekstin alla (ei odota nappia) """ - self.draw_matrix(matrix, x, y) - print(text) - Ansi.cup(1) - - def game_end(self, matrix): - """ pelin lopetus """ diff --git a/tui/kbd.py b/tui/kbd.py new file mode 100644 index 0000000..b6d2363 --- /dev/null +++ b/tui/kbd.py @@ -0,0 +1,78 @@ +""" tui/kbd.py - näppäimistön käsittellijä """ +# pylint: disable = multiple-imports +import termios, fcntl, sys, os +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 + 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) + + def __del__(self): + # palautetaan terminaali takaisin alkupetäiseen uskoon + fd = sys.stdin.fileno() + termios.tcsetattr(fd, termios.TCSAFLUSH, self.oldterm) + fcntl.fcntl(fd, fcntl.F_SETFL, self.oldflags) + + 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.BOMB | 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/tui/tui.py b/tui/tui.py index e925e57..7d8f565 100644 --- a/tui/tui.py +++ b/tui/tui.py @@ -1,136 +1,89 @@ -""" tui/tui.py - teksikäyttöliittymä """ +""" tui/tui.py - runko käyttöliittymälle """ # pylint: disable = multiple-imports -import termios, fcntl, sys, os -from time import sleep -from .static import Action, ActionKeys, TileTypes -from .ansi import Ansi +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 """ - def __init__(self, bot = None): - # Vaatii hieman terminaaliasetusten muokkaamista jotta yksittäiset - # napin painallukset voidaan lukea - # https://stackoverflow.com/questions/983354/how-do-i-wait-for-a-pressed-key - fd = sys.stdin.fileno() - self.oldterm = termios.tcgetattr(fd) + # pylint: disable = unused-argument + def __init__(self, **opts): + self.bot = opts['bot'] if 'bot' in opts else None + self.autoplay = opts['autoplay'] if 'autoplay' in opts else False + self.interact = opts['interact'] if 'interact' in opts else True + self.suppress = opts['suppress'] if 'suppress' in opts else False + self.height = opts['height'] if 'height' in opts else 15 + + # jos ei oo bottia pitää olla interaktiivinen + if self.bot is None: + self.autoplay = False + self.interact = True + self.suppress = False + + # jos ei mitään näytetä ei voi olla interaktiivinen + self.interact = False if self.suppress else self.interact + + # automaattipeli pitää olla päällä jos ei interaktiivinen + self.autoplay = self.autoplay if self.interact else True + + if self.interact: + self.kbd = Kbd() + else: + self.kbd = NoKbd() + + if self.suppress: + self.draw = SuppressDraw() + else: + self.draw = AnsiDraw(height=self.height) - 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) - - self.bot = bot - - - def __del__(self): - # palautetaan terminaali takaisin alkupetäiseen uskoon - fd = sys.stdin.fileno() - termios.tcsetattr(fd, termios.TCSAFLUSH, self.oldterm) - fcntl.fcntl(fd, fcntl.F_SETFL, self.oldflags) - print() - - - def draw_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 draw_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.draw_tile(matrix[x][y], hilight) - print() + 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: + self.draw.matrix(matrix, -1, -1) + return Action.OPEN if action==Action.SAFE else action, x, y - 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 + # ilman näppiskäsittelijää voidaan lopettaa + if not self.interact: + return Action.QUIT, 0, 0 - def matrix_selector(self, matrix, x, y): - """ piirtää ruudukon ja antaa käyttäjän valita nuolinäppäimillä """ - self.draw_matrix(matrix, x, y) + w, h = len(matrix), len(matrix[0]) while True: - action = self.read_action() + 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.BOMB | Action.SAFE: if matrix[x][y] >= 10: 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 < len(matrix[0])-1 else y - case Action.RIGHT: - x = x+1 if x < len(matrix)-1 else x - case Action.TOP: - y = 0 - case Action.BOTTOM: - y = len(matrix[0])-1 - case Action.BEGIN: - x = 0 - case Action.END: - x = len(matrix)-1 case Action.HINT: if self.bot is not None: return self.bot.hint(matrix, x, y) - self.draw_matrix(matrix, x, y) - - - def show_board_with_text(self, matrix, x, y, text): - """ näyttää laudan, tekstin alla ja jää odottelemaan nappia """ - self.draw_matrix(matrix, x, y) - print(text) - Ansi.cup(1) - self.read_action() - - - def game_begin(self, width, height): - """ ruudun alustus ja lähtökoordinaatien määritys """ - print(end="\n"*(height+1)) - Ansi.cup(1) - return width//2, height//2 - def game_over(self, matrix, x, y): - """ näyttää pelin lopputilanteen ja odottaa nappia """ - self.show_board_with_text(matrix, x, y, - "KUOLEMA! ...näppäimellä eteenpäin...") - + """ tehtävät kun kuolee """ + self.draw.matrix(matrix, x, y) + self.draw.status_line( + "K " if self.suppress else "Peli ohitse! Kuolit!" + ) + self.kbd.read_action() def game_win(self, matrix, x, y): - """ näyttäää pelin lopputilanteen ja odottaa nappia """ - self.show_board_with_text(matrix, x, y, - "VOITTO! ...näppäimellä eteenpäin...") - + """ tehtävät kun voittaa """ + self.draw.matrix(matrix, x, y) + self.draw.status_line( + "V " if self.suppress else "Peli ohitse! Voitit!" + ) + self.kbd.read_action() def game_end(self, matrix): - """ pelin lopetus """ - self.show_board_with_text(matrix, -1, -1, - "PELI OHI! ...näppäimellä eteenpäin...") - print() + """ tehtävät ihan pelin lopuksi """ + if self.interact: + self.draw.matrix(matrix, -1, -1) + self.draw.status_line("Kiitos! ") -- cgit v1.2.3