diff options
Diffstat (limited to 'board.py')
-rw-r--r-- | board.py | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/board.py b/board.py new file mode 100644 index 0000000..d401afe --- /dev/null +++ b/board.py @@ -0,0 +1,662 @@ +# Notes: +# need to program game stats reporting +# then program the ML algorithm! +# should I program ultimate before story? Just to stretch the legs of the ML Algorithm? +# consider adding an excel doc in the project folder that presents the report. Won't work for machines without an excel reader. could have a text based report. + +from msvcrt import * +from sqlite3 import * +import time +from access_methods import * + +class Board: + def __init__(self, plr_X, plr_O, wdl_stats=None, size=1): + blank_file = [' ', ' ', ' '] + self.file_name = ['a', 'b', 'c'] + self.rank_name = ['1', '2', '3'] + self.diag_name = ['a1_c3', 'a3_c1'] + self.board = [blank_file[:], blank_file[:], blank_file[:]] + self.plr_X = plr_X + self.plr_O = plr_O + self.nav_sqr = 'a3' + self.subtext_msg = '' + self.dr_menu = False + self.dr_entries = ['Offer Draw', 'Resign'] + self.nav_entry = '' + self.draw_offer = False + self.do_entries = ['Reject', 'Accept'] + self.draw_accept = False + self.resign = False + self.wdl_stats = wdl_stats + self.size = size + self.set_wdl_stats() + self.start_log() + + @property + def __dict__(self): + d = {} + for fn in self.file_name: + fi = self.file_name.index(fn) + d[fn] = {} + for rn in self.rank_name: + ri = self.rank_name.index(rn) + d[fn][rn] = self.board[fi][ri] + return d + + def start_log(self): + con = connect('games.db') + cur = con.cursor() + res = cur.execute(""" + SELECT max(game_id) AS game_id + FROM game_log + """).fetchone() + if res == None: + game_id = 0 + else: + game_id = Nz(res[0]) + 1 + self.move_log = [] + self.game_log = { + 'game_id': game_id, + 'plr_X_name': self.plr_X.name, + 'plr_X_color_int': self.plr_X.color_int, + 'plr_O_name': self.plr_O.name, + 'plr_O_color_int': self.plr_O.color_int, + 'start_time': time.time(), + 'start_board': self.fen_board + } + + @property + def side_bar(self): + msg = f'\t{self.plr_X.text_color}{self.plr_X.name}\033[0m' + msg += f'\t{self.plr_O.text_color}{self.plr_O.name}\033[0m' + i = 0 + for log in self.move_log: + if i != log['move_num']: + msg += f"\n{str(log['move_num'])}." + i = log['move_num'] + if log['turn'] == 'X': + msg += f"\t{self.plr_X.text_color}{log['move']}\033[0m" + elif log['turn'] == 'O' and self.move_log[0]['turn'] == 'O' and log['move_num'] == self.move_log[0]['move_num']: + msg += f"\t\t{self.plr_O.text_color}{log['move']}\033[0m" + elif log['turn'] == 'O': + msg += f"\t{self.plr_O.text_color}{log['move']}\033[0m" + + return msg + + @property + def subtext(self): + if self.vic_chk: return f'{self.victor.text_color}{self.victor.name} Wins!\033[0m' + elif self.end_game_chk and not self.vic_chk: return 'Draw' + elif self.draw_offer: + st = 'Accept Draw Offer?\n\n' + for entry in self.do_entries: + if self.nav_entry == entry: st += self.plr_alt.indicate_color + st += f'\t {entry} \033[0m' + return st + elif self.dr_menu: + st = 'End Game Options:\n\n' + for entry in self.dr_entries: + if self.nav_entry == entry: st += self.plr_turn.indicate_color + st += f'\t {entry} \033[0m' + return st + else: + st = f'{self.plr_turn.text_color}{self.plr_turn.name} to move' + if self.subtext_msg: st += f'\n\n\t{self.subtext_msg}' + return st + + @property + def fen_board(self): + fen = '' + space_count = 0 + r = 0 + for rank in reversed(self.ranks): + r += 1 + for sqr in rank: + if sqr == ' ': + space_count += 1 + else: + if space_count != 0: + fen += str(space_count) + space_count = 0 + fen += sqr + if space_count != 0: + fen += str(space_count) + space_count = 0 + if not r == 3: + fen += '/' + return fen + + @fen_board.setter + def fen_board(self, fen): + str_board = '' + for s in fen.replace('/',''): + if s.isnumeric(): str_board += int(s) * ' ' + else: str_board += s + self.board = [ + [str_board[6], str_board[3], str_board[0]], + [str_board[7], str_board[4], str_board[1]], + [str_board[8], str_board[5], str_board[2]] + ] + + def sqr_val(self, sqr_nm): + f = self.file_name.index(sqr_nm[0]) + r = self.rank_name.index(sqr_nm[1]) + return self.board[f][r] + + def is_vic_sqr(self, sqr_nm): return sqr_nm in self.vic_sqrs + + def dsp_sqr(self, sqr_nm): + sqr_val = self.sqr_val(sqr_nm) + color = '' + if sqr_nm in self.vic_sqrs: + color = f'{self.victor.indicate_color}\033[30m' + elif self.nav_sqr == sqr_nm: color = f'{self.plr_turn.indicate_color}\033[30m' + elif sqr_val == 'X': color = f'{self.plr_X.text_color}' + elif sqr_val == 'O': color = f'{self.plr_O.text_color}' + return f'{color}{sqr_val}\033[40m\033[97m' + + #https://en.wikipedia.org/wiki/Code_page_437 + # full block: █ + # left block: ▌ + # rite block: ▐ + # vr box bar: │ + def dsp_intr_sqr(self, sqr_1, sqr_2): + if sqr_1 in self.vic_sqrs and sqr_2 in self.vic_sqrs: + return f'{self.victor.indicate_color}│\033[40m\033[97m' + elif sqr_1 in self.vic_sqrs: + return f'{self.victor.text_color}▌\033[40m\033[97m' + elif sqr_2 in self.vic_sqrs: + return f'{self.victor.text_color}▐\033[40m\033[97m' + elif self.nav_sqr == sqr_1 and sqr_1 != '': + return f'{self.plr_turn.text_color}▌\033[40m\033[97m' + elif self.nav_sqr == sqr_2 and sqr_2 != '': + return f'{self.plr_turn.text_color}▐\033[40m\033[97m' + elif sqr_1 == '' or sqr_2 == '': + return ' ' + else: + return '│' + + def dsp_size_2(self): + brd = [] + for rank in self.rank_name: + rank_str = '' + rank_str += self.dsp_intr_sqr('', 'a' + rank) + if rank == '2' or rank == '3': + rank_str += '\033[4m' + rank_str += self.dsp_sqr('a' + rank) + rank_str += self.dsp_intr_sqr('a' + rank, 'b' + rank) + rank_str += self.dsp_sqr('b' + rank) + rank_str += self.dsp_intr_sqr('b' + rank, 'c' + rank) + rank_str += self.dsp_sqr('c' + rank) + if rank == '2' or rank == '3': + rank_str += '\033[0m' + rank_str += self.dsp_intr_sqr('c' + rank, '') + brd.append(rank_str) + return brd + + @property + def all_sqr_vals(self): + return [ + self.board[0][0], + self.board[0][1], + self.board[0][2], + self.board[1][0], + self.board[1][1], + self.board[1][2], + self.board[2][0], + self.board[2][1], + self.board[2][2] + ] + + @property + def sqr_nms(self): + return [ + ['a1', 'a2', 'a3'], + ['b1', 'b2', 'b3'], + ['c1', 'c2', 'c3'] + ] + + @property + def all_sqr_nms(self): return ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3'] + + @property + def all_sqrs_dict(self): return dict(zip(self.all_sqr_nms, self.all_sqr_vals)) + + @property + def plr_turn(self): + if self.all_sqr_vals.count('X') <= self.all_sqr_vals.count('O'): return self.plr_X + elif self.all_sqr_vals.count('X') > self.all_sqr_vals.count('O'): return self.plr_O + + @property + def plr_alt(self): + if self.all_sqr_vals.count('X') <= self.all_sqr_vals.count('O'): return self.plr_O + elif self.all_sqr_vals.count('X') > self.all_sqr_vals.count('O'): return self.plr_X + + @property + def move_num(self): return self.all_sqr_vals.count('O') + 1 + + @property + def turn_num(self): return 10 - self.all_sqr_vals.count(' ') + + @property + def val_a1(self): return self.board[0][0] + @property + def val_a2(self): return self.board[0][1] + @property + def val_a3(self): return self.board[0][2] + @property + def val_b1(self): return self.board[1][0] + @property + def val_b2(self): return self.board[1][1] + @property + def val_b3(self): return self.board[1][2] + @property + def val_c1(self): return self.board[2][0] + @property + def val_c2(self): return self.board[2][1] + @property + def val_c3(self): return self.board[2][2] + + @property + def dsp_a1(self): return self.dsp_sqr('a1') + @property + def dsp_a2(self): return self.dsp_sqr('a2') + @property + def dsp_a3(self): return self.dsp_sqr('a3') + @property + def dsp_b1(self): return self.dsp_sqr('b1') + @property + def dsp_b2(self): return self.dsp_sqr('b2') + @property + def dsp_b3(self): return self.dsp_sqr('b3') + @property + def dsp_c1(self): return self.dsp_sqr('c1') + @property + def dsp_c2(self): return self.dsp_sqr('c2') + @property + def dsp_c3(self): return self.dsp_sqr('c3') + + def __str__(self): + if self.size == 1: + s = f'''\033[40m\033[97m + 3 \033[4m{self.dsp_a3}│{self.dsp_b3}│{self.dsp_c3}\033[0m + 2 \033[4m{self.dsp_a2}│{self.dsp_b2}│{self.dsp_c2}\033[0m + 1 {self.dsp_a1}│{self.dsp_b1}│{self.dsp_c1}\033[0m + a b c''' + elif self.size == 2: + b = self.dsp_size_2() + + s = f'''\033[40m\033[97m + 3 {b[2]}\033[0m + 2 {b[1]}\033[0m + 1 {b[0]}\033[0m + a b c''' + + return s + + def display(self): + print('\033[0m', '\033[?25l', '\033[2J', '\033[0;0f', sep='', end='') + self.print_side_bar() + print(f'{self.str_wdl_stats}{str(self)}\n\n\t{self.subtext}') + self.subtext_msg = '' + + def set_wdl_stats(self): + msg = '' + if self.wdl_stats != None: + con = connect('games.db') + qry_wdl_stats = open('wdl_stats.sql','r').read() + rst = OpenRecordset(con, qry_wdl_stats, self.wdl_stats) + rcd = rst[0] + for fld in rcd: + if rcd[fld] == None: rcd[fld] = 0 + con = None + msg = f"\t{rcd['plr_1_text_color']}{rcd['plr_1_name']}\033[0m" + msg += f"\tDraw" + msg += f"\t{rcd['plr_2_text_color']}{rcd['plr_2_name']}\033[0m" + msg += f"\nX vs. O" + msg += f"\t{rcd['plr_1_text_color']}{rcd['plr_1_X_wins']}\033[0m" + msg += f"\t{rcd['game_X_O_draws']}" + msg += f"\t{rcd['plr_2_text_color']}{rcd['plr_2_O_wins']}\033[0m" + msg += f"\nO vs. X" + msg += f"\t{rcd['plr_1_text_color']}{rcd['plr_1_O_wins']}\033[0m" + msg += f"\t{rcd['game_O_X_draws']}" + msg += f"\t{rcd['plr_2_text_color']}{rcd['plr_2_X_wins']}\033[0m" + msg += f"\nTotal" + msg += f"\t{rcd['plr_1_text_color']}{rcd['plr_1_X_wins']+rcd['plr_1_O_wins']}\033[0m" + msg += f"\t{rcd['game_X_O_draws']+rcd['game_O_X_draws']}" + msg += f"\t{rcd['plr_2_text_color']}{rcd['plr_2_O_wins']+rcd['plr_2_X_wins']}\033[0m" + msg += f"\n" + self.str_wdl_stats = msg + + def print_side_bar(self): + if self.side_bar: + lines = self.side_bar.split('\n') + l = 1 + for line in lines: + print(f'\033[{l};35f', line, sep='', end='') + l+=1 + print('\033[0;0f', end='') + + @property + def row_a(self): return self.board[0] + @property + def row_b(self): return self.board[1] + @property + def row_c(self): return self.board[2] + @property + def row_1(self): return [self.board[0][0], self.board[1][0], self.board[2][0]] + @property + def row_2(self): return [self.board[0][1], self.board[1][1], self.board[2][1]] + @property + def row_3(self): return [self.board[0][2], self.board[1][2], self.board[2][2]] + @property + def row_a1_c3(self): return [self.board[0][0], self.board[1][1], self.board[2][2]] + @property + def row_a3_c1(self): return [self.board[0][2], self.board[1][1], self.board[2][0]] + + @property + def row_a_sqr_nms(self): return self.sqr_nms[0] + @property + def row_b_sqr_nms(self): return self.sqr_nms[1] + @property + def row_c_sqr_nms(self): return self.sqr_nms[2] + @property + def row_1_sqr_nms(self): return [self.sqr_nms[0][0], self.sqr_nms[1][0], self.sqr_nms[2][0]] + @property + def row_2_sqr_nms(self): return [self.sqr_nms[0][1], self.sqr_nms[1][1], self.sqr_nms[2][1]] + @property + def row_3_sqr_nms(self): return [self.sqr_nms[0][2], self.sqr_nms[1][2], self.sqr_nms[2][2]] + @property + def row_a1_c3_sqr_nms(self): return [self.sqr_nms[0][0], self.sqr_nms[1][1], self.sqr_nms[2][2]] + @property + def row_a3_c1_sqr_nms(self): return [self.sqr_nms[0][2], self.sqr_nms[1][1], self.sqr_nms[2][0]] + + @property + def row_a_dict(self): return dict(zip(self.row_a_sqr_nms, self.row_a)) + @property + def row_b_dict(self): return dict(zip(self.row_b_sqr_nms, self.row_b)) + @property + def row_c_dict(self): return dict(zip(self.row_c_sqr_nms, self.row_c)) + @property + def row_1_dict(self): return dict(zip(self.row_1_sqr_nms, self.row_1)) + @property + def row_2_dict(self): return dict(zip(self.row_2_sqr_nms, self.row_2)) + @property + def row_3_dict(self): return dict(zip(self.row_3_sqr_nms, self.row_3)) + @property + def row_a1_c3_dict(self): return dict(zip(self.row_a1_c3_sqr_nms, self.row_a1_c3)) + @property + def row_a3_c1_dict(self): return dict(zip(self.row_a1_c3_sqr_nms, self.row_a1_c3)) + + @property + def files(self): return self.board + + @property + def ranks(self): return [self.row_1, self.row_2, self.row_3] + + @property + def ranks_dict(self): + v = [self.row_1_dict, self.row_2_dict, self.row_3_dict] + return dict(zip(self.rank_name, v)) + + @property + def diagonals(self): return [self.row_a1_c3, self.row_a3_c1] + + @property + def rows(self): + return [ + self.row_a, + self.row_b, + self.row_c, + self.row_1, + self.row_2, + self.row_3, + self.row_a1_c3, + self.row_a3_c1 + ] + + @property + def row_dicts(self): + return [ + self.row_a_dict, + self.row_b_dict, + self.row_c_dict, + self.row_1_dict, + self.row_2_dict, + self.row_3_dict, + self.row_a1_c3_dict, + self.row_a3_c1_dict + ] + + @property + def row_names(self): return self.file_name + self.rank_name + self.diag_name + + @property + def row_dict(self): return dict(zip(self.row_names, self.rows)) + + @property + def row_dict_dicts(self): return dict(zip(self.row_names, self.row_dicts)) + + @property + def vic_chk(self): + for rng in self.rows: + if rng.count(rng[0]) == 3 and rng[0] != ' ': return True + return self.resign + + @property + def three_in_a_row_chk(self): + for rng in self.rows: + if rng.count(rng[0]) == 3 and rng[0] != ' ': return True + return False + + @property + def vic_sqrs(self): + sqrs = [] + for i in range(3): + if self.files[i].count(self.files[i][0]) == 3 and self.files[i][0] != ' ': + for rnk_nm in self.rank_name: sqrs.append(self.file_name[i] + rnk_nm) + if self.ranks[i].count(self.ranks[i][0]) == 3 and self.ranks[i][0] != ' ': + for fil_nm in self.file_name: sqrs.append(fil_nm + self.rank_name[i]) + if self.row_a1_c3.count(self.row_a1_c3[0]) == 3 and self.row_a1_c3[0] != ' ': + for i in range(3): sqrs.append(self.file_name[i] + self.rank_name[i]) + if self.row_a3_c1.count(self.row_a3_c1[0]) == 3 and self.row_a3_c1[0] != ' ': + for i in range(3): sqrs.append(self.file_name[i] + self.rank_name[2-i]) + return sqrs + + @property + def end_game_chk(self): + return self.all_sqr_vals.count(' ') == 0 or self.vic_chk or self.draw_accept + + @property + def victor(self): + if self.three_in_a_row_chk: + for rng in self.rows: + if rng.count(rng[0]) == 3: + if rng[0] == 'X': return self.plr_X + elif rng[0] == 'O': return self.plr_O + elif self.resign: return self.plr_alt + return None + + @property + def mark(self): + if self.plr_turn == self.plr_X: return 'X' + elif self.plr_turn == self.plr_O: return 'O' + + def log_move(self): + i = len(self.move_log) + self.move_log.append({'game_id': self.game_log['game_id']}) + self.move_log[i]['board'] = self.fen_board + self.move_log[i]['turn'] = self.mark + self.move_log[i]['move_num'] = self.move_num + self.move_log[i]['turn_num'] = self.turn_num + self.move_log[i]['move'] = self.nav_sqr + self.move_log[i]['timestamp'] = time.time() + + def mark_sqr(self, sqr_nm=None): + if sqr_nm != None: self.nav_sqr = sqr_nm + f = self.file_name.index(self.nav_sqr[0]) + r = self.rank_name.index(self.nav_sqr[1]) + if self.board[f][r] == ' ': + self.log_move() + self.board[f][r] = self.mark + return True + else: + self.subtext_msg = 'Illegal Move' + return False + + def draw_or_resign(self, ks): + chk = False + if not self.nav_entry in self.dr_entries: self.nav_entry = self.dr_entries[0] + if ks == [b'\r']: + if self.nav_entry == self.dr_entries[0]: + self.draw_offer = True + self.nav_entry = self.do_entries[0] + chk = self.kb_nav(self.accept_draw) + self.draw_offer = False + self.nav_entry = self.dr_entries[0] + elif self.nav_entry == self.dr_entries[1]: + self.resign = True + chk = True + elif ks == [b'\x1b']: + chk = True + elif ks == [b'\t']: + self.nav_entry = self.dr_entries[self.dr_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'H'] or ks == [b'\xe0', b'H']: # Up + self.nav_entry = self.dr_entries[self.dr_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'P'] or ks == [b'\xe0', b'P']: # Down + self.nav_entry = self.dr_entries[self.dr_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'M'] or ks == [b'\xe0', b'M']: # Right + self.nav_entry = self.dr_entries[self.dr_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'K'] or ks == [b'\xe0', b'K']: # Left + self.nav_entry = self.dr_entries[self.dr_entries.index(self.nav_entry)-1] + return chk + + def accept_draw(self, ks): + chk = False + if not self.nav_entry in self.do_entries: self.nav_entry = self.do_entries[0] + if ks == [b'\r']: + if self.nav_entry == self.do_entries[0]: + self.draw_offer = False + self.nav_entry = self.dr_entries[0] + chk = True + elif self.nav_entry == self.do_entries[1]: + self.draw_accept = True + chk = True + elif ks == [b'\x1b']: + self.draw_offer = False + self.nav_entry = self.dr_entries[0] + chk = True + elif ks == [b'\t']: + self.nav_entry = self.do_entries[self.do_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'H'] or ks == [b'\xe0', b'H']: # Up + self.nav_entry = self.do_entries[self.do_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'P'] or ks == [b'\xe0', b'P']: # Down + self.nav_entry = self.do_entries[self.do_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'M'] or ks == [b'\xe0', b'M']: # Right + self.nav_entry = self.do_entries[self.do_entries.index(self.nav_entry)-1] + elif ks == [b'\x00', b'K'] or ks == [b'\xe0', b'K']: # Left + self.nav_entry = self.do_entries[self.do_entries.index(self.nav_entry)-1] + return chk + + def nav_board(self, ks): + chk = False + if self.nav_sqr == '': self.nav_sqr = 'a3' + if ks == [b'\r']: + chk = self.mark_sqr() + elif ks == [b'\x1b']: + self.dr_menu = True + self.nav_entry = self.dr_entries[0] + chk = self.kb_nav(self.draw_or_resign) + self.nav_entry = '' + self.dr_menu = False + elif ks == [b'\t']: + f = self.file_name.index(self.nav_sqr[0]) + r = self.rank_name.index(self.nav_sqr[1]) + if f == 2: + f = 0 + if r == 0: r = 2 + else: r -= 1 + else: f += 1 + self.nav_sqr = self.file_name[f] + self.rank_name[r] + elif ks == [b'\x00', b'H'] or ks == [b'\xe0', b'H']: # Up + pos = self.rank_name.index(self.nav_sqr[1]) + if pos == len(self.rank_name) - 1: pos = 0 + else: pos += 1 + self.nav_sqr = self.nav_sqr[0] + self.rank_name[pos] + elif ks == [b'\x00', b'P'] or ks == [b'\xe0', b'P']: # Down + pos = self.rank_name.index(self.nav_sqr[1]) + if pos == 0: pos = len(self.rank_name) - 1 + else: pos -= 1 + self.nav_sqr = self.nav_sqr[0] + self.rank_name[pos] + elif ks == [b'\x00', b'M'] or ks == [b'\xe0', b'M']: # Right + pos = self.file_name.index(self.nav_sqr[0]) + if pos == len(self.file_name) - 1: pos = 0 + else: pos += 1 + self.nav_sqr = self.file_name[pos] + self.nav_sqr[1] + elif ks == [b'\x00', b'K'] or ks == [b'\xe0', b'K']: # Left + pos = self.file_name.index(self.nav_sqr[0]) + if pos == 0: pos = len(self.file_name) - 1 + else: pos -= 1 + self.nav_sqr = self.file_name[pos] + self.nav_sqr[1] + return chk + + def kb_nav(self, meth): + chk = False + while not chk: + self.display() + ks = [getch()] + if kbhit(): ks.append(getch()) + chk = meth(ks) + return chk + + def log_game(self): + if len(self.move_log) > 0: + con = connect('games.db') + cur = con.cursor() + + app_game_log_sql = open('app_game_log.sql', 'r').read() + cur.execute(app_game_log_sql, self.game_log) + + app_move_log_sql = open('app_move_log.sql', 'r').read() + for log in self.move_log: + cur.execute(app_move_log_sql, log) + + con.commit() + con = None + cur = None + + def play(self): + while not self.end_game_chk: + if not self.plr_turn.is_machine: + if not self.kb_nav(self.nav_board): + break + elif self.plr_turn.is_machine: + self.display() + sqr_nm = self.plr_turn.move(self.fen_board) + self.mark_sqr(sqr_nm) + + self.nav_sqr = '' + self.game_log['vic_chk'] = self.vic_chk + if self.vic_chk: + self.game_log['victor_name'] = self.victor.name + self.game_log['victor_color_int'] = self.victor.color_int + else: + self.game_log['victor_name'] = '' + self.game_log['victor_color_int'] = 0 + self.game_log['three_in_a_row'] = self.three_in_a_row_chk + self.game_log['draw_accept'] = self.draw_accept + self.game_log['resign'] = self.resign + self.game_log['end_board'] = self.fen_board + self.game_log['turns'] = self.turn_num - 1 + self.game_log['end_time'] = time.time() + + self.log_game() + self.set_wdl_stats() + self.display() + if not self.plr_X.is_machine or not self.plr_O.is_machine: + input() + + + + + |