From 158b36e51c8c2925d6c8af6e08380369d019378e Mon Sep 17 00:00:00 2001
From: TinWoodman92 <chrhodgden@gmail.com>
Date: Sat, 16 Dec 2023 06:18:44 -0600
Subject: Initial commit

---
 board.py | 662 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 662 insertions(+)
 create mode 100644 board.py

(limited to 'board.py')

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()
+
+
+	
+
+	
-- 
cgit v1.2.3