summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAineopintojen-harjoitustyo-Algoritmit-j <github-hy-tiralabra@v.hix.fi>2024-01-29 02:34:32 +0200
committerAineopintojen-harjoitustyo-Algoritmit-j <github-hy-tiralabra@v.hix.fi>2024-01-29 02:34:32 +0200
commite591d15abc4943a74c39f0fcab065213828a1514 (patch)
treebc22931abb08d8c213ba4eb81a85298d69e88890
parent0c034e6fbae0833f8524caf223deab450e9bb1b4 (diff)
Updating too much. Renewed bots and tui.
-rw-r--r--README.md5
-rw-r--r--__main__.py40
-rw-r--r--app.py32
-rw-r--r--batch/100-run-beginner-bad.sh9
-rw-r--r--batch/1000-run-beginner-bad.sh9
-rw-r--r--board/static.py2
-rw-r--r--bots/__init__.py4
-rw-r--r--bots/bad.py73
-rw-r--r--bots/bot.py133
-rw-r--r--bots/dssp.py80
-rw-r--r--bots/idiot.py18
-rw-r--r--bots/simple.py29
-rw-r--r--doc/viikkoraportti3.pdfbin0 -> 54141 bytes
-rw-r--r--game/game.py5
-rw-r--r--tui/__init__.py1
-rw-r--r--tui/ansi_draw.py61
-rw-r--r--tui/autotui.py27
-rw-r--r--tui/kbd.py78
-rw-r--r--tui/tui.py173
19 files changed, 493 insertions, 286 deletions
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
@@ -23,20 +23,50 @@ parser.add_argument(
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',
action='store_true'
)
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;i<COUNT;i++)); do
- python3 miinaharava $OPTIONS && let WINS++;
-done
-let PERCENT=100*WINS/COUNT
-echo -ne "\n\n## Voittoja $WINS/$COUNT ($PERCENT%)\n\n"
diff --git a/batch/1000-run-beginner-bad.sh b/batch/1000-run-beginner-bad.sh
deleted file mode 100644
index 394aeb3..0000000
--- a/batch/1000-run-beginner-bad.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-OPTIONS="-b -a -u"
-let COUNT=1000
-let WINS=0
-for ((i=0;i<COUNT;i++)); do
- python3 miinaharava $OPTIONS && let WINS++;
-done
-let PERCENT=100*WINS/COUNT
-echo -ne "\n\n## Voittoja $WINS/$COUNT ($PERCENT%)\n\n"
diff --git a/board/static.py b/board/static.py
index eea30fd..d6b48dd 100644
--- a/board/static.py
+++ b/board/static.py
@@ -29,6 +29,6 @@ class Tile(IntEnum):
LevelSpecs = {
Level.BEGINNER: ( 9, 9, 10 ),
- Level.INTERMEDIATE: ( 15, 15, 40 ),
+ Level.INTERMEDIATE: ( 16, 16, 40 ),
Level.EXPERT: ( 30, 16, 99 )
}
diff --git a/bots/__init__.py b/bots/__init__.py
index f180c3a..f346d07 100644
--- a/bots/__init__.py
+++ b/bots/__init__.py
@@ -1,4 +1,4 @@
""" bots - tämä alimoduli tarjoaa tekoälyn """
-from .bot import Bot
-from .bad import BadBot
+from .simple import SimpleBot
+from .dssp import DSSPBot
diff --git a/bots/bad.py b/bots/bad.py
deleted file mode 100644
index 5197dc9..0000000
--- a/bots/bad.py
+++ /dev/null
@@ -1,73 +0,0 @@
-""" bots/bad.py - botti joka ehkä osaa merkata jonkun asian """
-from random import sample
-from tui import Action
-from .bot import Bot
-
-class BadBot(Bot):
- """ IdiotBot - merkistsee kaikki turvallisiksi avata """
- # pylint: disable = too-few-public-methods
- def missing_bombs(self, matrix, x, y):
- """ test how many boms are not found at the coordinate """
- dx = len(matrix)
- dy = len(matrix[0])
- bcount = 0
- for nx, ny in self.neighbours(dx, dy, x, y):
- if matrix[nx][ny] in (9,11):
- bcount+=1
- return matrix[x][y]-bcount
-
- def get_tiles_at_border(self, matrix):
- """ get interesting tiles on the border of cleared and masked area """
- tiles = []
- w = len(matrix)
- h = len(matrix[0])
- for y in range(h):
- for x in range(w):
- if matrix[x][y] < 12:
- open_tiles=1
- masked_tiles=0
- else:
- open_tiles=0
- masked_tiles=1
- for nx, ny in self.neighbours(w, h, x, y):
- if matrix[nx][ny] < 12:
- open_tiles+=1
- else:
- masked_tiles+=1
- if open_tiles and masked_tiles:
- tiles.append( (x,y) )
- return tiles
-
- def get_unopened_tiles(self, matrix):
- """ get interesting tiles on the border of cleared and masked area """
- tiles = []
- w = len(matrix)
- h = len(matrix[0])
- for y in range(h):
- for x in range(w):
- if matrix[x][y] == 12:
- tiles.append( (x,y) )
- return tiles
-
- def hint(self, matrix, cursor_x, cursor_y):
- """ merkitsee jonkin ruudun """
- super().hint(matrix, cursor_x, cursor_y)
- w = len(matrix)
- h = len(matrix[0])
- # pylint: disable = consider-using-enumerate
- for x, y in self.get_tiles_at_border(matrix):
- ncoords=self.neighbours(w,h,x,y)
- ntiles=self.coordinates_to_tiles(matrix,ncoords)
- unopened=ntiles.count(12)
- bombs=ntiles.count(10)
- if unopened:
- if matrix[x][y]<9 and matrix[x][y]==bombs:
- safe = ncoords[ntiles.index(12)]
- return(Action.SAFE, safe[0], safe[1])
- if matrix[x][y]-bombs==unopened:
- bomb = ncoords[ntiles.index(12)]
- return(Action.BOMB, bomb[0], bomb[1])
- if self.uncertain:
- x, y = sample(self.get_unopened_tiles(matrix),1)[0]
- return (Action.OPEN, x, y)
- return (Action.NOOP, cursor_x, cursor_y)
diff --git a/bots/bot.py b/bots/bot.py
index 5ad0bd2..49bd962 100644
--- a/bots/bot.py
+++ b/bots/bot.py
@@ -1,32 +1,133 @@
""" bots/bot.py - bottien kantaisä """
from tui import Action
+from board import Tile
class Bot():
""" Bot - perusluokka perittäväksi """
def __init__(self, **opts):
self.uncertain = opts['uncertain'] if 'uncertain' in opts else False
- self.hints = 0
+ self.safe_tiles = set()
+ self.bomb_tiles = set()
+ self.matrix = []
+ self.w = 0
+ self.h = 0
- def neighbours(self,dx,dy,x,y):
- """ palauttaa listana viereiset koordinaatit """
+ def search(self):
+ """ search - etsii pommeja tai vapaita ko joukkoihin """
+ return False
+
+ def lucky_guess(self):
+ """ lucky_guess - lisää yhden arvatun vapaan vapaiden joukkoon """
+ return Action.NOOP, 0, 0
+
+ def get_hint_from_list(self):
+ """ palauttaa vihjeen suoraan listalta """
+ if self.safe_tiles:
+ x, y = self.safe_tiles.pop()
+ return Action.SAFE, x, y
+ if self.bomb_tiles:
+ x, y = self.bomb_tiles.pop()
+ return Action.BOMB, x, y
+ return Action.NOOP, 0, 0
+
+ def saved_hints(self):
+ """ onko muuveja varastossa """
+ return self.safe_tiles or self.bomb_tiles
+
+ def hint(self, matrix, cursor_x, cursor_y):
+ """ antaa vinkin. tässä tapauksessa ei mitään """
+ self.matrix = matrix
+ self.w, self.h = self.get_dimensions()
+
+ if self.saved_hints():
+ return self.get_hint_from_list()
+ if self.search():
+ return self.get_hint_from_list()
+ if self.uncertain and self.lucky_guess():
+ return self.get_hint_from_list()
+ return Action.NOOP, cursor_x, cursor_y
+
+ def get_dimensions(self):
+ """ palauttaa matriisin dimensiot """
+ return len(self.matrix), len(self.matrix[0])
+
+ def get_neighbours(self, tile):
+ """ palauttaa viereiset koordinaatit joukkona """
+ x, y = tile
offsets = (
(-1, -1), ( 0, -1), ( 1, -1),
(-1, 0), ( 1, 0),
(-1, 1), ( 0, 1), ( 1, 1),
)
- coords=[]
+ tiles=set()
for ox, oy in offsets:
- if ox+x in range(dx):
- if oy+y in range(dy):
- coords.append((ox+x, oy+y))
- return coords
+ if ox+x in range(self.w):
+ if oy+y in range(self.h):
+ tiles.add((ox+x, oy+y))
+ return tiles
- def coordinates_to_tiles(self, matrix, coords):
- """ lukee koordinaateissa olevien ruutujen arvot listaksi """
- return [matrix[x][y] for x,y in coords]
+ def get_value(self, tile):
+ """ palauttaa laatan arvon """
+ return self.matrix[tile[0]][tile[1]]
- def hint(self, matrix, cursor_x, cursor_y):
- """ antaa vinkin. tässä tapauksessa ei mitään """
- # pylint: disable = unused-argument
- self.hints += 1
- return Action.NOOP, cursor_x, cursor_y
+ def remove_number_tiles(self, tiles):
+ """ poistaa vapaat ja vapaaksi merkityt alueet ja numerolaatat """
+ for tile in list(tiles):
+ if self.matrix[tile[0]][tile[1]] < Tile.FLAG_BOMB:
+ tiles.remove(tile)
+
+ def remove_bomb_tiles(self, tiles):
+ """ poistaa pommit ja pommiksi merkityt """
+ count=0
+ for tile in list(tiles):
+ if self.matrix[tile[0]][tile[1]] in (Tile.BOMB, Tile.FLAG_BOMB):
+ tiles.remove(tile)
+ count+=1
+ return count
+
+ def known_tile(self, tile):
+ """ tutkii onko laatta tiedetty """
+ return self.matrix[tile[0]][tile[1]] < Tile.UNOPENED
+
+ def number_tile(self, tile):
+ """ tutkii onko numerolaatta """
+ return 0 < self.matrix[tile[0]][tile[1]] < Tile.BOMB
+
+ def count_unknowns(self, tiles):
+ """ laskee tunnistamattomat laatat """
+ count=0
+ for tile in list(tiles):
+ if not self.known_tile(tile):
+ count+=1
+ return count
+
+ def remove_unknowns(self, tiles):
+ """ poistaa tunnistamattomat laatat """
+ count=0
+ for tile in list(tiles):
+ if not self.known_tile(tile):
+ tiles.remove(tile)
+ count+=1
+ return count
+
+ def get_interesting_tiles(self):
+ """ palauttaa laatat joiden naapureissa on vaihtelua """
+ tiles = set()
+ for x in range(self.w):
+ for y in range(self.h):
+ if self.number_tile((x,y)):
+ n = self.get_neighbours((x,y))
+ l = len(n)
+ r = self.count_unknowns(n)
+ if r in range(1,l-1):
+ tiles.add((x,y))
+ return tiles
+
+ def get_unknown_tiles(self):
+ """ palauttaa kaikki tuntemattomat laatat """
+ tiles = set()
+ for x in range(self.w):
+ for y in range(self.h):
+ if not self.known_tile((x,y)):
+ tiles.add((x,y))
+ return tiles
diff --git a/bots/dssp.py b/bots/dssp.py
new file mode 100644
index 0000000..33578a0
--- /dev/null
+++ b/bots/dssp.py
@@ -0,0 +1,80 @@
+""" bots/dssp.py - päättelee kahden vierekkäisen laatan perusteella """
+from random import sample
+from .simple import SimpleBot
+
+class DSSPBot(SimpleBot):
+ """ DSSPBot - perustyhmä botti """
+
+ def search(self):
+ """ search - etsii kahden vierekkäisen laatan perusteella"""
+ if super().search():
+ return True
+ tiles = list(self.get_interesting_tiles())
+ pairs = []
+ # pylint: disable = consider-using-enumerate
+ for i in range(len(tiles)):
+ for j in range(i+1,len(tiles)):
+ if abs(tiles[i][0]-tiles[j][0])==1 or abs(tiles[i][1]-tiles[j][1])==1:
+ pairs.append((tiles[i],tiles[j]))
+ pairs.append((tiles[j],tiles[i]))
+
+ for tile1, tile2 in pairs:
+ c1 = self.get_value(tile1)
+ c2 = self.get_value(tile2)
+ n1 = self.get_neighbours(tile1)
+ n2 = self.get_neighbours(tile2)
+ self.remove_number_tiles(n1)
+ self.remove_number_tiles(n2)
+ c1 -= self.remove_bomb_tiles(n1)
+ c2 -= self.remove_bomb_tiles(n2)
+
+ # otetaan vain alue1:n laatat pois vähennetään se pommeista
+ # näin tiedetään montako pommia on jäätävä yhteiselle alueelle
+ nc = n1 & n2
+ n1 = n1 - nc
+ n2 = n2 - nc
+ cc = c1 - len(n1)
+
+ # jos yhteiselle alueelle ei jääkkään pommeja
+ if cc < 1:
+ continue
+
+ # vähennetään yhteinen alue ja sen pommit alueesta 2
+ # jos jäljelle ei jää pommeja merkataan seiffeiks
+ # jos avaamattomia pommien määrä merkataan pommeiks
+ c2 -= cc
+
+ if c2 == 0:
+ for safe in n2:
+ self.safe_tiles.add(safe)
+ if cc == len(nc) and c2 == len(n2):
+ for bomb in n2:
+ self.bomb_tiles.add(bomb)
+
+ return self.saved_hints()
+
+ def lucky_guess(self):
+ heatmap = dict.fromkeys(self.get_unknown_tiles(), 0)
+ tiles = self.get_interesting_tiles()
+ for tile in tiles:
+ n = self.get_neighbours(tile)
+ c = self.get_value(tile) - self.remove_bomb_tiles(n)
+ self.remove_number_tiles(n)
+ for tile in n:
+ heatmap[tile] = max( heatmap[tile], c/len(n) )
+
+ for tile, value in heatmap.items():
+ if value>0:
+ 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
--- /dev/null
+++ b/doc/viikkoraportti3.pdf
Binary files 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! ")