summaryrefslogtreecommitdiff
path: root/player.py
diff options
context:
space:
mode:
authorTinWoodman92 <chrhodgden@gmail.com>2023-12-16 06:18:44 -0600
committerTinWoodman92 <chrhodgden@gmail.com>2023-12-16 06:18:44 -0600
commit158b36e51c8c2925d6c8af6e08380369d019378e (patch)
treef91fadecf85e982714927eb6ab451c9173e37de0 /player.py
Initial commit
Diffstat (limited to 'player.py')
-rw-r--r--player.py563
1 files changed, 563 insertions, 0 deletions
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
+
+
+
+
+
+
+
+
+