| PyPylolo |
| 1 | from tkinter import * |
| 2 | from scripts.utils import read_config_file |
| 3 | cfg = read_config_file() |
| 4 | root = Tk() |
| 5 | root.geometry('{0}x{1}'.format(cfg['info_box_size']['width'], cfg['info_box_size']['height'])) |
| 6 | root.title('PokerStarsHelper') |
| 7 | root.configure(background='ivory3') |
| 8 | lab = Label(root, anchor="w", justify=LEFT, font=("Arial", 18)) |
| 9 | lab.pack(fill="both", expand=True) |
| 10 | def update_label(text): |
| 11 | lab.configure(text=text) |
| 12 | root.update() |
| 13 | from abc import ABC, abstractmethod |
| 14 | class PokerTableRecognizer(ABC): |
| 15 | @abstractmethod |
| 16 | def detect_hero_step(self): |
| 17 | Based on the area under hero's cards, |
| 18 | we determine whether hero should make a move |
| 19 | @abstractmethod |
| 20 | def detect_hero_cards(self): |
| 21 | pass |
| 22 | @abstractmethod |
| 23 | def detect_table_cards(self): |
| 24 | @abstractmethod |
| 25 | def find_total_pot(self): |
| 26 | @abstractmethod |
| 27 | def get_dealer_button_position(self): |
| 28 | determine who is closer to the dealer button |
| 29 | @abstractmethod |
| 30 | def get_empty_seats(self, players_info): |
| 31 | find players whose places are currently vacant |
| 32 | @abstractmethod |
| 33 | def get_so_players(self, players_info): |
| 34 | find players who are not currently in the game |
| 35 | @abstractmethod |
| 36 | def find_players_bet(self, players_info): |
| 37 | from PIL import Image |
| 38 | import numpy as np |
| 39 | import cv2 |
| 40 | from mss import mss |
| 41 | from pokerstars_recognition import PokerStarsTableRecognizer |
| 42 | from utils import read_config_file, set_window_size, remove_cards, data_concatenate |
| 43 | from equity import calc_equity |
| 44 | from info_box import update_label |
| 45 | sct = mss() |
| 46 | config = read_config_file() |
| 47 | set_window_size() |
| 48 | table_data = [] |
| 49 | while True: |
| 50 | updated_table_data = [] |
| 51 | monitor = {'top': 80, 'left': 70, 'width': config['table_size']['width'], |
| 52 | 'height': config['table_size']['height']} |
| 53 | img = Image.frombytes('RGB', (config['table_size']['width'], config['table_size']['height']), |
| 54 | sct.grab(monitor).rgb) |
| 55 | img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB) |
| 56 | recognizer = PokerStarsTableRecognizer(img, config) |
| 57 | hero_step = recognizer.detect_hero_step() |
| 58 | if hero_step: |
| 59 | hero_cards = recognizer.detect_hero_cards() |
| 60 | table_cards = recognizer.detect_table_cards() |
| 61 | total_pot = recognizer.find_total_pot() |
| 62 | updated_table_data.append([hero_cards, table_cards, total_pot]) |
| 63 | if table_data == updated_table_data: |
| 64 | pass |
| 65 | else: |
| 66 | table_data = updated_table_data |
| 67 | deck = remove_cards(hero_cards, table_cards) |
| 68 | equity = calc_equity(deck, hero_cards, table_cards) |
| 69 | players_info = recognizer.get_dealer_button_position() |
| 70 | players_info = recognizer.get_empty_seats(players_info) |
| 71 | players_info = recognizer.get_so_players(players_info) |
| 72 | players_info = recognizer.assign_positions(players_info) |
| 73 | players_info = recognizer.find_players_bet(players_info) |
| 74 | text = data_concatenate(hero_cards, table_cards, total_pot, equity, players_info) |
| 75 | update_label(text) |
| 76 | deck = [eval7.Card(card) for card in deck] |
| 77 | table_cards = [eval7.Card(card) for card in table_cards] |
| 78 | hero_cards = [eval7.Card(card) for card in hero_cards] |
| 79 | max_table_cards = 5 |
| 80 | win_count = 0 |
| 81 | for _ in range(iters): |
| 82 | np.random.shuffle(deck) |
| 83 | num_remaining = max_table_cards - len(table_cards) |
| 84 | draw = deck[:num_remaining+2] |
| 85 | opp_hole, remaining_comm = draw[:2], draw[2:] |
| 86 | player_hand = hero_cards + table_cards + remaining_comm |
| 87 | opp_hand = opp_hole + table_cards + remaining_comm |
| 88 | player_strength = eval7.evaluate(player_hand) |
| 89 | opp_strength = eval7.evaluate(opp_hand) |
| 90 | if player_strength > opp_strength: |
| 91 | win_count += 1 |
| 92 | win_prob = (win_count / iters) * 100 |
| 93 | return round(win_prob, 2) |
| 94 | import yaml |
| 95 | import os |
| 96 | import cv2 |
| 97 | import numpy as np |
| 98 | from time import sleep |
| 99 | from math import sqrt |
| 100 | def read_config_file(filename='config.yaml'): |
| 101 | """ |
| 102 | Parameters: |
| 103 | filename (str): config file name |
| 104 | Returns: loaded_data (dict) |
| 105 | """ |
| 106 | with open(filename, 'r') as stream: |
| 107 | loaded_data = yaml.safe_load(stream) |
| 108 | return loaded_data |
| 109 | def sort_bboxes(bounding_boxes, method): |
| 110 | """ |
| 111 | Parameters: |
| 112 | bounding_boxes(list of lists of int): bounding_boxes in [x_0, y_0, x_1, y_1] format |
| 113 | method(int): the method of sorting bounding boxes. |
| 114 | It can be left-to-right, bottom-to-top or top-to-bottom |
| 115 | Returns: |
| 116 | bounding_boxes (list of tuple of int): sorted bounding boxes. |
| 117 | Each bounding box presented in [x_0, y_0, x_1, y_1] format |
| 118 | "" |
| 119 | methods = ['left-to-right', 'bottom-to-top', 'top-to-bottom'] |
| 120 | if method not in methods: |
| 121 | raise ValueError("Invalid method. Expected one of: %s" % methods) |
| 122 | else: |
| 123 | if method == 'left-to-right': |
| 124 | bounding_boxes.sort(key=lambda tup: tup[0]) |
| 125 | elif method == 'bottom-to-top': |
| 126 | bounding_boxes.sort(key=lambda tup: tup[1], reverse=True) |
| 127 | elif method == 'top-to-bottom': |
| 128 | bounding_boxes.sort(key=lambda tup: tup[1], reverse=False) |
| 129 | return bounding_boxes |
| 130 | def mse(img, benchmark_img): |
| 131 | """ |
| 132 | the 'Mean Squared Error' between two images that |
| 133 | is the sum of the squared difference between the two images. |
| 134 | NOTE: the two images must have the same dimension. |
| 135 | Parameters: |
| 136 | img(numpy.ndarray): image of a part of the table |
| 137 | benchmark_img(numpy.ndarray): benchmark image. |
| 138 | This image read from the folder. |
| 139 | Returns: |
| 140 | err (float): the error between two images |
| 141 | """ |
| 142 | err = np.sum((img.astype("float") - benchmark_img.astype("float")) ** 2) |
| 143 | err /= float(img.shape[0] * img.shape[1]) |
| 144 | return err |
| 145 | def image_comparison(img, benchmark_img, color_of_img): |
| 146 | """ |
| 147 | Parameters: |
| 148 | img(numpy.ndarray): image of a part of the table |
| 149 | benchmark_img(str): path to benchmark image |
| 150 | color_of_img(int): set in which format to read the image. |
| 151 | It can be cv2.IMREAD_COLOR or cv2.IMREAD_GRAYSCALE |
| 152 | Returns: |
| 153 | err (float): the error between two images |
| 154 | """ |
| 155 | colors = [cv2.IMREAD_COLOR, cv2.IMREAD_GRAYSCALE] |
| 156 | if color_of_img not in colors: |
| 157 | raise ValueError("Invalid method. Expected one of: %s" % colors) |
| 158 | benchmark_img = cv2.imread(benchmark_img, color_of_img) |
| 159 | res_img = cv2.resize(img, (benchmark_img.shape[1], benchmark_img.shape[0])) |
| 160 | if color_of_img == cv2.IMREAD_GRAYSCALE: |
| 161 | res_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2GRAY) |
| 162 | err = mse(res_img, benchmark_img) |
| 163 | return err |
| 164 | def thresholding(img, value_1, value_2): |
| 165 | """ |
| 166 | Parameters: |
| 167 | img(numpy.ndarray): image of a part of the table |
| 168 | value_1(int): threshold value |
| 169 | value_2(int): the maximum value that is assigned |
| 170 | to pixel values that exceed the threshold value |
| 171 | Returns: binary_img(numpy.ndarray) |
| 172 | """ |
| 173 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| 174 | _, binary_img = cv2.threshold(img, value_1, value_2, cv2.THRESH_BINARY) |
| 175 | return binary_img |
| 176 | def table_part_recognition(img, directory, color_of_img): |
| 177 | """ |
| 178 | Parameters: |
| 179 | img(numpy.ndarray): image of a part of the table |
| 180 | directory(str): path to the template images |
| 181 | color_of_img(int): set in which format to read the image. |
| 182 | It can be cv2.IMREAD_COLOR or cv2.IMREAD_GRAYSCALE |
| 183 | Returns: |
| 184 | table_part(str): recognized suit, value (J, K etc.), pot etc. |
| 185 | """ |
| 186 | err_dict = {} |
| 187 | for full_image_name in os.listdir(directory): |
| 188 | image_name = full_image_name.split('.')[0] |
| 189 | err = image_comparison(img, directory + full_image_name, color_of_img) |
| 190 | err_dict[image_name] = err |
| 191 | table_part = min(err_dict, key=err_dict.get) |
| 192 | return table_part |
| 193 | def convert_contours_to_bboxes(contours, min_height, min_width): |
| 194 | """ |
| 195 | convert contours to bboxes, also remove all small bounding boxes |
| 196 | Parameters: |
| 197 | contours (tuple): each individual contour is a numpy array |
| 198 | of (x, y) coordinates of boundary points of the object |
| 199 | contours(list of tuples of int): bounding boxes suits |
| 200 | and values (J, K etc.) in [x, y, w, h] format |
| 201 | Returns: |
| 202 | cards_bboxes(list of lists of int): bounding boxes suits |
| 203 | and values (J, K etc.) in [x_0, y_0, x_1, y_1] format |
| 204 | """ |
| 205 | bboxes = [cv2.boundingRect(contour) for contour in contours] |
| 206 | cards_bboxes = [] |
| 207 | for i in range(0, len(bboxes)): |
| 208 | x, y, w, h = bboxes[i][0], bboxes[i][1], \ |
| 209 | bboxes[i][2], bboxes[i][3] |
| 210 | if h >= min_height and w >= min_width: |
| 211 | contour_coordinates = [x - 1, y - 1, x + w + 1, y + h + 1] |
| 212 | cards_bboxes.append(contour_coordinates) |
| 213 | return cards_bboxes |
| 214 | def card_separator(bboxes, separators): |
| 215 | """ |
| 216 | determine which bounding box belongs to which card |
| 217 | Parameters: |
| 218 | bboxes(list of lists of int): bounding boxes suits and values (J, K etc.) |
| 219 | separators(list of int): contains values where the card ends |
| 220 | Returns: |
| 221 | sorted_dct(dict) key - card number, value - bounding boxes |
| 222 | """ |
| 223 | dct = {} |
| 224 | for bbox in bboxes: |
| 225 | for separator in separators: |
| 226 | if bbox[2] < separator: |
| 227 | dct[separators.index(separator)] = dct.get(separators.index(separator), []) + [bbox] |
| 228 | break |
| 229 | sorted_dct = {key: value for key, value in sorted(dct.items(), key=lambda item: int(item[0]))} |
| 230 | return sorted_dct |
| 231 | def find_by_template(img, path_to_image): |
| 232 | """ |
| 233 | Object detection using a "template". |
| 234 | Parameters: |
| 235 | img(numpy.ndarray): image of a part of the table/of the entire table |
| 236 | path_to_image(str): the path to a template image |
| 237 | Returns: |
| 238 | max_val(float): largest value with the most likely match |
| 239 | max_loc(tuple of int): location with the largest value. |
| 240 | Location is presented in (x, y) format |
| 241 | """ |
| 242 | template_img_gray = cv2.imread(path_to_image, 0) |
| 243 | img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| 244 | result = cv2.matchTemplate(img_gray, template_img_gray, |
| 245 | cv2.TM_CCOEFF_NORMED) |
| 246 | (min_val, max_val, min_loc, max_loc) = cv2.minMaxLoc(result) |
| 247 | return max_val, max_loc |
| 248 | def load_images(directory): |
| 249 | """ |
| 250 | Parameters: |
| 251 | directory(str): path to specific directory |
| 252 | Returns: |
| 253 | images(list of numpy.ndarray): images of a part of the table |
| 254 | file_names(list of str): the name of the files in the folder |
| 255 | """ |
| 256 | images = [cv2.imread(directory + file) for file in sorted(os.listdir(directory))] |
| 257 | file_names = [file for file in sorted(os.listdir(directory))] |
| 258 | return images, file_names |
| 259 | def find_closer_point(players_coordinates, button_coordinates): |
| 260 | """ |
| 261 | find the distance between two points and find the minimum distance |
| 262 | Parameters: |
| 263 | players_coordinates(dict): key - player number, value - player coordinates |
| 264 | button_coordinates(tuple): Coordinates are presented in (x, y) format |
| 265 | Returns: |
| 266 | player_with_button(int): the number of the player who is closest |
| 267 | to the buttonthe number of the player who is closest to the button |
| 268 | """ |
| 269 | distance_dict = {} |
| 270 | for player, player_coordinates in players_coordinates.items(): |
| 271 | distance = sqrt((player_coordinates[0] - button_coordinates[0]) ** 2 |
| 272 | + (player_coordinates[1] - button_coordinates[1]) ** 2) |
| 273 | distance_dict[player] = distance |
| 274 | player_with_button = min(distance_dict, key=distance_dict.get) |
| 275 | return player_with_button |
| 276 | def remove_cards(hero_cards, table_cards): |
| 277 | """ |
| 278 | Parameters: |
| 279 | hero_cards(list of str): cards that belong to the hero |
| 280 | table_cards(list of str): cards that are on the table |
| 281 | Returns: |
| 282 | all_cards(list of str): all other cards, not including cards that are on the table and hero cards |
| 283 | for card in hero_cards+table_cards: |
| 284 | all_cards.remove(card) |
| 285 | return all_cards |
| 286 | def set_window_size(): |
| 287 | """ |
| 288 | set the application window in the right place and with the right size |
| 289 | """ |
| 290 | sleep(3) |
| 291 | cmd = 'wmctrl -r :ACTIVE: -e 0,0,0,1100,900' |
| 292 | os.system(cmd) |
| 293 | def data_concatenate(hero_hand, table_cards, total_pot, equity, players_info): |
| 294 | """ |
| 295 | information from all lists, dictionaries are added to one common line |
| 296 | """ |
| 297 | text_players_info = '' |
| 298 | for key, value in players_info.items(): |
| 299 | value = 'no bet found' if value == '' else value |
| 300 | text_players_info += str(key) + ':' + str(value) + ' ' |
| 301 | table_cards = ['no cards on the table'] if table_cards == [] else table_cards |
| 302 | text = 'Hero hand: ' + ' '.join(hero_hand) + ' ' + 'Board: ' + ' '.join(table_cards) + ' ' + |
| 303 | 'Pot: ' + total_pot + ' ' + 'Equity: ' + str(equity) + '%' + ' ' + \ |
| 304 | '------------------------------' + ' ' + text_players_info |
| 305 | return text |
| 306 | import cv2 |
| 307 | import numpy as np |
| 308 | from scripts.table_recognition import PokerTableRecognizer |
| 309 | from scripts.utils import sort_bboxes, thresholding, card_separator, table_part_recognition, \ |
| 310 | convert_contours_to_bboxes, find_by_template, find_closer_point, read_config_file |
| 311 | class PokerStarsTableRecognizer(PokerTableRecognizer): |
| 312 | def __init__(self, img, cfg): |
| 313 | """ |
| 314 | Parameters: |
| 315 | img(numpy.ndarray): image of the whole table |
| 316 | cfg (dict): config file |
| 317 | """ |
| 318 | self.img = img |
| 319 | self.cfg = cfg |
| 320 | def detect_hero_step(self): |
| 321 | """ |
| 322 | Based on the area under hero's cards, |
| 323 | we determine whether hero should make a move |
| 324 | Returns: |
| 325 | Boolean Value(True or False): True, if hero step now |
| 326 | """ |
| 327 | res_img = self.img[self.cfg['hero_step_define']['y_0']:self.cfg['hero_step_define']['y_1'], |
| 328 | self.cfg['hero_step_define']['x_0']:self.cfg['hero_step_define']['x_1']] |
| 329 | hsv_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2HSV_FULL) |
| 330 | mask = cv2.inRange(hsv_img, np.array(self.cfg['hero_step_define']['lower_gray_color']), |
| 331 | np.array(self.cfg['hero_step_define']['upper_gray_color'])) |
| 332 | count_of_white_pixels = cv2.countNonZero(mask) |
| 333 | return True if count_of_white_pixels > self.cfg['hero_step_define']['min_white_pixels'] else False |
| 334 | def detect_cards(self, separators, sort_bboxes_method, cards_coordinates, path_to_numbers, path_to_suits): |
| 335 | """ |
| 336 | Parameters: |
| 337 | separators(list of int): contains values where the card ends |
| 338 | sort_bboxes_method(str): defines how we will sort the contours. |
| 339 | It can be left-to-right, bottom-to-top, top-to-bottom |
| 340 | cards_coordinates(str): path to cards coordinates |
| 341 | path_to_numbers(str): path where located numbers (J, K etc.) |
| 342 | path_to_suits(str) : path where located suits |
| 343 | Returns: |
| 344 | cards_name(list of str): name of the cards |
| 345 | """ |
| 346 | cards_name = [] |
| 347 | img = self.img[self.cfg[cards_coordinates]['y_0']:self.cfg[cards_coordinates]['y_1'], |
| 348 | self.cfg[cards_coordinates]['x_0']:self.cfg[cards_coordinates]['x_1']] |
| 349 | binary_img = thresholding(img, 200, 255) |
| 350 | contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| 351 | bounding_boxes = convert_contours_to_bboxes(contours, 10, 2) |
| 352 | bounding_boxes = sort_bboxes(bounding_boxes, method=sort_bboxes_method) |
| 353 | cards_bboxes_dct = card_separator(bounding_boxes, separators) |
| 354 | for _, cards_bboxes in cards_bboxes_dct.items(): |
| 355 | if len(cards_bboxes) == 3: |
| 356 | cards_bboxes = [cards_bboxes[0]] |
| 357 | elif len(cards_bboxes) == 0: |
| 358 | return [] |
| 359 | elif len(cards_bboxes) > 3: |
| 360 | raise ValueError("The number of bounding boxes should not be more than 3!") |
| 361 | card_name = '' |
| 362 | for key, bbox in enumerate(cards_bboxes): |
| 363 | color_of_img, directory = (cv2.IMREAD_COLOR, self.cfg['paths'][path_to_suits]) if key == 0 \ |
| 364 | else (cv2.IMREAD_GRAYSCALE, self.cfg['paths'][path_to_numbers]) |
| 365 | res_img = img[bbox[1]:bbox[3], bbox[0]:bbox[2]] |
| 366 | card_part = table_part_recognition(res_img, directory, color_of_img) |
| 367 | card_name = card_part + 'T' if len(cards_bboxes) == 1 else card_name + card_part |
| 368 | cards_name.append(card_name[::-1]) |
| 369 | return cards_name |
| 370 | def detect_hero_cards(self): |
| 371 | """ |
| 372 | Returns: |
| 373 | cards_name(list of str): name of the hero's cards |
| 374 | """ |
| 375 | separators = [self.cfg['hero_cards']['separator_1'], self.cfg['hero_cards']['separator_2']] |
| 376 | sort_bboxes_method = 'bottom-to-top' |
| 377 | cards_coordinates = 'hero_cards' |
| 378 | path_to_numbers = 'hero_cards_numbers' |
| 379 | path_to_suits = 'hero_cards_suits' |
| 380 | cards_name = self.detect_cards(separators, sort_bboxes_method, cards_coordinates, |
| 381 | path_to_numbers, path_to_suits) |
| 382 | return cards_name |
| 383 | def detect_table_cards(self): |
| 384 | """ |
| 385 | Returns: |
| 386 | cards_name(list of str): name of the cards on the table |
| 387 | """ |
| 388 | separators = [self.cfg['table_cards']['separator_1'], self.cfg['table_cards']['separator_2'], |
| 389 | self.cfg['table_cards']['separator_3'], self.cfg['table_cards']['separator_4'], |
| 390 | self.cfg['table_cards']['separator_5']] |
| 391 | sort_bboxes_method = 'top-to-bottom' |
| 392 | cards_coordinates = 'table_cards' |
| 393 | path_to_numbers = 'table_cards_numbers' |
| 394 | path_to_suits = 'table_cards_suits' |
| 395 | cards_name = self.detect_cards(separators, sort_bboxes_method, cards_coordinates, |
| 396 | path_to_numbers, path_to_suits) |
| 397 | return cards_name |
| 398 | def find_total_pot(self): |
| 399 | """ |
| 400 | Returns: |
| 401 | number(str): number with total pot |
| 402 | """ |
| 403 | img = self.img[self.cfg['pot']['y_0']:self.cfg['pot']['y_1'], |
| 404 | self.cfg['pot']['x_0']:self.cfg['pot']['x_1']] |
| 405 | _, max_loc = find_by_template(img, self.cfg['paths']['pot_image']) |
| 406 | bet_img = img[max_loc[1] - 3:max_loc[1] + self.cfg['pot']['height'], |
| 407 | max_loc[0] + self.cfg['pot']['pot_template_width']: |
| 408 | max_loc[0] + self.cfg['pot']['pot_template_width'] + self.cfg['pot']['width']] |
| 409 | binary_img = thresholding(bet_img, 105, 255) |
| 410 | contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| 411 | bounding_boxes = convert_contours_to_bboxes(contours, 3, 1) |
| 412 | bounding_boxes = sort_bboxes(bounding_boxes, method='left-to-right') |
| 413 | number = '' |
| 414 | for bbox in bounding_boxes: |
| 415 | number_img = bet_img[bbox[1]:bbox[3], bbox[0]:bbox[2]] |
| 416 | symbol = table_part_recognition(number_img, self.cfg['paths']['pot_numbers'], cv2.IMREAD_GRAYSCALE) |
| 417 | number += symbol |
| 418 | return number |
| 419 | def get_dealer_button_position(self): |
| 420 | """ |
| 421 | determine who is closer to the dealer button |
| 422 | Returns: |
| 423 | player_info(dict): here is information about all players as it becomes available |
| 424 | """ |
| 425 | player_info = {key: value for key in range(1, 7) for value in ['']} |
| 426 | players_coordinates = self.cfg['player_center_coordinates'] |
| 427 | _, button_coordinates = find_by_template(self.img, self.cfg['paths']['dealer_button']) |
| 428 | player_with_button = find_closer_point(players_coordinates, button_coordinates) |
| 429 | player_info[player_with_button] = 'dealer_button' |
| 430 | return player_info |
| 431 | def get_missing_players(self, players_info, path_to_template_img, flag): |
| 432 | """ |
| 433 | find players who are currently absent for various reasons |
| 434 | Parameters: |
| 435 | players_info(dict): key - player number, value - '' - if the player is in the game; |
| 436 | '-' - if a player's seat is available; '-so-' - if the player is absent |
| 437 | path_to_template_img(str): the path to the images where the benchmark images are located |
| 438 | flag(str): It can be - and -so- |
| 439 | Returns: |
| 440 | players_info(dict): info about players |
| 441 | """ |
| 442 | players_coordinates = self.cfg['players_coordinates'] |
| 443 | players_for_checking = [key for key, value in players_info.items() if value == ''] |
| 444 | for player, bbox in players_coordinates.items(): |
| 445 | if player != 1 and player in players_for_checking: |
| 446 | player_img = self.img[bbox[1]:bbox[3], bbox[0]:bbox[2]] |
| 447 | max_val, _ = find_by_template(player_img, self.cfg['paths'][path_to_template_img]) |
| 448 | if max_val > 0.8: |
| 449 | players_info[player] = flag |
| 450 | return players_info |
| 451 | def get_empty_seats(self, players_info): |
| 452 | """ |
| 453 | find players whose places are currently vacant |
| 454 | """ |
| 455 | path_to_template_img = 'empty_seat' |
| 456 | flag = '-' |
| 457 | players_info = self.get_missing_players(players_info, path_to_template_img, flag) |
| 458 | return players_info |
| 459 | def get_so_players(self, players_info): |
| 460 | """ |
| 461 | find players who are not currently in the game |
| 462 | """ |
| 463 | path_to_template_img = 'sitting_out' |
| 464 | flag = '-so-' |
| 465 | players_info = self.get_missing_players(players_info, path_to_template_img, flag) |
| 466 | return players_info |
| 467 | def assign_positions(self, players_info): |
| 468 | """ |
| 469 | assign each player one of six positions if the player in the game |
| 470 | Parameters: |
| 471 | players_info(dict): info about players in {1:'', 2: 'dealer_button',3:'-so-' etc. } format |
| 472 | Returns: |
| 473 | players_info(dict): info about players in {1: 'BB', 2: 'SB', 3: '-so-' etc. } format |
| 474 | """ |
| 475 | busy_seats = [k for k, v in players_info.items() if v != '-' and v != '-so-'] |
| 476 | exist_positions = ['BTN', 'SB', 'BB', 'UTG', 'MP', 'CO'] |
| 477 | del exist_positions[3:3 + (6 - len(busy_seats))] |
| 478 | player_with_button = [k for k, v in players_info.items() if v == 'dealer_button'][0] |
| 479 | for index, player_number in enumerate( |
| 480 | busy_seats[busy_seats.index(player_with_button):] + busy_seats[:busy_seats.index(player_with_button)]): |
| 481 | if len(busy_seats) == 2: |
| 482 | position = 'SB' if index == 0 else 'BB' |
| 483 | players_info[player_number] = position |
| 484 | else: |
| 485 | players_info[player_number] = exist_positions[index] |
| 486 | return players_info |
| 487 | def find_players_bet(self, players_info): |
| 488 | """ |
| 489 | Parameters: |
| 490 | players_info(dict): info about players in {1:'BTN', 2:'SB', 3:'BB' etc. } format |
| 491 | Returns: |
| 492 | updated_players_info(dict): info about players in {'Hero':'BTN', 'SB':'', 'BB':'50' etc. } format |
| 493 | """ |
| 494 | players_bet_location = self.cfg['players_bet'] |
| 495 | updated_players_info = {'Hero': players_info[1]} |
| 496 | for i, location_coordinates in players_bet_location.items(): |
| 497 | if players_info[i] not in ('-so-', '-'): |
| 498 | bet_img = self.img[location_coordinates[1]:location_coordinates[3], |
| 499 | location_coordinates[0]:location_coordinates[2]] |
| 500 | binary_img = thresholding(bet_img, 105, 255) |
| 501 | contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| 502 | bounding_boxes = convert_contours_to_bboxes(contours, 3, 1) |
| 503 | bounding_boxes = sort_bboxes(bounding_boxes, method='left-to-right') |
| 504 | number = '' |
| 505 | for bbox in bounding_boxes: |
| 506 | number_img = bet_img[bbox[1]:bbox[3], bbox[0]:bbox[2]] |
| 507 | symbol = table_part_recognition(number_img, self.cfg['paths']['pot_numbers'], cv2.IMREAD_GRAYSCALE) |
| 508 | number += symbol |
| 509 | updated_players_info[players_info[i]] = number |
| 510 | else: |
| 511 | updated_players_info[i] = players_info[i] |
| 512 | return updated_players_info |
| 513 | import sys |
| 514 | sys.path.append('./redis') |
| 515 | sys.path.append('./bots') |
| 516 | sys.path.append('./main_functions') |
| 517 | sys.path.append('./PyPokerEngine') |
| 518 | import os |
| 519 | import time |
| 520 | import pickle |
| 521 | import argparse |
| 522 | from redis import Redis |
| 523 | from rq import Queue |
| 524 | from operator import add |
| 525 | from u_generate import gen_rand_bots, gen_decks |
| 526 | from u_training_games import run_generation_games |
| 527 | from u_neuroevolution import select_next_gen_bots, get_best_ANE_earnings |
| 528 | from u_formatting import prep_gen_dirs, get_gen_flat_params |
| 529 | if __name__ == '__main__': |
| 530 | """ #### PARSE ARGUMENTS, AND PROCESS #### """ |
| 531 | parser = argparse.ArgumentParser(description='') |
| 532 | parser.add_argument('--simul_id', default = -1, type=int, help='Id of the simulation. Should be defined to avoid overwriting past simulations.') |
| 533 | parser.add_argument('--network', default='6max_full', type=str, help='Neural network architecture to use. [hu_first, hu_second, 6max_single, 6max_full]') |
| 534 | parser.add_argument('--redis_host', default='local', type=str, help='Address of redis host. [local, ec2, *]') |
| 535 | parser.add_argument('--train_env', default='default', type=str, help='Environment used in training (table size, game type and opponents). Default will select according to the neural network [default, hu_cash_mixed, 6max_sng_ruler, 6max_sng_mixed]') |
| 536 | parser.add_argument('--ga_ini_gen', default=0, type=int, help='The generation at which to start the genetic algorithm. Used to resume an interrupted simulation.') |
| 537 | parser.add_argument('--ga_nb_gens', default=250, type=int, help='Number of generations of the genetic algorithm.') |
| 538 | parser.add_argument('--ga_popsize', default=70, type=int, help='Population size of the genetic algorithm.') |
| 539 | parser.add_argument('--norm_fitfunc', default=True, type=bool, help='Wether to use normalization scheme in the genetic algorithm\'s fitness function.') |
| 540 | parser.add_argument('--worker_timeout', default=800, type=int, help='Time in seconds before a job taken by a worker and not returned is considered to have timed out.') |
| 541 | parser.add_argument('--max_hands', default=300, type=int, help='Maximum number of hands played in a tournament. If attained, the agent is considered to have lost.') |
| 542 | parser.add_argument('--small_blind', default=10, type=int, help='Initial small blind amount. If tournament, the blind structure will overrule.') |
| 543 | parser.add_argument('--ini_stack', default=1500, type=int, help='Initial stack of the players.') |
| 544 | parser.add_argument('--nb_opps', default=4, type=int, help= 'Number of different tables against which to train') |
| 545 | parser.add_argument('--log_dir', default='../data/training_data', type=str, help='The path of the file where the simulation data will be stored.') |
| 546 | parser.add_argument('--verbose', default=True, type= bool, help = 'Whether to print detailled run time information.') |
| 547 | args = parser.parse_args() |
| 548 | if args.redis_host == 'local': |
| 549 | REDIS_HOST = '127.0.0.1' |
| 550 | elif args.redis_host == 'ec2': |
| 551 | REDIS_HOST = '172.31.42.99' |
| 552 | else: |
| 553 | REDIS_HOST = args.redis_host |
| 554 | my_network = args.network |
| 555 | my_normalize = args.norm_fitfunc |
| 556 | my_timeout = args.worker_timeout |
| 557 | simul_id = args.simul_id |
| 558 | ga_popsize = args.ga_popsize |
| 559 | nb_generations = args.ga_nb_gens |
| 560 | ini_gen = args.ga_ini_gen |
| 561 | nb_hands = args.max_hands |
| 562 | sb_amount = args.small_blind |
| 563 | ini_stack = args.ini_stack |
| 564 | log_dir = args.log_dir |
| 565 | train_env = args.train_env |
| 566 | verbose = args.verbose |
| 567 | nb_opps = args.nb_opps #TODO, move to simul arch def |
| 568 | my_nb_games = nb_opps*4 #TODO, move to simul arch def |
| 569 | if my_network in ['hu_first','hu_second']: #TODO handle differently. |
| 570 | my_nb_games=1 |
| 571 | if train_env == 'default': |
| 572 | if my_network in ['hu_first','hu_second']: |
| 573 | train_env = 'hu_cash_mixed' |
| 574 | elif my_network == '6max_single': |
| 575 | train_env = '6max_sng_ruler' |
| 576 | elif my_network =='6max_full': |
| 577 | train_env = '6max_sng_mixed' |
| 578 | """ #### SETTING UP #### """ |
| 579 | # Start redis host and clear queue |
| 580 | redis = Redis(REDIS_HOST) |
| 581 | q = Queue(connection=redis, default_timeout=my_timeout) |
| 582 | for j in q.jobs: |
| 583 | j.cancel() |
| 584 | if ini_gen==0: |
| 585 | # if this is a new simulation (not resuming one) |
| 586 | # Generate the first generation of bots randomly |
| 587 | gen_dir = log_dir+'/simul_'+str(simul_id)+'/gen_'+str(ini_gen) |
| 588 | gen_rand_bots(gen_dir, network=my_network, ga_popsize = ga_popsize, overwrite=False) |
| 589 | # Write configuration details to text file |
| 590 | file = open(log_dir+'/simul_'+str(simul_id)+"/configuration_details.txt",'w') |
| 591 | file.write("## CONFIGURATION DETAILS ## ") |
| 592 | file.write(str(args)) |
| 593 | file.close() |
| 594 | """ #### STARTING TRAINING #### """ |
| 595 | if verbose: print('## Starting training session ##') |
| 596 | for gen_id in range(ini_gen, nb_generations): |
| 597 | if verbose: print(' Starting generation: ' + str(gen_id)) |
| 598 | #Define generation's directory. Create one except if already existing |
| 599 | gen_dir = log_dir+'/simul_'+str(simul_id)+'/gen_'+str(gen_id) |
| 600 | if not os.path.exists(gen_dir): |
| 601 | os.makedirs(gen_dir) |
| 602 | #Prepare all decks of the generation |
| 603 | cst_decks = gen_decks(gen_dir = gen_dir, overwrite=False, nb_hands=nb_hands, nb_games=my_nb_games) |
| 604 | """#### RUN THE GAMES ####""" |
| 605 | time_start_games = time.time() |
| 606 | all_earnings = run_generation_games(gen_dir = gen_dir, ga_popsize = ga_popsize, my_network = my_network, |
| 607 | my_timeout = my_timeout, train_env = train_env, cst_decks=cst_decks, |
| 608 | ini_stack=ini_stack, sb_amount = sb_amount, nb_hands = nb_hands, |
| 609 | q = q) |
| 610 | # Saving earnings |
| 611 | for i, earnings in enumerate(all_earnings): |
| 612 | with open(gen_dir+'/bots/'+str(i+1)+'/earnings.pkl', 'wb') as f: |
| 613 | pickle.dump(earnings, f) |
| 614 | if verbose: |
| 615 | ## Getting best earnings |
| 616 | best_earnings = get_best_ANE_earnings(all_earnings = all_earnings, BB=2*sb_amount, ga_popsize = ga_popsize, nb_opps=nb_opps,normalize=my_normalize) |
| 617 | print("Best agent score: {}".format(["%.2f" % earning for earning in best_earnings.values()])) |
| 618 | # Getting average earning of deepbots agents |
| 619 | avg_earnings=[0,]*len(all_earnings[0].values()) |
| 620 | for i in range(ga_popsize): |
| 621 | avg_earnings= list(map(add, avg_earnings, all_earnings[i].values())) |
| 622 | avg_earnings= [el/ga_popsize for el in avg_earnings] |
| 623 | print("Average agent's scores: {}".format(["%.2f" % earning for earning in avg_earnings])) |
| 624 | time_end_games = time.time() |
| 625 | if verbose: print("Running games took {:.0f} seconds.".format(time_end_games-time_start_games)) |
| 626 | time_start_evo = time.time() |
| 627 | gen_flat_params = get_gen_flat_params(dir_=gen_dir) |
| 628 | next_gen_dir = log_dir+'/simul_'+str(simul_id)+'/gen_'+str(gen_id+1) |
| 629 | prep_gen_dirs(dir_=next_gen_dir) |
| 630 | next_gen_bots_flat = select_next_gen_bots(log_dir=log_dir, simul_id=simul_id, gen_id=gen_id, all_earnings=all_earnings, BB=2*sb_amount, |
| 631 | ga_popsize=ga_popsize, gen_flat_params = gen_flat_params, nb_gens = nb_generations, |
| 632 | network=my_network, nb_opps=nb_opps, normalize=my_normalize, verbose = verbose) |
| 633 | for bot_id in range(1, ga_popsize+1): |
| 634 | if not os.path.exists(next_gen_dir+'/bots/'+str(bot_id)): |
| 635 | os.makedirs(next_gen_dir+'/bots/'+str(bot_id)) |
| 636 | deepbot_flat = next_gen_bots_flat[bot_id-1] |
| 637 | with open(next_gen_dir+'/bots/'+str(bot_id)+'/bot_'+str(bot_id)+'_flat.pkl', 'wb') as f: |
| 638 | pickle.dump(deepbot_flat, f) |
| 639 | time_end_evo = time.time() |
| 640 | if verbose: print("Evolution took {:.0f} seconds.".format(time_end_evo-time_start_evo)) |
| 641 | import sys |
| 642 | sys.path.append('./PyPokerEngine') |
| 643 | sys.path.append('./bots') |
| 644 | sys.path.append('./main_functions') |
| 645 | import pickle |
| 646 | import argparse |
| 647 | from pypokerengine.api.game import setup_config, start_poker |
| 648 | from u_formatting import get_full_dict |
| 649 | from bot_TestBot import TestBot |
| 650 | from bot_CallBot import CallBot |
| 651 | from bot_PStratBot import PStratBot |
| 652 | from bot_DeepBot import DeepBot |
| 653 | from bot_EquityBot import EquityBot |
| 654 | from bot_ManiacBot import ManiacBot |
| 655 | from bot_CandidBot import CandidBot |
| 656 | from bot_ConservativeBot import ConservativeBot |
| 657 | if __name__ == '__main__': |
| 658 | """ #### PARSE ARGUMENTS #### """ |
| 659 | parser = argparse.ArgumentParser(description='') |
| 660 | parser.add_argument('--agent_file', default = '../data/trained_agents_git/6max_single/gen_250/bots/1/bot_1_flat.pkl', type=str, help='Path to file of a trained agent (in flat format).') |
| 661 | parser.add_argument('--network', default = '6max_single', type=str, help='Neural network of the agent. [hu_first, hu_second, 6max_single, 6max_full]') |
| 662 | parser.add_argument('--table_ind', default = 0, type = int, help='Indice of the table of opponents to play against. For more details open this file') |
| 663 | parser.add_argument('--max_hands', default=300, type=int, help='Maximum number of hands played in a tournament. If attained, the agent is considered to have lost.') |
| 664 | args = parser.parse_args() |
| 665 | agent_file = args.agent_file |
| 666 | my_network = args.network |
| 667 | table_ind = args.table_ind |
| 668 | max_hands = args.max_hands |
| 669 | ##Possible opponent tables## |
| 670 | opp_tables = [[PStratBot, PStratBot, PStratBot, PStratBot, PStratBot], |
| 671 | [CallBot, CallBot, CallBot, ConservativeBot, PStratBot], |
| 672 | [ConservativeBot, ConservativeBot, ConservativeBot, CallBot, PStratBot], |
| 673 | [ManiacBot, ManiacBot, ManiacBot, ConservativeBot, PStratBot], |
| 674 | [PStratBot, PStratBot, PStratBot, CallBot, ConservativeBot]] |
| 675 | ref_full_dict = DeepBot(network=my_network).full_dict |
| 676 | """ #### PREPARE AND PLAY GAME #### """ |
| 677 | print('## Starting ##') |
| 678 | ## LOADING AGENT ## |
| 679 | with open(agent_file, 'rb') as f: |
| 680 | deepbot_flat = pickle.load(f) |
| 681 | deepbot_dict = get_full_dict(all_params = deepbot_flat, ref_full_dict = ref_full_dict) |
| 682 | deepbot = DeepBot(full_dict = deepbot_dict, network=my_network) |
| 683 | ## PREPARING GAME ## |
| 684 | config = setup_config(max_round=max_hands, initial_stack=1500, small_blind_amount=10) |
| 685 | nb_opps, plays_per_blind = 5, 90 |
| 686 | for ind in range(nb_opps): |
| 687 | config.register_player(name="p-"+str(ind+1), algorithm=opp_tables[table_ind][ind]()) |
| 688 | config.register_player(name="deepbot", algorithm=deepbot) |
| 689 | blind_structure={0*plays_per_blind:{'ante':0, 'small_blind':10},\ |
| 690 | 1*plays_per_blind:{'ante':0, 'small_blind':15},\ |
| 691 | 2*plays_per_blind:{'ante':0, 'small_blind':25},\ |
| 692 | 3*plays_per_blind:{'ante':0, 'small_blind':50},\ |
| 693 | 4*plays_per_blind:{'ante':0, 'small_blind':100},\ |
| 694 | 5*plays_per_blind:{'ante':25, 'small_blind':100},\ |
| 695 | 6*plays_per_blind:{'ante':25, 'small_blind':200},\ |
| 696 | 7*plays_per_blind:{'ante':50, 'small_blind':300},\ |
| 697 | 8*plays_per_blind:{'ante':50, 'small_blind':400},\ |
| 698 | 9*plays_per_blind:{'ante':75, 'small_blind':600},} |
| 699 | config.set_blind_structure(blind_structure) |
| 700 | game_result, last_two_players, deepbot_rank = start_poker(config, verbose=True, return_last_two = True, return_deepbot_rank = True) |
| 701 | earning =- 1 |
| 702 | deepbot_rank += 1 |
| 703 | earning=-1 |
| 704 | ini_hero_pos = 5 |
| 705 | if deepbot.round_count==max_hands: |
| 706 | print('Game could not finish in max number of hands') |
| 707 | earning = -1 |
| 708 | else: |
| 709 | if "deepbot" in last_two_players: |
| 710 | earning=1 |
| 711 | if game_result['players'][ini_hero_pos]['stack']>0: |
| 712 | earning=3 |
| 713 | print(" Finishing place: "+str(deepbot_rank)) |
| 714 | print("Tokens earned: "+str(earning)) |
Комментарии