<commit_before>__author__ = '<NAME>'

import itertools
import copy
import sys
import logging
import time
import random
from multiprocessing import Pool, cpu_count
import signal

from PIL import ImageGrab

from utility.mouse import *
from utility.screen import *


def guess_grid_item(pixel):
    closest_amount = 0.0
    closest_item = None
    for griditemtype in Grid.GridItemTypes:
        amount = griditemtype.color.compare(pixel)
        if amount > closest_amount:
            closest_amount = amount
            closest_item = griditemtype
    return closest_item, closest_amount


def calibrate_colors():
    input("Place mouse over center of top left gem and press enter.")
    xoffset, yoffset = Mouse.get_position()
    # Move the mouse away
    win32api.SetCursorPos((xoffset - 50, yoffset - 50))
    screengrab = ImageGrab.grab()
    for y in range(5):
        row = []
        for x in range(5):
            posx = xoffset + (x * 50)
            posy = yoffset + (y * 50)
            row.append(get_avg_pixel(screengrab, posx, posy))

        print(", ".join([str(color) for color in row]))


class Move:
    """
    Captures data related to a move.
    """

    def __init__(self, x1, y1, itemtype1, x2, y2, itemtype2):
        self.x1 = x1
        self.y1 = y1
        self.itemtype1 = itemtype1
        self.x2 = x2
        self.y2 = y2
        self.itemtype2 = itemtype2

        self.points = 0
        self.submove = None

    def get_total_points(self, depth_factor=0.75):
        if self.submove is None:
            return self.points
        else:
            # Until something is accounted for unknown blocks creating matches,
            # we need to slightly favor clearing things earlier.
            return self.points + (self.submove.get_total_points() * depth_factor)

    def describe(self):
        description = "{0},{1}({2})<->{3},{4}({5}) {6:0.1f}pts".format(
            self.x1 + 1, self.y1 + 1, self.itemtype1.name[0],
            self.x2 + 1, self.y2 + 1, self.itemtype2.name[0],
            self.points)
        if self.submove is None:
            return description
        else:
            return "{0}, {1}".format(description, self.submove.describe())


class GridItemType:
    count = 0

    def __init__(self, name, color):
        self.name = name
        self.color = color
        self.index = GridItemType.count
        GridItemType.count += 1

    def __hash__(self):
        return self.index

    def __eq__(self, other):
        return self.index == other.index


