From e785dbd4f726c5716f21071ed25dc35ac87c0c74 Mon Sep 17 00:00:00 2001 From: Aineopintojen-harjoitustyo-Algoritmit-j Date: Sat, 17 Feb 2024 09:41:48 +0200 Subject: Dev tools and directory structure rework. --- src/miinaharava/bots/__init__.py | 4 ++ src/miinaharava/bots/bot.py | 142 +++++++++++++++++++++++++++++++++++++++ src/miinaharava/bots/dssp.py | 75 +++++++++++++++++++++ src/miinaharava/bots/simple.py | 30 +++++++++ 4 files changed, 251 insertions(+) create mode 100644 src/miinaharava/bots/__init__.py create mode 100644 src/miinaharava/bots/bot.py create mode 100644 src/miinaharava/bots/dssp.py create mode 100644 src/miinaharava/bots/simple.py (limited to 'src/miinaharava/bots') diff --git a/src/miinaharava/bots/__init__.py b/src/miinaharava/bots/__init__.py new file mode 100644 index 0000000..f346d07 --- /dev/null +++ b/src/miinaharava/bots/__init__.py @@ -0,0 +1,4 @@ +""" bots - tämä alimoduli tarjoaa tekoälyn """ + +from .simple import SimpleBot +from .dssp import DSSPBot diff --git a/src/miinaharava/bots/bot.py b/src/miinaharava/bots/bot.py new file mode 100644 index 0000000..2f3baae --- /dev/null +++ b/src/miinaharava/bots/bot.py @@ -0,0 +1,142 @@ +""" 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.safe_tiles = set() + self.mine_tiles = set() + self.matrix = [] + self.w = 0 + self.h = 0 + + 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.mine_tiles: + x, y = self.mine_tiles.pop() + return Action.MINE, x, y + return Action.NOOP, 0, 0 + + def saved_hints(self): + """ poistetaan auenneet laatat ja palautetaan onko muuveja """ + for tile in list(self.safe_tiles): + if self.known_tile(tile): + self.safe_tiles.remove(tile) + for tile in list(self.mine_tiles): + if self.known_tile(tile): + self.mine_tiles.remove(tile) + return self.safe_tiles or self.mine_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), + ) + tiles=set() + for ox, oy in offsets: + if ox+x in range(self.w): + if oy+y in range(self.h): + tiles.add((ox+x, oy+y)) + return tiles + + def get_value(self, tile): + """ palauttaa laatan arvon """ + return self.matrix[tile[0]][tile[1]] + + 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_MINE: + tiles.remove(tile) + + def remove_mine_tiles(self, tiles): + """ poistaa pommit ja pommiksi merkityt """ + count=0 + for tile in list(tiles): + if self.matrix[tile[0]][tile[1]] in (Tile.MINE, Tile.FLAG_MINE): + 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.MINE + + 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 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_border_tiles(self): + """ palauttaa palauttaa numerolaatat joiden vieressä avaamaton """ + 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)) + if self.count_unknowns(n): + 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/src/miinaharava/bots/dssp.py b/src/miinaharava/bots/dssp.py new file mode 100644 index 0000000..1815b49 --- /dev/null +++ b/src/miinaharava/bots/dssp.py @@ -0,0 +1,75 @@ +""" 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_mine_tiles(n1) + c2 -= self.remove_mine_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ää miinoja merkataan alueet seiffeiks + c2 -= cc + + if c2 == 0: + for safe in n2: + self.safe_tiles.add(safe) + + return self.saved_hints() + + def lucky_guess(self): + heatmap = dict.fromkeys(self.get_unknown_tiles(), float(0)) + tiles = self.get_border_tiles() + for tile in tiles: + n = self.get_neighbours(tile) + c = self.get_value(tile) - self.remove_mine_tiles(n) + self.remove_number_tiles(n) + for ntile in n: + heatmap[ntile] += c/len(n) + + for tile in heatmap: + if tile[0] in range(1,self.w-1): + heatmap[tile]+=0.005 + if tile[1] in range(1,self.h-1): + heatmap[tile]+=0.005 + + 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/src/miinaharava/bots/simple.py b/src/miinaharava/bots/simple.py new file mode 100644 index 0000000..46b9506 --- /dev/null +++ b/src/miinaharava/bots/simple.py @@ -0,0 +1,30 @@ +""" 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_mine_tiles(n) + if c == 0: + for safe in n: + self.safe_tiles.add(safe) + if c == len(n): + for mine in n: + self.mine_tiles.add(mine) + 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 -- cgit v1.2.3