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)) |
Комментарии