Загрузка данных
import sys, time, math, os, ctypes
import tkinter as tk
from typing import Dict, Tuple, Optional
from PIL import Image
import mss
VK_F7 = 0x76
VK_ESCAPE = 0x1B
def is_key_pressed(vk_code: int) -> bool:
try:
return bool(ctypes.windll.user32.GetAsyncKeyState(vk_code) & 0x8000)
except Exception:
return False
class MinesweeperBitboard:
def __init__(self, rows: int, cols: int):
self.rows = rows
self.cols = cols
self.unopened_mask = 0
self.flags_mask = 0
self.opened_empty_mask = 0
self.numbers_masks: Dict[int, int] = {num: 0 for num in range(1, 9)}
def cell_to_index(self, row: int, col: int) -> int:
return row * self.cols + col
def set_bit(self, mask: int, row: int, col: int) -> int:
return mask | (1 << self.cell_to_index(row, col))
def get_bit(self, mask: int, row: int, col: int) -> bool:
return bool((mask >> self.cell_to_index(row, col)) & 1)
def print_mask(self, mask: int, title: str = "Bitmask") -> None:
print(f"\n--- {title} ({self.rows}x{self.cols}) ---")
col_header = " " + "".join([str(c % 10) for c in range(self.cols)])
print(col_header)
print(" +" + "-" * self.cols + "+")
for r in range(self.rows):
row_str = f"{r:2d} |"
for c in range(self.cols):
row_str += "1" if self.get_bit(mask, r, c) else "0"
print(row_str + "|")
print(" +" + "-" * self.cols + "+")
print(f"Dec: {mask}\nHex: 0x{mask:X}")
def print_board_summary(self) -> None:
print(f"\nBOARD ({self.rows}x{self.cols}):")
if self.cols <= 10:
col_header = " " + " ".join([str(c) for c in range(self.cols)])
else:
col_header = " " + " ".join([str(c // 10) if c >= 10 else " " for c in range(self.cols)]) + "\n " + " ".join([str(c % 10) for c in range(self.cols)])
print(col_header)
border = " +" + "-" * (self.cols * 2 + 1) + "+"
print(border)
for r in range(self.rows):
row_cells = []
for c in range(self.cols):
if self.get_bit(self.flags_mask, r, c):
row_cells.append("!")
elif self.get_bit(self.unopened_mask, r, c):
row_cells.append("?")
elif self.get_bit(self.opened_empty_mask, r, c):
row_cells.append("0")
else:
found = None
for num in range(1, 9):
if self.get_bit(self.numbers_masks[num], r, c):
found = str(num)
break
row_cells.append(found if found else "E")
print(f"{r:2d} | " + " ".join(row_cells) + f" | {r:2d}")
print(border)
print(col_header)
def get_fingerprint(self) -> Tuple:
return (self.unopened_mask, self.flags_mask, self.opened_empty_mask, tuple(self.numbers_masks[n] for n in range(1, 9)))
class ScreenSelector:
def __init__(self):
self.selected_region: Optional[Tuple[int, int, int, int]] = None
def select_area(self) -> Optional[Tuple[int, int, int, int]]:
root = tk.Tk()
root.attributes("-fullscreen", True)
root.attributes("-topmost", True)
try:
root.attributes("-alpha", 0.35)
except Exception:
pass
canvas = tk.Canvas(root, highlightthickness=0, bg="#000000", cursor="cross")
canvas.pack(fill=tk.BOTH, expand=True)
rect_id = canvas.create_rectangle(0, 0, 0, 0, outline="#00FF00", width=2)
start_x = start_y = 0
def on_down(e):
nonlocal start_x, start_y
start_x, start_y = e.x, e.y
def on_drag(e):
canvas.coords(rect_id, start_x, start_y, e.x, e.y)
def on_up(e):
x1, x2 = min(start_x, e.x), max(start_x, e.x)
y1, y2 = min(start_y, e.y), max(start_y, e.y)
if (x2 - x1) > 10 and (y2 - y1) > 10:
self.selected_region = (x1, y1, x2 - x1, y2 - y1)
canvas.bind("<ButtonPress-1>", on_down)
canvas.bind("<B1-Motion>", on_drag)
canvas.bind("<ButtonRelease-1>", on_up)
root.bind("<Escape>", lambda e: root.destroy())
root.bind("<Return>", lambda e: root.destroy())
root.bind("<Double-Button-1>", lambda e: root.destroy())
root.mainloop()
return self.selected_region
class CellAnalyzer:
COLOR_MAP = {
1: (0, 0, 255), 2: (0, 128, 0), 3: (255, 0, 0), 4: (0, 0, 128),
5: (128, 0, 0), 6: (0, 128, 128), 7: (0, 0, 0), 8: (128, 128, 128)
}
@classmethod
def classify_cell(cls, cell_img: Image.Image) -> str:
w, h = cell_img.size
if w < 4 or h < 4:
return 'UNOPENED'
img = cell_img.convert("RGB")
pixels = img.load()
tl = [pixels[x, y] for x in range(1, min(w, 5)) for y in range(1, min(h, 5))]
br = [pixels[x, y] for x in range(max(0, w - 5), w - 1) for y in range(max(0, h - 5), h - 1)]
tl_avg = sum(sum(p) / 3.0 for p in tl) / max(len(tl), 1)
br_avg = sum(sum(p) / 3.0 for p in br) / max(len(br), 1)
is_beveled = (tl_avg - br_avg) > 35.0
mx, my = max(1, int(w * 0.2)), max(1, int(h * 0.2))
inner = [pixels[x, y] for x in range(mx, w - mx) for y in range(my, h - my)]
if not inner:
return 'UNOPENED' if is_beveled else 'EMPTY'
flags = sum(1 for (r, g, b) in inner if r > 160 and g < 70 and b < 70)
if flags > len(inner) * 0.05:
return 'FLAG'
if is_beveled:
return 'UNOPENED'
counts = {n: 0 for n in range(1, 9)}
for r, g, b in inner:
if abs(r - g) < 15 and abs(g - b) < 15 and abs(r - b) < 15 and r > 150:
continue
best_num, min_dist = None, 80.0
for num, ref in cls.COLOR_MAP.items():
dist = math.sqrt((r - ref[0])**2 + (g - ref[1])**2 + (b - ref[2])**2)
if dist < min_dist:
min_dist, best_num = dist, num
if best_num:
counts[best_num] += 1
req = max(3, int(len(inner) * 0.025))
top_num = max(counts, key=counts.get)
return str(top_num) if counts[top_num] >= req else 'EMPTY'
def analyze_screen(rows: int, cols: int, region: Tuple[int, int, int, int]) -> MinesweeperBitboard:
rx, ry, rw, rh = region
with mss.mss() as sct:
sct_img = sct.grab({"top": ry, "left": rx, "width": rw, "height": rh})
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
board = MinesweeperBitboard(rows, cols)
cw, ch = rw / cols, rh / rows
for r in range(rows):
for c in range(cols):
crop = img.crop((int(c * cw), int(r * ch), int((c + 1) * cw), int((r + 1) * ch)))
st = CellAnalyzer.classify_cell(crop)
if st == 'UNOPENED':
board.unopened_mask = board.set_bit(board.unopened_mask, r, c)
elif st == 'FLAG':
board.flags_mask = board.set_bit(board.flags_mask, r, c)
elif st == 'EMPTY':
board.opened_empty_mask = board.set_bit(board.opened_empty_mask, r, c)
elif st.isdigit():
num = int(st)
if 1 <= num <= 8:
board.numbers_masks[num] = board.set_bit(board.numbers_masks[num], r, c)
return board
def get_grid_size() -> Tuple[int, int]:
print("1) 9x9 | 2) 16x16 | 3) 16x30 | 4) Custom")
ch = input("Выбор [1-4] (def: 1): ").strip()
if ch == "2": return 16, 16
if ch == "3": return 16, 30
if ch == "4":
try: return max(1, int(input("N: "))), max(1, int(input("M: ")))
except Exception: pass
return 9, 9
if __name__ == '__main__':
rows, cols = get_grid_size()
print("Выделите зону мышкой. [F7] - Новая зона | [ESC] - Выход")
region = ScreenSelector().select_area()
last_fp = None
f7_prev = False
while True:
if is_key_pressed(VK_ESCAPE):
break
f7_now = is_key_pressed(VK_F7)
if f7_now and not f7_prev:
time.sleep(0.2)
new_reg = ScreenSelector().select_area()
if new_reg:
region, last_fp = new_reg, None
f7_prev = f7_now
if region:
board = analyze_screen(rows, cols, region)
fp = board.get_fingerprint()
if fp != last_fp:
last_fp = fp
os.system('cls' if os.name == 'nt' else 'clear')
print(f"[F7] Новая зона | [ESC] Выход | {rows}x{cols} | {time.strftime('%H:%M:%S')}")
board.print_board_summary()
board.print_mask(board.unopened_mask, "UNOPENED (?)")
board.print_mask(board.flags_mask, "FLAGS (!)")
board.print_mask(board.opened_empty_mask, "EMPTY (0)")
for n in range(1, 9):
if board.numbers_masks[n]:
board.print_mask(board.numbers_masks[n], f"NUMBER ({n})")
time.sleep(0.33)