diff options
author | TinWoodman92 <chrhodgden@gmail.com> | 2023-12-16 06:18:44 -0600 |
---|---|---|
committer | TinWoodman92 <chrhodgden@gmail.com> | 2023-12-16 06:18:44 -0600 |
commit | 158b36e51c8c2925d6c8af6e08380369d019378e (patch) | |
tree | f91fadecf85e982714927eb6ab451c9173e37de0 |
Initial commit
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | access_methods.py | 27 | ||||
-rw-r--r-- | alg_move.sql | 68 | ||||
-rw-r--r-- | analysis_board.py | 212 | ||||
-rw-r--r-- | app_game_log.sql | 36 | ||||
-rw-r--r-- | app_move_log.sql | 18 | ||||
-rw-r--r-- | board.py | 662 | ||||
-rw-r--r-- | bot.py | 156 | ||||
-rw-r--r-- | crt_db.py | 121 | ||||
-rw-r--r-- | edt_db.py | 13 | ||||
-rw-r--r-- | entry.py | 173 | ||||
-rw-r--r-- | entry_list.py | 70 | ||||
-rw-r--r-- | games.db | bin | 0 -> 20480 bytes | |||
-rw-r--r-- | load_nav.py | 227 | ||||
-rw-r--r-- | menu.py | 114 | ||||
-rw-r--r-- | moves_out_1.sql | 11 | ||||
-rw-r--r-- | moves_out_2.sql | 12 | ||||
-rw-r--r-- | play.py | 22 | ||||
-rw-r--r-- | player.py | 563 | ||||
-rw-r--r-- | readme.md | 11 | ||||
-rw-r--r-- | wdl_stats.sql | 59 |
21 files changed, 2580 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df5c3b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*notes* +*restore* +*snippets* +*archive* +*__pycache__* diff --git a/access_methods.py b/access_methods.py new file mode 100644 index 0000000..f315b82 --- /dev/null +++ b/access_methods.py @@ -0,0 +1,27 @@ +from sqlite3 import * + +def IIf(chk, true_case, false_case=None): + if chk: return true_case + else: return false_case + +def Nz(val, nul_case=0): + if val == None: return nul_case + else: return val + +# return a list of dictionaries from a db query +# need the connection. Is inherent in DAO.Database +# Read only +def OpenRecordset(con, qry_sql, param=None): + cur = con.cursor() + rst = [] + k = [] + + if param != None: res = cur.execute(qry_sql, param) + else: res = cur.execute(qry_sql) + + for desc in cur.description: k.append(desc[0]) + + for row in res.fetchall(): rst.append(dict(zip(k,row))) + + cur = None + return rst
\ No newline at end of file diff --git a/alg_move.sql b/alg_move.sql new file mode 100644 index 0000000..3ac8cad --- /dev/null +++ b/alg_move.sql @@ -0,0 +1,68 @@ +-- Does expression * NULL return NULL? +-- How about with other arithmetic operators? +-- If so, we can simplify the aggregation to eliminate CASE and/or nested CASE statements +SELECT + move_log.move, + count(move_log.move) AS move_count, + count(CASE + WHEN game_log.victor_name = :plr_name AND game_log.victor_color_int = :plr_color_int + THEN move_log.move ELSE NULL END) AS win_count, + count(CASE + WHEN game_log.vic_chk = 0 + THEN move_log.move ELSE NULL END) AS draw_count, + count(CASE + WHEN game_log.vic_chk = 1 AND (game_log.victor_name <> :plr_name OR game_log.victor_color_int <> :plr_color_int) + THEN move_log.move ELSE NULL END) AS loss_count, + sum(CASE + WHEN game_log.victor_name = :plr_name AND game_log.victor_color_int = :plr_color_int + THEN (1.0 / 1.0) + ELSE NULL END) AS sum_win_factor, + sum(CASE + WHEN game_log.vic_chk = 0 + THEN (0.0 / 1.0) + ELSE NULL END) AS sum_draw_factor, + sum(CASE + WHEN game_log.vic_chk = 1 AND (game_log.victor_name <> :plr_name OR game_log.victor_color_int <> :plr_color_int) + THEN (-1.0 / 1.0) + ELSE NULL END) AS sum_loss_factor, + avg(CASE + WHEN game_log.victor_name = :plr_name AND game_log.victor_color_int = :plr_color_int + THEN (1.0 / 1.0) + ELSE NULL END) AS avg_win_factor, + avg(CASE + WHEN game_log.vic_chk = 0 + THEN (0.0 / 1.0) + ELSE NULL END) AS avg_draw_factor, + avg(CASE + WHEN game_log.vic_chk = 1 AND (game_log.victor_name <> :plr_name OR game_log.victor_color_int <> :plr_color_int) + THEN (-1.0 / 1.0) + ELSE NULL END) AS avg_loss_factor, + sum(CASE + WHEN game_log.victor_name = :plr_name AND game_log.victor_color_int = :plr_color_int + THEN (1.0 / 1.0) + WHEN game_log.vic_chk = 0 + THEN (0.0 / 1.0) + WHEN game_log.vic_chk = 1 AND (game_log.victor_name <> :plr_name OR game_log.victor_color_int <> :plr_color_int) + THEN (-1.0 / 1.0) + ELSE NULL END) AS sum_net_factor, + avg(CASE + WHEN game_log.victor_name = :plr_name AND game_log.victor_color_int = :plr_color_int + THEN (1.0 / 1.0) + WHEN game_log.vic_chk = 0 + THEN (0.0 / 1.0) + WHEN game_log.vic_chk = 1 AND (game_log.victor_name <> :plr_name OR game_log.victor_color_int <> :plr_color_int) + THEN (-1.0 / 1.0) + ELSE NULL END) AS avg_net_factor +FROM + game_log INNER JOIN move_log + ON game_log.game_id = move_log.game_id +WHERE + move_log.board = :fen_board + AND game_log.plr_X_name = :plr_name + AND game_log.plr_X_color_int = :plr_color_int +GROUP BY + move_log.move +ORDER BY + (win_count - loss_count) DESC, + move_log.move +;
\ No newline at end of file diff --git a/analysis_board.py b/analysis_board.py new file mode 100644 index 0000000..044f20f --- /dev/null +++ b/analysis_board.py @@ -0,0 +1,212 @@ +class AnalysisBoard(): + def __init__(self, fen_board='3/3/3'): + self.file_name = ['a', 'b', 'c'] + self.rank_name = ['1', '2', '3'] + self.diag_name = ['a1_c3', 'a3_c1'] + self.fen_board = fen_board + + @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 + + @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]] + ] + + @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 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 sqr_nms(self): + return [ + ['a1', 'a2', 'a3'], + ['b1', 'b2', 'b3'], + ['c1', 'c2', 'c3'] + ] + + @property + def turn(self): + if self.all_sqr_vals.count('X') <= self.all_sqr_vals.count('O'): return 'X' + elif self.all_sqr_vals.count('X') > self.all_sqr_vals.count('O'): return 'O' + + @property + def alt_turn(self): + if self.all_sqr_vals.count('X') <= self.all_sqr_vals.count('O'): return 'O' + elif self.all_sqr_vals.count('X') > self.all_sqr_vals.count('O'): return 'X' + + @property + def legal_moves(self): + legal_moves = [] + for f in range(3): + for r in range(3): + if self.board[f][r] == ' ': legal_moves.append('abc'[f] + '123'[r]) + return legal_moves + + @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 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 sqr_rows(self): + sr = {} + for sqr_nm in self.all_sqr_nms: + rows = [sqr_nm[0], sqr_nm[1]] + if sqr_nm in self.row_a1_c3_sqr_nms: rows.append('a1_c3') + if sqr_nm in self.row_a3_c1_sqr_nms: rows.append('a3_c1') + sr[sqr_nm] = rows + return sr + + @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_a3_c1_sqr_nms, self.row_a3_c1)) + + @property + def files(self): return self.board + @property + def ranks(self):return [self.row_1, self.row_2, self.row_3] + @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)) + + 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] +
\ No newline at end of file diff --git a/app_game_log.sql b/app_game_log.sql new file mode 100644 index 0000000..35a33a3 --- /dev/null +++ b/app_game_log.sql @@ -0,0 +1,36 @@ +INSERT INTO game_log ( + game_id, + plr_X_name, + plr_X_color_int, + plr_O_name, + plr_O_color_int, + start_time, + start_board, + vic_chk, + victor_name, + victor_color_int, + three_in_a_row, + draw_accept, + resign, + end_board, + turns, + end_time +) +VALUES ( + :game_id, + :plr_X_name, + :plr_X_color_int, + :plr_O_name, + :plr_O_color_int, + :start_time, + :start_board, + :vic_chk, + :victor_name, + :victor_color_int, + :three_in_a_row, + :draw_accept, + :resign, + :end_board, + :turns, + :end_time +)
\ No newline at end of file diff --git a/app_move_log.sql b/app_move_log.sql new file mode 100644 index 0000000..bb5417c --- /dev/null +++ b/app_move_log.sql @@ -0,0 +1,18 @@ +INSERT INTO move_log ( + game_id, + board, + turn, + move_num, + turn_num, + move, + timestamp +) +VALUES ( + :game_id, + :board, + :turn, + :move_num, + :turn_num, + :move, + :timestamp +)
\ No newline at end of file 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() + + + + + @@ -0,0 +1,156 @@ +from menu import * +from entry_list import * +from entry import * +from analysis_board import * +from random import * +from time import time + +def rnd(i = 1): return randint(0,i) + +class Bot(): + def __init__(self, name, entry, plr_list=None): + self.color_int = 7 + self.is_machine = True + self.name = name + # bots go at the end of the list + self.plr_list = plr_list + self.alg_1 = self.no_alg + self.alg_2 = self.no_alg + self.alg_3 = self.no_alg + self.alg_4 = self.no_alg + self.alg_5 = self.no_alg + # initialize a menu and set it to self.menu + self.param_alg_1 = Param('1st Algorithm', self.alg_name_list, setr_meth=self.set_alg_1) + self.param_alg_2 = Param('2nd Algorithm', self.alg_name_list, setr_meth=self.set_alg_2) + self.param_alg_3 = Param('3rd Algorithm', self.alg_name_list, setr_meth=self.set_alg_3) + self.param_alg_4 = Param('4th Algorithm', self.alg_name_list, setr_meth=self.set_alg_4) + self.param_alg_5 = Param('5th Algorithm', self.alg_name_list, setr_meth=self.set_alg_5) + self.param_alg_1.item = self.alg_1.__name__ + self.param_alg_2.item = self.alg_2.__name__ + self.param_alg_3.item = self.alg_3.__name__ + self.param_alg_4.item = self.alg_4.__name__ + self.param_alg_5.item = self.alg_5.__name__ + self.entries = EntryList([self.param_alg_1, self.param_alg_2, self.param_alg_3, self.param_alg_4, self.param_alg_5]) + self.prompt = f"Edit {self.name}'s Settings" + self.menu = Menu(self.prompt, self.entries) + self.entry = entry + entry.text = name + entry.item = self.menu + self.board = AnalysisBoard() + + @property + def text_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[9{self.color_int}m' + + @property + def indicate_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[10{self.color_int}m\033[30m' + + def set_alg_1(self, alg_name): self.alg_1 = self.alg_list[self.alg_name_list.index(alg_name)] + def set_alg_2(self, alg_name): self.alg_2 = self.alg_list[self.alg_name_list.index(alg_name)] + def set_alg_3(self, alg_name): self.alg_3 = self.alg_list[self.alg_name_list.index(alg_name)] + def set_alg_4(self, alg_name): self.alg_4 = self.alg_list[self.alg_name_list.index(alg_name)] + def set_alg_5(self, alg_name): self.alg_5 = self.alg_list[self.alg_name_list.index(alg_name)] + + @property + def alg_list(self): + lst = [] + for att in dir(self): + if len(att) >= 5: + if att[-4:] == '_alg' or att[:5] == 'rand_': + lst.append(getattr(self, att)) + return lst + + @property + def alg_name_list(self): + lst = [] + for alg in self.alg_list: + lst.append(alg.__name__) + return lst + + def rand_fr(self): return choice('abc') + choice('123') + + def rand_sqr(self): return choice(['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) + + def rand_lgl(self): return choice(self.board.legal_moves) + + def no_alg(self): return '' + + def tiar_alg(self): + sqr_nm = '' + row_names = self.board.row_names + shuffle(row_names) + for row_name in row_names: + if self.board.row_dict[row_name].count(self.board.turn) == 2 and self.board.row_dict[row_name].count(' ') == 1: + pos = self.board.row_dict[row_name].index(' ') + sqr_nms = self.board.__getattribute__(f'row_{row_name}_sqr_nms') + sqr_nm = sqr_nms[pos] + + return sqr_nm + + def block_alg(self): + sqr_nm = '' + row_names = self.board.row_names + shuffle(row_names) + for row_name in row_names: + if self.board.row_dict[row_name].count(self.board.alt_turn) == 2 and self.board.row_dict[row_name].count(' ') == 1: + pos = self.board.row_dict[row_name].index(' ') + sqr_nms = self.board.__getattribute__(f'row_{row_name}_sqr_nms') + sqr_nm = sqr_nms[pos] + + return sqr_nm + + def build_alg(self): + sqr_nm = '' + row_names = self.board.row_names + shuffle(row_names) + for row_name in row_names: + if self.board.row_dict[row_name].count(self.board.turn) == 1 and self.board.row_dict[row_name].count(' ') == 2: + rng_ind = [0, 1, 2] + pos = self.board.row_dict[row_name].index(self.board.turn) + rng_ind.remove(pos) + sqr_nms = self.board.__getattribute__(f'row_{row_name}_sqr_nms') + sqr_nm = sqr_nms[rng_ind[rnd()]] + + return sqr_nm + + def fork_alg(self): + sqr_nm = '' + sn = self.board.all_sqr_nms + shuffle(sn) + for s in sn: + if self.board.all_sqrs_dict[s] == self.board.turn: + s_2 = '' + for s_0 in sn: + if s_0 != s and self.board.all_sqrs_dict[s_0] == self.board.turn: s_2 = s_0 + if s_2 != '': + rows_s_1 = self.board.sqr_rows[s] + rows_s_2 = self.board.sqr_rows[s_2] + for q in sn: + if self.board.all_sqrs_dict[q] == ' ': + row_q_1 = '' + row_q_2 = '' + for row_q in self.board.sqr_rows[q]: + if row_q in rows_s_1 and self.board.row_dict[row_q].count(' ') == 2 and self.board.row_dict[row_q].count(self.board.turn) == 1: + row_q_1 = row_q + if row_q in rows_s_2 and self.board.row_dict[row_q].count(' ') == 2 and self.board.row_dict[row_q].count(self.board.turn) == 1: + row_q_2 = row_q + pass + if row_q_1 != '' and row_q_2 != '': + sqr_nm = q + + return sqr_nm + + def move(self, fen_board): + sqr_nm = '' + self.board.fen_board = fen_board + if sqr_nm == '': sqr_nm = self.alg_1() + if sqr_nm == '': sqr_nm = self.alg_2() + if sqr_nm == '': sqr_nm = self.alg_3() + if sqr_nm == '': sqr_nm = self.alg_4() + if sqr_nm == '': sqr_nm = self.alg_5() + self.board.fen_board = '3/3/3' + return sqr_nm + diff --git a/crt_db.py b/crt_db.py new file mode 100644 index 0000000..d05a062 --- /dev/null +++ b/crt_db.py @@ -0,0 +1,121 @@ +from sqlite3 import * +import os + +db_name = 'games.db' + +if os.path.exists(os.path.join(os.getcwd(), 'games.db')): os.remove('games.db') + +con = connect('games.db') +cur = con.cursor() + +game_log_fld_types = { + 'game_id': 'Integer', + 'plr_X_name': 'Text', + 'plr_X_color_int': 'Integer', + 'plr_O_name': 'Text', + 'plr_O_color_int': 'Integer', + 'start_time': 'Real', + 'start_board': 'Text', + 'vic_chk': 'Integer', + 'victor_name': 'Text', + 'victor_color_int': 'Integer', + 'three_in_a_row': 'Integer', + 'draw_accept': 'Integer', + 'resign': 'Integer', + 'end_board': 'Text', + 'turns': 'Integer', + 'end_time': 'Real' +} + +crt_flds = '' +for k in game_log_fld_types: + crt_flds += f'{k} {game_log_fld_types[k]}, ' +crt_flds = crt_flds[:-2] + +qry_crt_game_log = f'CREATE TABLE game_log ({crt_flds})' +cur.execute(qry_crt_game_log) + +move_log_fld_types = { + 'game_id': 'Integer', + 'board': 'Text', + 'turn': 'Text', + 'move_num': 'Integer', + 'turn_num': 'Integer', + 'move': 'Text', + 'timestamp': 'Real' +} + +crt_flds = '' +for k in move_log_fld_types: + crt_flds += f'{k} {move_log_fld_types[k]}, ' +crt_flds = crt_flds[:-2] + +qry_crt_move_log = f'CREATE TABLE move_log ({crt_flds})' +cur.execute(qry_crt_move_log) + +players_fld_types = { + 'color_int': 'Integer', + 'name': 'Text', + 'is_machine': 'Integer' +} + +crt_flds = '' +for k in players_fld_types: + crt_flds += f'{k} {players_fld_types[k]}, ' +crt_flds = crt_flds[:-2] + +qry_crt_players = f'CREATE TABLE players ({crt_flds})' +cur.execute(qry_crt_players) + +plrs = [ + {'color_int': 1, 'name': 'Red', 'is_machine': False}, + {'color_int': 2, 'name': 'Green', 'is_machine': False}, + {'color_int': 3, 'name': 'Yellow', 'is_machine': False}, + {'color_int': 4, 'name': 'Blue', 'is_machine': False}, + {'color_int': 5, 'name': 'Magenta', 'is_machine': False}, + {'color_int': 6, 'name': 'Cyan', 'is_machine': False}, +] + +qry_app_players = """INSERT INTO players (color_int, name, is_machine) +VALUES (:color_int, :name, :is_machine)""" + +for plr in plrs: + cur.execute(qry_app_players, plr) + +machines_fld_types = { + 'color_int': 'Integer', + 'name': 'Text', + 'is_machine': 'Integer', + 'alg_1': 'Text', + 'alg_2': 'Text', + 'alg_3': 'Text', + 'export_alg_res': 'Integer' +} + +crt_flds = '' +for k in machines_fld_types: + crt_flds += f'{k} {machines_fld_types[k]}, ' +crt_flds = crt_flds[:-2] + +qry_crt_machines = f'CREATE TABLE machines ({crt_flds})' +cur.execute(qry_crt_machines) + +machines = [ + {'color_int': 1, 'name': 'Red', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, + {'color_int': 2, 'name': 'Green', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, + {'color_int': 3, 'name': 'Yellow', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, + {'color_int': 4, 'name': 'Blue', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, + {'color_int': 5, 'name': 'Magenta', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, + {'color_int': 6, 'name': 'Cyan', 'is_machine': False, 'alg_1': 'no_alg', 'alg_2': 'no_alg', 'alg_3': 'no_alg', 'export_alg_res': False}, +] + +qry_app_machine = """INSERT INTO machines (color_int, name, is_machine, alg_1, alg_2, alg_3, export_alg_res) +VALUES (:color_int, :name, :is_machine, :alg_1, :alg_2, :alg_3, :export_alg_res)""" + +for machine in machines: + cur.execute(qry_app_machine, machine) + +con.commit() +con.close() +con = None +cur = None
\ No newline at end of file diff --git a/edt_db.py b/edt_db.py new file mode 100644 index 0000000..dc4fc2b --- /dev/null +++ b/edt_db.py @@ -0,0 +1,13 @@ +from sqlite3 import * + +con = connect('games.db') +cur = con.cursor() + +qry_sql = """ALTER TABLE machines +ADD export_alg_res Integer""" +cur.execute(qry_sql) + +con.commit() +con.close() +con = None +cur = None
\ No newline at end of file diff --git a/entry.py b/entry.py new file mode 100644 index 0000000..04ff074 --- /dev/null +++ b/entry.py @@ -0,0 +1,173 @@ +# NOTES: +# Look into subclasses +# - Report() +# - should this be a Menu subclass? +# - ReportSummary() & ReportDetail() +# - ReportSummary puts a stat in the item fiels like Param does. +# - ReportDetail prints new lines with columns and rows on .indicate() +# - I do not want to select a report entry. So doesn't need to be an EntryList? +# - I would like some connectivity to Param entries that would change the report args + +from msvcrt import * + +class Entry(): + def __init__(self, text, color_int=7): + self.text = text + self.color_int = color_int + self.select = False + + @property + def text_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[9{self.color_int}m' + + @property + def indicate_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[10{self.color_int}m\033[30m' + + def indicate(self, color_int=None): + color = '\033[0m' + if self.select and color_int == None: + color = f'\033[10{self.color_int}m\033[30m' + elif self.select: + color = f'\033[10{color_int}m\033[30m' + elif not self.select and self.color_int != 7: + color = f'\033[9{self.color_int}m' + return color + ' ' + self.text + ' ' + '\033[0m' + +class Launcher(Entry): + def __init__(self, text, meth, color_int=7, args_getr_meth=None, *args, **kwargs): + super().__init__(text, color_int) + self.meth = meth + self.color_int = color_int + self.args_getr_meth = args_getr_meth + self.args = args + self.kwargs = kwargs + + @property + def item(self): + return self + + def get_args(self): + if self.args_getr_meth != None: + self.args = self.args_getr_meth() + + def launch(self): + self.get_args() + self.meth(*self.args) + + + +# There are Params with a control list to select from +# There are Params that use input() +# Need to handle boolean values for toggle setting. + # I could use existing list functionality passing a list [False, True] + # My vision was to display a "box" with brakets and populate with a star [*] for True, [ ] for False. +class Param(Entry): + def __init__(self, text, init_val=None, color_int=7, itm_color_int=None, setr_meth=None, getr_meth=None): + super().__init__(text, color_int) + if getr_meth != None: + self.options = getr_meth() + self.item = self.options[0] + elif type(init_val) is list: + self.options = init_val + self.item = init_val[0] + else: + self.item = init_val + self.edit = False + self.itm_color_int = itm_color_int + self.setr_meth = setr_meth + self.getr_meth = getr_meth + + def set_item(self, new_val): + self.item = new_val + if self.setr_meth != None: + self.setr_meth(new_val) + + def get_options(self): + if self.getr_meth != None: + self.options = self.getr_meth() + if not self.item in self.options: + self.set_item(self.options[0]) + + def indicate(self, color_int=None): + if self.color_int != 7: + txt_clr_i = self.color_int + elif color_int != None: + txt_clr_i = color_int + else: + txt_clr_i = self.color_int + + if self.itm_color_int != None: + itm_clr_i = self.itm_color_int + else: + itm_clr_i = txt_clr_i + + if self.select and self.edit: + txt_color = f'\033[9{txt_clr_i}m' + itm_color = f'\033[10{itm_clr_i}m\033[30m\033[s' + elif self.select: + txt_color = f'\033[10{txt_clr_i}m\033[30m' + itm_color = f'\033[9{itm_clr_i}m' + else: + txt_color = f'\033[9{txt_clr_i}m' + itm_color = f'\033[9{itm_clr_i}m' + + txt = '' + if len(self.text) < 6: + txt = f'{txt_color} {self.text} \033[0m\t\t' + elif len(self.text) >= 6 and len(self.text) < 14: + txt = f'{txt_color} {self.text} \033[0m\t' + elif len(self.text) >= 14: + txt = f'{txt_color} {self.text[0:15]} \033[0m' + + return f'''{txt}: {itm_color} {str(self.item)} \033[0m''' + + # Using list indexes is lazy. I really want to "professionalize" the code by using iterator functionality. This would also handle other data types and custom class objects as well. + def edit_item(self, next='NEXT'): + if next == 'NEXT': + next_ind = self.options.index(self.item) + 1 + if next_ind > (len(self.options) - 1): + next_ind = 0 + elif next == 'PREV': + next_ind = self.options.index(self.item) - 1 + if next_ind < 0: + next_ind = len(self.options) - 1 + self.set_item(self.options[next_ind]) + + if self.itm_color_int != None: + itm_clr_i = self.itm_color_int + else: + itm_clr_i = self.color_int + + itm_color = f'\033[10{itm_clr_i}m\033[30m' + print(f'\033[u{itm_color} {self.item} \033[0m', sep='') + + def keyboard_edit(self): + itm_color = f'\033[10{self.color_int}m\033[30m' + if hasattr(self, 'options'): + self.get_options() + while True: + cmd = getch() + if cmd == b'\x1b' or cmd == b'\x08' or cmd == b'\r': + break + elif cmd == b'\x00' or cmd == b'\xe0': + cmd = getch() + if cmd == b'M' or cmd == b'P': + self.edit_item('NEXT') + elif cmd == b'K' or cmd == b'H': + self.edit_item('PREV') + elif cmd == b'\t': + self.edit_item('NEXT') + else: + # there are some UI improvements that could be made. you are typing over the old value on the screen. + # I don't want it to clear the old value, I want it to wait for a keyboard command before clearing. + # if we did that, we'd actually have to print that keystroke and put it in the self.item. + new_val = input(f'\033[u{itm_color} ') + if new_val: + self.set_item(new_val) + + + + diff --git a/entry_list.py b/entry_list.py new file mode 100644 index 0000000..09719fd --- /dev/null +++ b/entry_list.py @@ -0,0 +1,70 @@ +#Notes: +# There is a simplification opportinity to depreciate the EntryList class. +# because we are using list index funtionality and not iterator functionalit +# just put the Entry objects into a list object, then create a nav str object that is populated with Entry.text items. +# ks == [b'\r'] will loop to find entry.text == nav_str +# Should we keep EntryList, but move that functionality here? +# the entry.indicate() method is referencing entry.select. May need to keep the .select setter functionality. + +class EntryList(): + def __init__(self, init_list): + self.entries = init_list + self.iter_entries = iter(init_list) + self.entries[0].select = True + + def __iter__(self): + self.iter_entries = iter(self.entries) + return self.iter_entries + + def __next__(self): + return next(self.iter_entries) + + # FOR THE LOVE OF GOD DEVS MAKE BACK A THING + def __back__(self): + pass + + def add_entry(self, entry): + if not entry in self.entries: + entry.select = False + self.entries.append(entry) + self.iter_entries = iter(self.entries) + + def remove_entry(self, entry): + chk = False + if entry in self.entries: + chk = entry.select + self.entries.remove(entry) + self.iter_entries = iter(self.entries) + if chk: + self.entries[0].select = True + + def set_select(self): + i = 0 + for entry in self.entries: + entry.select = (i == 0) + i += 1 + + def nav(self, next = 'NEXT'): + chk = False + if next == 'NEXT': + for entry in self.entries: + if entry.select: + chk = entry.select + entry.select = False + elif chk: + entry.select = chk + chk = False + self.entries[0].select = chk + + elif next == 'PREV': + for entry in reversed(self.entries): + if entry.select: + chk = entry.select + entry.select = False + elif chk: + entry.select = chk + chk = False + self.entries[-1].select = chk + + + diff --git a/games.db b/games.db Binary files differnew file mode 100644 index 0000000..3915a40 --- /dev/null +++ b/games.db diff --git a/load_nav.py b/load_nav.py new file mode 100644 index 0000000..2b5eba0 --- /dev/null +++ b/load_nav.py @@ -0,0 +1,227 @@ +from csv import * +from sqlite3 import * +from entry import * +from entry_list import * +from menu import * +from player import * +from board import * +from bot import * +from access_methods import * + +def launch_game(plr_1, plr_2): + size = lab_entry_4.item + game_board = Board(plr_1, plr_2, size=size) + game_board.play() + if plr_1.is_machine and plr_2.is_machine: + input() + +def launch_games(plr_1, plr_2, game_count): + size = lab_entry_4.item + wdl_stats_param = { + 'plr_1_name': plr_1.name, + 'plr_1_color_int': plr_1.color_int, + 'plr_1_text_color': plr_1.text_color, + 'plr_2_name': plr_2.name, + 'plr_2_color_int': plr_2.color_int, + 'plr_2_text_color': plr_2.text_color, + } + + for i in range(game_count): + if kbhit(): + ks = [getch()] + if kbhit(): ks.append(getch()) + if ks == [b'\x1b']: + # would like an options dialogue here + break + if i % 2 == 0: + game_board = Board(plr_1, plr_2, wdl_stats_param, size=size) + game_board.play() + else: + game_board = Board(plr_2, plr_1, wdl_stats_param, size=size) + game_board.play() + if plr_1.is_machine and plr_2.is_machine: + input() + +con = connect('games.db') +cur = con.cursor() + +# Main Menu +mm_prompt = 'Main Menu' + +mm_entry_1 = Entry('Play') +mm_entry_2 = Entry('Laboratory') +mm_entry_3 = Entry('Story Mode') +mm_entry_4 = Entry('Profiles') +mm_entry_5 = Entry('Control Bot Profiles') + +mm_entries = EntryList([mm_entry_1, mm_entry_2, mm_entry_3, mm_entry_4, mm_entry_5]) + +mm_menu = Menu(mm_prompt, mm_entries) + + +# Story Mode +story_prompt = 'Select Human User and AI Machine and complete the challenges together.' + +story_entry_1 = Entry('User') +story_entry_2 = Entry('Machine') +story_entry_3 = Entry('Challenges') + +story_entries = EntryList([story_entry_1, story_entry_2, story_entry_3]) + +story_menu = Menu(story_prompt, story_entries) + +# Profiles menu +prof_prompt = 'Manage User and AI profiles.' + +prof_entry_1 = Entry('Red', color_int=1) +prof_entry_3 = Entry('Yellow', color_int=3) +prof_entry_2 = Entry('Green', color_int=2) +prof_entry_6 = Entry('Cyan', color_int=6) +prof_entry_4 = Entry('Blue', color_int=4) +prof_entry_5 = Entry('Magenta', color_int=5) + +prof_entries = EntryList([prof_entry_1, prof_entry_3, prof_entry_2, prof_entry_6, prof_entry_4, prof_entry_5]) + +prof_menu = Menu(prof_prompt, prof_entries) + +# Bot Menu +bot_prompt = 'Edit Bot Settings' + +bot_1_entry = Entry('Bot 1') +bot_2_entry = Entry('Bot 2') + +bot_entries = EntryList([bot_1_entry, bot_2_entry]) + +bot_menu = Menu(bot_prompt, bot_entries) + +# Load Entries +mm_entry_3.item = story_menu +mm_entry_4.item = prof_menu +mm_entry_5.item = bot_menu + +# initialize players +# set thier menu attribute to color entry.item + +rst = OpenRecordset(con, "SELECT * FROM players") + +for rcd in rst: + rcd['is_machine'] = bool(rcd['is_machine']) + if rcd['is_machine']: rcd['cls'] = Machine + else: rcd['cls'] = Player + +plr_1 = rst[0]['cls'](rst[0]['color_int'], rst[0]['name'], rst[0]['is_machine'], prof_entry_1) +plr_2 = rst[1]['cls'](rst[1]['color_int'], rst[1]['name'], rst[1]['is_machine'], prof_entry_2) +plr_3 = rst[2]['cls'](rst[2]['color_int'], rst[2]['name'], rst[2]['is_machine'], prof_entry_3) +plr_4 = rst[3]['cls'](rst[3]['color_int'], rst[3]['name'], rst[3]['is_machine'], prof_entry_4) +plr_5 = rst[4]['cls'](rst[4]['color_int'], rst[4]['name'], rst[4]['is_machine'], prof_entry_5) +plr_6 = rst[5]['cls'](rst[5]['color_int'], rst[5]['name'], rst[5]['is_machine'], prof_entry_6) + +bot_1 = Bot('Bot 1', bot_1_entry) +bot_2 = Bot('Bot 2', bot_2_entry) + +# I would like to find an alternative to a player list being an attribute of all the players +# class method/attribute? +plr_list = [plr_1, plr_2, plr_3, plr_4, plr_5, plr_6, bot_1, bot_2] + +plr_1.plr_list = plr_list +plr_2.plr_list = plr_list +plr_3.plr_list = plr_list +plr_4.plr_list = plr_list +plr_5.plr_list = plr_list +plr_6.plr_list = plr_list +bot_1.plr_list = plr_list +bot_2.plr_list = plr_list + +prof_entry_1.item = plr_1.menu +prof_entry_2.item = plr_2.menu +prof_entry_3.item = plr_3.menu +prof_entry_4.item = plr_4.menu +prof_entry_5.item = plr_5.menu +prof_entry_6.item = plr_6.menu +bot_1_entry.item = bot_1.menu +bot_2_entry.item = bot_2.menu + +# do we have to define the get_players() getter method after the players have been initialized? +def get_players(): + players = [] + for entry in prof_entries.entries: + players.append(entry.text) + for entry in bot_entries.entries: + players.append(entry.text) + return players + +def set_plrs_getr(plr_param, alt_plr_param): + def get_plrs(): + players = [] + for entry in prof_entries.entries: + if entry.color_int != alt_plr_param.itm_color_int: + players.append(entry.text) + for entry in bot_entries.entries: + if entry.text != alt_plr_param.item: + players.append(entry.text) + return players + return get_plrs + +# there is a bug if 2 or more profiles have the same name +def set_plr_setr(param): + def set_plr(plr_name): + clr_i = None + for entry in prof_entries.entries: + if entry.text == plr_name: + clr_i = entry.color_int + for entry in bot_entries.entries: + if entry.text == plr_name: + clr_i = entry.color_int + param.itm_color_int = clr_i + return set_plr + +# Play/Lab Menu +play_prompt = 'Play Menu' +lab_prompt = 'Welcome to the Laboratory!' + +init_plrs = get_players() + +lab_plr_1 = Param('Player 1', init_val=init_plrs) +lab_plr_2 = Param('Player 2', init_val=init_plrs) +lab_plr_1.setr_meth = set_plr_setr(lab_plr_1) +lab_plr_2.setr_meth = set_plr_setr(lab_plr_2) +lab_plr_1.getr_meth = set_plrs_getr(lab_plr_1, lab_plr_2) +lab_plr_2.getr_meth = set_plrs_getr(lab_plr_2, lab_plr_1) +lab_plr_1.set_item(lab_plr_1.item) +lab_plr_2.set_item(lab_plr_2.item) +lab_plr_2.get_options() +lab_plr_1.get_options() + +def get_game_players(): + global plr_list + for plr in plr_list: + if lab_plr_1.item == plr.name: game_plr_1 = plr + if lab_plr_2.item == plr.name: game_plr_2 = plr + return [game_plr_1, game_plr_2] + +play_entry_3 = Launcher('Play', launch_game, args_getr_meth=get_game_players) +lab_entry_3 = Param('Games', 10) +lab_entry_4 = Param('Board Size', [2, 1]) + +def get_series_settings(): + args = get_game_players() + args.append(int(lab_entry_3.item)) + return args + +lab_entry_5 = Launcher('Play Games', launch_games, args_getr_meth=get_series_settings) +lab_entry_6 = Entry('Help') +lab_entry_7 = Entry('Reports') + +play_entries = EntryList([lab_plr_1, lab_plr_2, lab_entry_4, play_entry_3]) +lab_entries = EntryList([lab_plr_1, lab_plr_2, lab_entry_3, lab_entry_4, lab_entry_5, lab_entry_6, lab_entry_7]) + +play_menu = Menu(play_prompt, play_entries) +lab_menu = Menu(lab_prompt, lab_entries) + +mm_entry_1.item = play_menu +mm_entry_2.item = lab_menu + + + + + @@ -0,0 +1,114 @@ +# check if the entry.py and time modules are being used. Remove import if not. +from msvcrt import * +from entry import * +from time import * + +class Menu(): + def __init__(self, prompt, entries, color_int=7, side_bar=None): + self.prompt = prompt + self.entries = entries + self.iter_entries = iter(entries) + self.color_int = color_int + self.next_item = None + self.side_bar = side_bar + + @property + def text_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[9{self.color_int}m' + + @property + def indicate_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[10{self.color_int}m\033[30m' + + def refresh(self): + if self.color_int == 7: color_int = None + else: color_int = self.color_int + self.text = '\n' + if color_int: self.text += f'\033[9{color_int}m' + self.text += self.prompt + self.text += '\n' * 2 + for entry in self.entries: + if hasattr(entry, 'get_options'): + entry.get_options() + self.text += '\t' + entry.indicate(color_int) + if color_int: self.text += f'\033[9{color_int}m' + self.text += '\n' + self.text += '\n' + + def open(self, prev_menu): + self.clear() + self.prev_menu = prev_menu + self.next_item = None + self.entries.set_select() + self.refresh() + self.print_side_bar() + print(self.text) + self.keyboard_nav() + + def clear(self): + print('\033[0m', '\033[?25l', '\033[2J', '\033[0;0f', sep='', end='') + + def reprint(self): + self.clear() + self.print_side_bar() + print(self.text) + + def print_side_bar(self): + if self.side_bar: + lines = self.side_bar.split('\n') + l = 2 + for line in lines: + print(f'\033[{l};40f', f'\033[9{self.color_int}m', line, '\033[0m', sep='', end='') + l+=1 + print('\033[0;0f', end='') + + + def nav_entries(self, next='NEXT'): + self.entries.nav(next) + self.refresh() + self.reprint() + + + def edit_param(self, param): + param.edit = True + self.refresh() + self.reprint() + param.keyboard_edit() + param.edit = False + self.refresh() + self.reprint() + + def keyboard_nav(self): + while True: + cmd = getch() + if cmd == b'\x1b' or cmd == b'\x08': + self.next_item = self.prev_menu + break + elif cmd == b'\x00' or cmd == b'\xe0': + cmd = getch() + if cmd == b'M' or cmd == b'P': + self.nav_entries(next='NEXT') + elif cmd == b'K' or cmd == b'H': + self.nav_entries(next='PREV') + elif cmd == b'\t': + self.nav_entries(next='NEXT') + elif cmd == b'\r': + for entry in self.entries: + if entry.select and type(entry) is Param: + self.edit_param(entry) + elif entry.select: + self.next_item = entry.item + if self.next_item != None: + break + + + + + + + + + +
\ No newline at end of file diff --git a/moves_out_1.sql b/moves_out_1.sql new file mode 100644 index 0000000..91b1c47 --- /dev/null +++ b/moves_out_1.sql @@ -0,0 +1,11 @@ +SELECT + game_log.*, + move_log.* +FROM + game_log INNER JOIN move_log + ON game_log.game_id = move_log.game_id +WHERE + move_log.board = :fen_board + AND game_log.plr_X_name = :plr_name + AND game_log.plr_X_color_int = :plr_color_int +;
\ No newline at end of file diff --git a/moves_out_2.sql b/moves_out_2.sql new file mode 100644 index 0000000..e6b2a39 --- /dev/null +++ b/moves_out_2.sql @@ -0,0 +1,12 @@ +SELECT DISTINCT cur_move_log.board AS cur_board, cur_move_log.move, move_log.board AS next_board +FROM + ( + SELECT game_log.*, move_log.*, move_log.turn_num + 2 AS next_turn_num + FROM game_log INNER JOIN move_log ON game_log.game_id = move_log.game_id + WHERE move_log.board = :fen_board AND game_log.plr_X_name = :plr_name AND game_log.plr_X_color_int = :plr_color_int + ) AS cur_move_log INNER JOIN move_log + ON cur_move_log.game_id = move_log.game_id + AND cur_move_log.next_turn_num = move_log.turn_num +ORDER BY + cur_move_log.move, next_board +;
\ No newline at end of file @@ -0,0 +1,22 @@ +from load_nav import * + +menu_tree = [None, None, mm_menu] + +while True: + + # if next item is the previous item, remove next item and current item and load the previous item + if menu_tree[-1] == menu_tree[-3]: + menu_tree.pop(-1) + menu_tree.pop(-1) + + if menu_tree[-1] == None: + # quit program + break + elif type(menu_tree[-1]) is Launcher: + menu_tree[-1].launch() + menu_tree.pop(-1) + elif type(menu_tree[-1]) is Menu: + menu_tree[-1].open(prev_menu=menu_tree[-2]) + menu_tree[-1].clear() + menu_tree.append(menu_tree[-1].next_item) + diff --git a/player.py b/player.py new file mode 100644 index 0000000..9b80ed9 --- /dev/null +++ b/player.py @@ -0,0 +1,563 @@ +# A reports section would be nice. + +from sqlite3 import * +from csv import * +import os +from menu import * +from entry_list import * +from entry import * +from access_methods import * +from analysis_board import * +from random import * +from subprocess import * +import importlib +import idlelib + +class Player(): + def __init__(self, color_int, name, is_machine, entry, menu=None, plr_list=None): + self.color_int = color_int + self.is_machine = is_machine + self.entry = entry + self.name = name + entry.text = name + self.plr_list = plr_list + if menu != None: + self.menu = menu + self.entries = menu.entries + menu.entries.entries[0].setr_meth = self.set_name + menu.entries.entries[1].setr_meth = self.set_machine + self.param_name = menu.entries.entries[0] + self.param_is_machine = menu.entries.entries[1] + self.prompt = f"Edit {self.name}'s Settings" + else: + # initialize a menu and set it to self.menu + self.param_name = Param('Name', name, color_int, setr_meth=self.set_name) + self.param_is_machine = Param('AI', [False, True], color_int, setr_meth=self.set_machine) + self.param_is_machine.item = is_machine + self.entries = EntryList([self.param_name, self.param_is_machine]) + self.prompt = f"Edit {self.name}'s Settings" + self.menu = Menu(self.prompt, self.entries, entry.color_int) + self.menu.side_bar = None + entry.item = self.menu + + @property + def __dict__(self): + d = {} + d['name'] = self.name + d['color_int'] = self.color_int + d['is_machine'] = self.is_machine + if hasattr(self, 'alg_1'): d['alg_1'] = self.alg_1.__name__ + if hasattr(self, 'alg_2'): d['alg_2'] = self.alg_2.__name__ + if hasattr(self, 'alg_3'): d['alg_3'] = self.alg_3.__name__ + if hasattr(self, 'export_alg_res'): d['export_alg_res'] = self.export_alg_res + return d + + @property + def text_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[9{self.color_int}m' + + @property + def indicate_color(self): + if self.color_int > 0 and self.color_int <=7: + return f'\033[10{self.color_int}m\033[30m' + + def save_file(self): + con = connect('games.db') + cur = con.cursor() + + upd_qry_sql = """ + UPDATE players + SET name = :name, is_machine = :is_machine + WHERE color_int = :color_int + """ + + cur.execute(upd_qry_sql, self.__dict__) + + con.commit() + con = None + cur = None + + # I want to store a getter method as like an attribute, but have the stored value be the method and not the returned value of the method, that way the stored value returns the updated value when called. + def set_name(self, new_name): + self.name = new_name + self.save_file() + self.entry.text = new_name + self.prompt = f"Edit {self.name}'s Settings" + self.menu.prompt = self.prompt + if hasattr(self, 'post'): + self.menu.side_bar = self.post + else: + self.menu.side_bar = None + + def set_machine(self, is_machine): + if is_machine: + self = Machine(self.color_int, self.name, is_machine, self.entry, self.menu, self.plr_list) + elif not is_machine: + self = Player(self.color_int, self.name, is_machine, self.entry, self.menu, self.plr_list) + # Will need to remove Machine specific Entry objects in self.entries and self.menu.entries + self.entries.remove_entry(self.entries.entries[-1]) + self.entries.remove_entry(self.entries.entries[-1]) + self.entries.remove_entry(self.entries.entries[-1]) + self.entries.remove_entry(self.entries.entries[-1]) + self.entries.remove_entry(self.entries.entries[-1]) + self.entries.remove_entry(self.entries.entries[-1]) + self.menu.entries = self.entries + + self.plr_list[self.color_int-1] = self + + self.save_file() + +# Add params that conrtol move step processes (best move & random) +# Add launcher that pre-aggregates decision model per fen_board +class Machine(Player): + def __init__(self, color_int, name, is_machine, entry, menu=None, plr_list=None): + super().__init__(color_int, name, is_machine, entry, menu, plr_list) + self.load_machine_settings() + self.param_alg_1 = Param('1st Algorithm', self.alg_name_list, color_int, setr_meth=self.set_alg_1) + self.param_alg_2 = Param('2nd Algorithm', self.alg_name_list, color_int, setr_meth=self.set_alg_2) + self.param_alg_3 = Param('3rd Algorithm', self.alg_name_list, color_int, setr_meth=self.set_alg_3) + self.param_alg_1.item = self.alg_1.__name__ + self.param_alg_2.item = self.alg_2.__name__ + self.param_alg_3.item = self.alg_3.__name__ + self.param_export_alg_res = Param('Export Alg', [False, True], color_int, setr_meth=self.set_export_alg_res) + self.param_export_alg_res.item = self.export_alg_res + self.static_alg_gen = Launcher('Generate Static Algorithm', self.static_alg_generate, color_int) + self.static_alg_edt = Launcher('Edit Static Algorithm', self.static_alg_edit, color_int) + self.entries.add_entry(self.param_alg_1) + self.entries.add_entry(self.param_alg_2) + self.entries.add_entry(self.param_alg_3) + self.entries.add_entry(self.param_export_alg_res) + self.entries.add_entry(self.static_alg_gen) + self.entries.add_entry(self.static_alg_edt) + self.menu.entries = self.entries + self.menu.side_bar = self.post + self.bad_moves = [] + self.on_alg_num = 0 + self.board = AnalysisBoard() + + def set_alg_1(self, alg_name): + self.alg_1 = self.alg_list[self.alg_name_list.index(alg_name)] + self.save_machine_settings() + + def set_alg_2(self, alg_name): + self.alg_2 = self.alg_list[self.alg_name_list.index(alg_name)] + self.save_machine_settings() + + def set_alg_3(self, alg_name): + self.alg_3 = self.alg_list[self.alg_name_list.index(alg_name)] + self.save_machine_settings() + + def set_export_alg_res(self, export_alg_res): + self.export_alg_res = export_alg_res + self.save_machine_settings() + + def save_machine_settings(self): + con = connect('games.db') + cur = con.cursor() + + upd_qry_sql = """ + UPDATE machines + SET name = :name, is_machine = :is_machine, alg_1 = :alg_1, alg_2 = :alg_2, alg_3 = :alg_3, export_alg_res = :export_alg_res + WHERE color_int = :color_int + """ + + cur.execute(upd_qry_sql, self.__dict__) + + con.commit() + con = None + cur = None + + def load_machine_settings(self): + con = connect('games.db') + qry_sql = """ + SELECT * + FROM machines + WHERE color_int = :color_int + """ + + rcd = OpenRecordset(con, qry_sql, self.__dict__)[0] + + self.alg_1 = self.alg_list[self.alg_name_list.index(rcd['alg_1'])] + self.alg_2 = self.alg_list[self.alg_name_list.index(rcd['alg_2'])] + self.alg_3 = self.alg_list[self.alg_name_list.index(rcd['alg_3'])] + self.export_alg_res = bool(rcd['export_alg_res']) + + con = None + + def static_alg_generate(self): + con = connect('games.db') + static_script = open(f'{self.name}_{self.color_int}_static_alg.py', 'w', newline='') + static_script.write("def static_alg(fen_board=None):\n") + static_script.write("\tsqr_nm = ''\n\n") + static_script.write("##\tdemo board\n") + static_script.write("##\tif fen_board == None:\n") + static_script.write("##\t\tfen_board = '3/3/3'\n\n") + + static_script.write("\tif fen_board == None: sqr_nm = ''\n") + + qry_sql = """ + SELECT DISTINCT board + FROM game_log INNER JOIN move_log ON game_log.game_id = move_log.game_id + WHERE + ( + plr_X_name = :name + AND plr_X_color_int = :color_int + AND turn = 'X' + ) + OR + ( + plr_O_name = :name + AND plr_O_color_int = :color_int + AND turn = 'O' + ) + """ + param = self.__dict__ + + boards = OpenRecordset(con, qry_sql, param) + + for board in boards: + sqr_nm = '' + sqr_nm = self.move(board['board']) + if sqr_nm != '': + static_script.write(f"\tif fen_board == '{board['board']}': sqr_nm = '{sqr_nm}'\n") + + static_script.write("\n\treturn sqr_nm\n") + + def static_alg_edit(self): + static_script = os.path.join(os.getcwd(),f'{self.name}_{self.color_int}_static_alg.py') + if os.path.exists(static_script): + idle_bat = idlelib.__file__.replace('__init__.py','idle.bat') + Popen(f'{idle_bat} "{static_script}"') + + + @property + def post(self): + return f'{self.name}:\nHello World!\nI am {self}.\nMy name is {self.name}.' + + @property + def alg_list(self): + lst = [] + for att in dir(self): + if len(att) >= 5: + if att[-4:] == '_alg' or att[:5] == 'rand_': + lst.append(getattr(self, att)) + return lst + + # should we have distinct sub_alg select and sort_alg select? + # straight field names is one list to select for sort alg + @property + def alg_name_list(self): + lst = [] + for alg in self.alg_list: + lst.append(alg.__name__) + return lst + + def rand_fr(self, fen_board): return choice('abc') + choice('123') + + def rand_sqr(self, fen_board): return choice(['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']) + + def rand_lgl(self, fen_board): return choice(self.board.legal_moves) + + def rand_rem(self, fen_board): + legal_moves = self.board.legal_moves + if len(self.bad_moves) > 0: + for move in self.bad_moves: + legal_moves.remove(move) + pass + # for when all moves are losing + if legal_moves == []: + legal_moves = self.board.legal_moves + return choice(legal_moves) + + def no_alg(self, fen_board): return '' + + def static_alg(self, fen_board): + sqr_nm = '' + static_script = os.path.join(os.getcwd(),f'{self.name}_{self.color_int}_static_alg.py') + if os.path.exists(static_script): + static_mod_name = f"{self.name}_{self.color_int}_static_alg" + static_alg_module = importlib.import_module(static_mod_name, package=None) + sqr_nm = static_alg_module.static_alg(fen_board) + + return sqr_nm + + def w_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(win_count)' + sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + # This algorithm works. + # the machine can get stuck in bad habits. + # can get stuck in a loop against a different ML alg + # would like a dynamic way of aggregating past results + def wml_mte_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'({ce} / ((game_log.turns + 1) - move_log.turn_num))' + sort_alg = '(sum_net_factor)' + sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + # if turns-to-end = total-turns => 100% chance do that move + # if turns-to-end = opponent's next turn => 100% chance do not do that move + # it will only use previous moves, even if losing + def eg_alg(self, fen_board): + sqr_nm = '' + clean_chk = False + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): + sa = f"""(CASE + WHEN game_log.turns = move_log.turn_num THEN {ce} + WHEN game_log.turns = move_log.turn_num + 1 THEN {ce} + ELSE NULL + END)""" + sa = sa.replace('\n',' ') + sa = sa.replace('\t','') + return sa + sort_alg = '(sum_net_factor)' + prev_move_set = self.find_wdl_alg_move_set(fen_board, wdl_ces, sub_alg, sort_alg) + + if prev_move_set != None: + if len(prev_move_set) > 0: + if prev_move_set[0] != None: + if prev_move_set[0]['move'] != None: + clean_chk = True + + if clean_chk: + for move in prev_move_set: + if move[sort_alg[1:-1]] != None: + if move[sort_alg[1:-1]] > 0.0: + if sqr_nm == '': + sqr_nm = move['move'] + elif move[sort_alg[1:-1]] < 0.0: + self.bad_moves.append(move['move']) + + # sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + # I am wanting to know which moves gets me to a known winning position or which move getss me to an all-losing position + # do we build a loop that queries each current legal move to generate a list of possible fen_boards from past games, then run those through the eg_alg? + # or one query that can aggragate the results + # will have to group by cur_next_move, then by next_board, aggregate the results on the next_boards, then somehow aggregate the results up to cur_next_move. + + def m_out_1_alg(self, fen_board): + sqr_nm = '' + clean_chk = False + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): + sa = f"""(CASE + WHEN game_log.turns = move_log.turn_num THEN {ce} + WHEN game_log.turns = move_log.turn_num + 1 THEN {ce} + ELSE NULL + END)""" + sa = sa.replace('\n',' ') + sa = sa.replace('\t','') + return sa + sort_alg = '(sum_net_factor)' + prev_move_set = self.find_wdl_alg_move_set(fen_board, wdl_ces, sub_alg, sort_alg) + + if prev_move_set != None: + if len(prev_move_set) > 0: + if prev_move_set[0] != None: + if prev_move_set[0]['move'] != None: + clean_chk = True + + if clean_chk: + for move in prev_move_set: + if move[sort_alg[1:-1]] != None: + if move[sort_alg[1:-1]] > 0.0: + if sqr_nm == '': + sqr_nm = move['move'] + elif move[sort_alg[1:-1]] < 0.0: + self.bad_moves.append(move['move']) + + # sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + # create a dc_rem_alg that filters out known bad moves? + def dc_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(-move_count)' + legal_moves = self.board.legal_moves + prev_move_set = self.find_wdl_alg_move_set(fen_board, wdl_ces, sub_alg, sort_alg) + + for mov_p in prev_move_set: + legal_moves.remove(mov_p['move']) + + if legal_moves == []: + if prev_move_set != None: + if len(prev_move_set) > 0: + if prev_move_set[0] != None: + if prev_move_set[0]['move'] != None: + sqr_nm = prev_move_set[0]['move'] + else: + sqr_nm = legal_moves[0] + + return sqr_nm + + def dc_rem_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(-move_count)' + legal_moves = self.board.legal_moves + prev_move_set = self.find_wdl_alg_move_set(fen_board, wdl_ces, sub_alg, sort_alg) + prev_move_set_0 = prev_move_set + + for mov_p in prev_move_set: + legal_moves.remove(mov_p['move']) + + if legal_moves == []: + if prev_move_set != None: + if len(prev_move_set) > 0: + if prev_move_set[0] != None: + if prev_move_set[0]['move'] != None: + if len(self.bad_moves) > 0: + for move in prev_move_set_0: + if move['move'] in self.bad_moves: + prev_move_set_0.remove(move) + pass + if len(prev_move_set_0) > 0: + sqr_nm = prev_move_set_0[0]['move'] + else: + sqr_nm = prev_move_set[0]['move'] + else: + sqr_nm = legal_moves[0] + + return sqr_nm + + def wml_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(win_count - loss_count)' + sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + def wol_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(win_count / (CASE WHEN loss_count = 0 THEN 0.5 ELSE loss_count END))' + sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + def lmw_alg(self, fen_board): + sqr_nm = '' + wdl_ces = [1.0, 0.0, -1.0] + def sub_alg(ce): return f'{ce}' + sort_alg = '(loss_count - win_count)' + sqr_nm = self.find_wdl_alg_move(fen_board, wdl_ces, sub_alg, sort_alg) + return sqr_nm + + def find_wdl_alg_move(self, fen_board, wdl_ces, sub_alg, sort_alg): + sqr_nm = '' + clean_chk = False + move_set = self.find_wdl_alg_move_set(fen_board, wdl_ces, sub_alg, sort_alg) + + if move_set != None: + if len(move_set) > 0: + if move_set[0] != None: + if move_set[0]['move'] != None: + clean_chk = True + + if clean_chk: + if len(self.bad_moves) > 0: + for move in move_set: + if move['move'] in self.bad_moves: + move_set.remove(move) + pass + if len(move_set) > 0: + sqr_nm = move_set[0]['move'] + + return sqr_nm + + def find_wdl_alg_move_set(self, fen_board, wdl_ces, sub_alg, sort_alg): + con = connect('games.db') + + w_cond = sub_alg(wdl_ces[0]) + d_cond = sub_alg(wdl_ces[1]) + l_cond = sub_alg(wdl_ces[2]) + + qry_sql = open('alg_move.sql','r').read() + + qry_sql = qry_sql.replace('plr_X_', f'plr_{self.board.turn}_') + qry_sql = qry_sql.replace('(1.0 / 1.0)', w_cond) + qry_sql = qry_sql.replace('(0.0 / 1.0)', d_cond) + qry_sql = qry_sql.replace('(-1.0 / 1.0)', l_cond) + qry_sql = qry_sql.replace('(win_count - loss_count)', sort_alg) + + param = { + 'plr_name': self.name, + 'plr_color_int': self.color_int, + 'fen_board': fen_board + } + + move_set = OpenRecordset(con, qry_sql, param) + + if self.export_alg_res: self.log_alg_move(qry_sql, param, move_set) + + con = None + return move_set + + def log_alg_move(self, qry_sql, param, move_set): + + # we'll figure out where to put these later + # will need to update the .txt output for sqlite3.dll to read correct correct directory + # if not os.path.exists(os.path.join(os.getcwd(), 'alg_export')): os.mkdir('alg_export') + # os.chdir(os.path.join(os.getcwd(), 'alg_export')) + + open(f'alg_move_{self.on_alg_num}.sql','w', newline='').write(qry_sql) + + txt_writer = open(f'alg_move_{self.on_alg_num}.txt','w',newline='') + txt_writer.write('.open games.db\n') + txt_writer.write('.mode box\n') + txt_writer.write('.parameter init\n') + for fld in param: + txt_writer.write(f'.parameter set :{fld} {param[fld]}\n') + txt_writer.write(f'.read alg_move_{self.on_alg_num}.sql') + txt_writer = None + + if move_set != None: + if len(move_set) > 0: + if move_set[0] != None: + if move_set[0]['move'] != None: + csv_writer = DictWriter(open(f'alg_move_{self.on_alg_num}.csv','w',newline=''), fieldnames=move_set[0].keys()) + csv_writer.writeheader() + for move in move_set: + csv_writer.writerow(move) + csv_writer = None + + def move(self, fen_board): + sqr_nm = '' + self.board.fen_board = fen_board + self.bad_moves = [] + self.on_alg_num = 0 + if sqr_nm == '': + self.on_alg_num = 1 + sqr_nm = self.alg_1(fen_board) + if sqr_nm == '': + self.on_alg_num = 2 + sqr_nm = self.alg_2(fen_board) + if sqr_nm == '': + self.on_alg_num = 3 + sqr_nm = self.alg_3(fen_board) + self.bad_moves = [] + self.on_alg_num = 0 + self.board.fen_board = '3/3/3' + return sqr_nm + + + + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..61f6781 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +#Tic Tac Toe Machine Learning Lab Kit + +To run this project, execute play.py. + +This is a simple and fun project for me to learn python. Not all the menu items have been set up and will crash if they are selected. The keyboard interface only works with Microsoft Windows OS. + +It does not apply proper machine learning techniques. The "machine learning" is aggregating in raw SQL for each occurance. They do infact improve and figure out how to play. + +Next steps for this project will be to integrate proper machine learning into the the AI profiles. Then add the rest of the menu elements. + +I may decide to do this in JavaScript and HTML with the Math.js or TensorFlow libraries. The potential for this project is educational. It would be nice to share easily and be more interactive.
\ No newline at end of file diff --git a/wdl_stats.sql b/wdl_stats.sql new file mode 100644 index 0000000..d247ee0 --- /dev/null +++ b/wdl_stats.sql @@ -0,0 +1,59 @@ +SELECT + :plr_1_name AS plr_1_name, + :plr_1_text_color AS plr_1_text_color, + :plr_2_name AS plr_2_name, + :plr_2_text_color AS plr_2_text_color, + sum(CASE + WHEN plr_X_name = :plr_1_name + AND plr_X_color_int = :plr_1_color_int + AND victor_name = :plr_1_name + AND victor_color_int = :plr_1_color_int + THEN 1 ELSE 0 END) AS plr_1_X_wins, + sum(CASE + WHEN plr_O_name = :plr_1_name + AND plr_O_color_int = :plr_1_color_int + AND victor_name = :plr_1_name + AND victor_color_int = :plr_1_color_int + THEN 1 ELSE 0 END) AS plr_1_O_wins, + sum(CASE + WHEN plr_X_name = :plr_2_name + AND plr_X_color_int = :plr_2_color_int + AND victor_name = :plr_2_name + AND victor_color_int = :plr_2_color_int + THEN 1 ELSE 0 END) AS plr_2_X_wins, + sum(CASE + WHEN plr_O_name = :plr_2_name + AND plr_O_color_int = :plr_2_color_int + AND victor_name = :plr_2_name + AND victor_color_int = :plr_2_color_int + THEN 1 ELSE 0 END) AS plr_2_O_wins, + sum(CASE + WHEN plr_X_name = :plr_1_name + AND plr_X_color_int = :plr_1_color_int + AND plr_O_name = :plr_2_name + AND plr_O_color_int = :plr_2_color_int + AND victor_name = '' + AND victor_color_int = 0 + THEN 1 ELSE 0 END) AS game_X_O_draws, + sum(CASE + WHEN plr_X_name = :plr_2_name + AND plr_X_color_int = :plr_2_color_int + AND plr_O_name = :plr_1_name + AND plr_O_color_int = :plr_1_color_int + AND victor_name = '' + AND victor_color_int = 0 + THEN 1 ELSE 0 END) AS game_O_X_draws +FROM game_log +WHERE + ( + plr_X_name = :plr_1_name + AND plr_X_color_int = :plr_1_color_int + AND plr_O_name = :plr_2_name + AND plr_O_color_int = :plr_2_color_int + ) OR ( + plr_X_name = :plr_2_name + AND plr_X_color_int = :plr_2_color_int + AND plr_O_name = :plr_1_name + AND plr_O_color_int = :plr_1_color_int + ) +;
\ No newline at end of file |