class Grid:
    """
    Abstraction of the 5x5 board grid applicable to both Gemology and Dragon Souls
    """
    debug = False
    fast0 = False
    delay = 1.5
    GridItemTypes = []
    # Special type for unknown grid items.
    GridItemTypeUnknown = GridItemType('Unknown', None)

    def __init__(self, xoffset, yoffset, grid=None, depth=3, processes=-1):
        self.cleared = None
        self.depth = depth
        if processes == -1:
            self.processes = cpu_count()
        else:
            self.processes = processes

        if grid is None:
            screengrab = ImageGrab.grab()

            # Let's try to adjust the offsets to see if there's a more accurate position.
            print("Searching for good reference point...")
            griditem, best_accuracy = guess_grid_item(get_avg_pixel(screengrab, xoffset, yoffset))
            best_x = xoffset
            best_y = yoffset
            search_r = 15
            positions = list(itertools.product(
                range(xoffset - search_r, xoffset + search_r),
                range(yoffset - search_r, yoffset + search_r))
            )
            for posx, posy in positions:
                griditem, accuracy = guess_grid_item(get_avg_pixel(screengrab, posx, posy))
                if accuracy > best_accuracy:
                    best_accuracy = accuracy
                    best_x = posx
                    best_y = posy
                if best_accuracy > 0.90:
                    break

            xoffset = best_x
            yoffset = best_y

            self.xoffset = xoffset
            self.yoffset = yoffset

            if not self.update():
                print("ERROR: Initial accuracy of board recognition is too low.")
                sys.exit(1)
        else:
            #Use the supplied grid.
            self.grid = grid
            self.xoffset = xoffset
            self.yoffset = yoffset

    def copy_grid(self):
        """
        Copies our existing grid array to separate from a prior copy of this class
        :return:
        """
        newgrid = []
        for x in range(5):
            column = []
            for y in range(5):
                column.append(self.grid[x][y])
            newgrid.append(column)
        self.grid = newgrid

    def update(self, compareprevious=False):
        screengrab = ImageGrab.grab()
        lowest_accuracy = 1.00
        newgrid = []
        item_changed = False
        for x in range(5):
            column = []
            for y in range(5):
                posx = self.xoffset + (x * 50)
                posy = self.yoffset + (y * 50)
                #print("{0}, {1} : {2}".format(x, y, get_avg_pixel(screengrab, posx, posy)))
                griditemtype, accuracy = guess_grid_item(get_avg_pixel(screengrab, posx, posy))
                #colors += " {0:>10}({1:>03.1f}%)".format(gem.name, accuracy * 100.0)
                column.append(griditemtype)
                if accuracy < lowest_accuracy:
                    lowest_accuracy = accuracy
                if compareprevious and griditemtype != self.grid[x][y]:
                    item_changed = True
            newgrid.append(column)

        if lowest_accuracy < 0.60:
            #print("WARNING: Lowest accuracy was {0:02.1f}%".format(lowest_accuracy * 100.0))
            return False
        if compareprevious and not item_changed:
            print("Warning, grid did not change.")
        self.grid = newgrid

        # Make sure the game didn't enter some bad state.
        self.clear()
        if self.drop() > 0:
            print("ERROR: The updated grid contains gems that should have cleared.")
            sys.exit(1)
        return True

    def describe_grid_large(self):
        desc = ""

        row = " "
        for x in range(5):
            row += " {0:>10}".format(x+1)
        desc += row + "\n"
        for y in range(5):
            row = "{0}".format(y+1)
            for x in range(5):
                if self.grid[x][y] is None:<commit_msg>Better grid init, and retries on updates. Init search now does a spiral search around clicked point, with a slightly higher threshold to early exit. This should make init on random dragonsoul (brown), less annoying. Updates now retry if accuracy is low, or the grid seems invalid. Fixes a few cases where it thinks the move has finished too early or other random errors.<commit_after>__author__ = '<NAME>'

import itertools
import copy
import sys
import logging
import time
import random
from multiprocessing import Pool, cpu_count
import signal

from PIL import ImageGrab

from utility.mouse import *
from utility.screen import *


def guess_grid_item(pixel):
    closest_amount = 0.0
    closest_item = None
    for griditemtype in Grid.GridItemTypes:
        amount = griditemtype.color.compare(pixel)
        if amount > closest_amount:
            closest_amount = amount
            closest_item = griditemtype
    return closest_item, closest_amount


def calibrate_colors():
    input("Place mouse over center of top left gem and press enter.")
    xoffset, yoffset = Mouse.get_position()
    # Move the mouse away
    win32api.SetCursorPos((xoffset - 50, yoffset - 50))
    screengrab = ImageGrab.grab()
    for y in range(5):
        row = []
        for x in range(5):
            posx = xoffset + (x * 50)
            posy = yoffset + (y * 50)
            row.append(get_avg_pixel(screengrab, posx, posy))

        print(", ".join([str(color) for color in row]))


class Move:
    """
    Captures data related to a move.
    """

    def __init__(self, x1, y1, itemtype1, x2, y2, itemtype2):
        self.x1 = x1
        self.y1 = y1
        self.itemtype1 = itemtype1
        self.x2 = x2
        self.y2 = y2
        self.itemtype2 = itemtype2

        self.points = 0
        self.submove = None

    def get_total_points(self, depth_factor=0.75):
        if self.submove is None:
            return self.points
        else:
            # Until something is accounted for unknown blocks creating matches,
            # we need to slightly favor clearing things earlier.
            return self.points + (self.submove.get_total_points() * depth_factor)

    def describe(self):
        description = "{0},{1}({2})<->{3},{4}({5}) {6:0.1f}pts".format(
            self.x1 + 1, self.y1 + 1, self.itemtype1.name[0],
            self.x2 + 1, self.y2 + 1, self.itemtype2.name[0],
            self.points)
        if self.submove is None:
            return description
        else:
            return "{0}, {1}".format(description, self.submove.describe())


class GridItemType:
    count = 0

    def __init__(self, name, color):
        self.name = name
        self.color = color
        self.index = GridItemType.count
        GridItemType.count += 1

    def __hash__(self):
        return self.index

    def __eq__(self, other):
        return self.index == other.index


