#!/usr/bin/env python3 import time COLUMNS = 7 BLOCK_SIZE = 60 MAX_COLUMN_HEIGHT = 546 DETECT_COLUMN_OFFSET_X = 9, 49 DETECT_COLUMN_OFFSET_Y = 11 MIN_COLUMN_SAT = 130 MIN_COLUMN_VAL = 120 COLUMN_VSHIFT = [1, 1, 1, 0, 0, 0, 0] #COLUMN_VSHIFT = [2, 2, 1, 1, 0, 0, 0] RED, PINK, GREEN, BLUE, YELLOW, NOBLOCK = range(6) BOMB_OFFSET = NOBLOCK + 1 BASIC_HUES = [248, 224, 118, 158, 26] BOMB_HUES = [250, 219, 132, 174, 38] HUE_TOLERANCE = 5 DETECT_BASIC_X = 9 DETECT_BASIC_Y = 15 DETECT_BOMB_X = 22 DETECT_BOMB_Y = 43 DETECT_BOMB_Y_TOLERANCE = 7 DETECT_BOMB_CHECK = 9, 20, 137, 230, 9 # x, y, hue, value, tolerance MIN_BASIC_SAT = 180 MIN_BOMB_SAT = 130 DETECT_EXA_X = 30 DETECT_EXA_Y = 547 EXA_HUE = 129 EXA_MIN_VAL = 194 DETECT_HELD_Y = 579 DETECT_EXA_LIGHT_X = 30 DETECT_EXA_LIGHT_Y = 609 EXA_LIGHT_HUE = 226 MIN_EXA_LIGHT_VAL = 180 def is_hue(h, hexpect): return abs(h - hexpect) <= HUE_TOLERANCE def is_basic(block): return RED <= block <= YELLOW def is_bomb(block): return block > NOBLOCK def bomb_to_basic(block): assert is_bomb(block) return block - BOMB_OFFSET def detect_columns(board): def saturated(x, y): h, s, v = board.getpixel((x, y)) return s > MIN_COLUMN_SAT and v > MIN_COLUMN_VAL miny = -min(DETECT_BASIC_Y, DETECT_BOMB_Y) for y in range(MAX_COLUMN_HEIGHT, miny + BLOCK_SIZE, -1): for col in range(COLUMNS): x = col * BLOCK_SIZE if all(saturated(x + cx, y) for cx in DETECT_COLUMN_OFFSET_X): #print('found bottom in column', col) return y + 1 + DETECT_COLUMN_OFFSET_Y - COLUMN_VSHIFT[col] def detect_block_type(board, x, y): # check for basic blocks first, use saturation filter to avoid confusing # green blocks with background h, s, v = board.getpixel((x + DETECT_BASIC_X, y + DETECT_BASIC_Y)) if s >= MIN_BASIC_SAT: for ty, hexpect in enumerate(BASIC_HUES): if is_hue(h, hexpect): return ty # if no basic block is detected, check another range of pixels for bomb # contents def check_bomb(): dx, dy, hexpect, minval, tolerance = DETECT_BOMB_CHECK for offset in range(tolerance): h, s, v = board.getpixel((x + dx, y + dy + offset)) if is_hue(h, hexpect) and v >= minval: return True return False if check_bomb(): for offset in range(DETECT_BOMB_Y_TOLERANCE): h, s, v = board.getpixel((x + DETECT_BOMB_X, y + DETECT_BOMB_Y + offset)) if s >= MIN_BOMB_SAT: for basic_ty, hexpect in enumerate(BOMB_HUES): if is_hue(h, hexpect): return basic_ty + BOMB_OFFSET # no basic block or bomb -> empty slot return NOBLOCK def detect_blocks(board): maxy = detect_columns(board) - BLOCK_SIZE miny = -min(DETECT_BASIC_Y, DETECT_BOMB_Y) for y in range(maxy, miny, -BLOCK_SIZE): for col in range(COLUMNS): x = col * BLOCK_SIZE yield detect_block_type(board, x, y + COLUMN_VSHIFT[col]) def detect_exa(board): for col in range(COLUMNS): x = col * BLOCK_SIZE + DETECT_EXA_X y = DETECT_EXA_Y + COLUMN_VSHIFT[col] h, s, v = board.getpixel((x, y)) if is_hue(h, EXA_HUE) and v >= EXA_MIN_VAL: return col def detect_held(board, exa): if exa is not None: x = exa * BLOCK_SIZE + DETECT_EXA_LIGHT_X y = DETECT_EXA_LIGHT_Y + COLUMN_VSHIFT[exa] h, s, v = board.getpixel((x, y)) if not is_hue(h, EXA_LIGHT_HUE) or v < MIN_EXA_LIGHT_VAL: return detect_block_type(board, exa * BLOCK_SIZE, DETECT_HELD_Y) return NOBLOCK def print_board(blocks, exa, held): rows = len(blocks) // COLUMNS for row in range(rows): row = rows - row row_blocks = blocks[(row - 1) * COLUMNS:row * COLUMNS] print(' ' + ''.join('rpgby.RPGBY'[ty] for ty in row_blocks)) if exa is not None: print(' ' * exa + ' |') print(' ' * int(exa > 0), '-' * (exa - 1), '(', 'rpgby RPGBY'[held], ')', '-' * (COLUMNS - exa - 2), sep='') if __name__ == '__main__': from interaction import get_exapunks_window, screenshot_board win = get_exapunks_window() win.raise_window() while True: board = screenshot_board(win) blocks = list(detect_blocks(board)) exa = detect_exa(board) held = detect_held(board, exa) print('\033c', end='') print_board(blocks, exa, held) time.sleep(0.05)