class Grid:
    """
    Abstraction of the 5x5 board grid applicable to both Gemology and Dragon Souls
    """
    debug = False
    fast0 = False
    delay = 1.5
    GridItemTypes = []
    # Special type for unknown grid items.
    GridItemTypeUnknown = GridItemType('Unknown', None)

    def __init__(self, xoffset, yoffset, grid=None, depth=3, processes=-1):
        self.cleared = None
        self.depth = depth
        self.grid = None
        if processes == -1:
            self.processes = cpu_count()
        else:
            self.processes = processes

        if grid is None:
            screengrab = ImageGrab.grab()

            # Let's try to adjust the offsets to see if there's a more accurate position.
            print("Searching for good reference point...")
            griditem, best_accuracy = guess_grid_item(get_avg_pixel(screengrab, xoffset, yoffset))
            best_x = xoffset
            best_y = yoffset
            search_r = 15
            positions = list(itertools.product(
                range(xoffset - search_r, xoffset + search_r),
                range(yoffset - search_r, yoffset + search_r))
            )
            for posx, posy in positions:
                griditem, accuracy = guess_grid_item(get_avg_pixel(screengrab, posx, posy))
                if accuracy > best_accuracy:
                    best_accuracy = accuracy
                    best_x = posx
                    best_y = posy
                if best_accuracy > 0.90:
                    break

            xoffset = best_x
            yoffset = best_y

            self.xoffset = xoffset
            self.yoffset = yoffset

            if not self.update():
                print("ERROR: Initial accuracy of board recognition is too low.")
                sys.exit(1)
        else:
            #Use the supplied grid.
            self.grid = grid
            self.xoffset = xoffset
            self.yoffset = yoffset

    def copy_grid(self):
        """
        Copies our existing grid array to separate from a prior copy of this class
        :return:
        """
        newgrid = []
        for x in range(5):
            column = []
            for y in range(5):
                column.append(self.grid[x][y])
            newgrid.append(column)
        self.grid = newgrid

    def update(self, compareprevious=False):
        timeout = time.time() + 10.0
        while True:
            screengrab = ImageGrab.grab()
            lowest_accuracy = 1.00
            newgrid = []
            item_changed = False
            for x in range(5):
                column = []
                for y in range(5):
                    posx = self.xoffset + (x * 50)
                    posy = self.yoffset + (y * 50)
                    #print("{0}, {1} : {2}".format(x, y, get_avg_pixel(screengrab, posx, posy)))
                    griditemtype, accuracy = guess_grid_item(get_avg_pixel(screengrab, posx, posy))
                    #colors += " {0:>10}({1:>03.1f}%)".format(gem.name, accuracy * 100.0)
                    column.append(griditemtype)
                    if accuracy < lowest_accuracy:
                        lowest_accuracy = accuracy
                    if compareprevious and griditemtype != self.grid[x][y]:
                        item_changed = True
                newgrid.append(column)

            if lowest_accuracy < 0.60:
                #print("WARNING: Lowest accuracy was {0:02.1f}%".format(lowest_accuracy * 100.0))
                return False
            if compareprevious and not item_changed:
                print("Warning, grid did not change.")
            oldgrid = self.grid
            self.grid = newgrid

            # Make sure the game didn't enter some bad state.
            self.clear()
            if self.drop() > 0:
                if time.time() > timeout:
                    print("ERROR: Grid state is bad. Please exit and re-enter the minigame and try again.")
                    sys.exit(1)
                print("WARNING: The updated grid contains gems that should have cleared. Waiting 1 second to retry.")
                self.grid = oldgrid
                time.sleep(1.0)
            else:
                break
        return True

    def describe_grid_large(self):
        desc = ""

        row = " "
        for x in range(5):
            row += " {0:>10}".format(x+1)
        desc += row + "\n"
        for y in range(5):
            row = "{0}".format(y+1)
            for x in range(5):
                if self.grid[x][y] is None:
{'fraction_non_alphanumeric': 0.09444922547332187, 'fraction_numerical': 0.017928858290304075, 'mean_word_length': 1.7445384766778194, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '148054', 'n_tokens_mistral': 4142, 'n_tokens_neox': 3933, 'n_words': 1170}