init
This commit is contained in:
commit
e2774e098e
1
ai.json
Normal file
1
ai.json
Normal file
@ -0,0 +1 @@
|
||||
{"1": 17, "4": 17, "256": 19, "37": 19, "97": 19, "289": 19, "1057": 19, "4129": 20, "16417": 20, "65569": 20, "2149": 19, "2341": 19, "6181": 20, "18469": 20, "67621": 20, "100": 19, "292": 19, "1060": 19, "4132": 20, "16420": 20, "65572": 20, "2404": 19, "6244": 20, "18532": 20, "67684": 20, "352": 19, "1312": 21, "4384": 20, "16672": 20, "65824": 20, "2401": 19, "6433": 20, "18721": 20, "67873": 20, "6241": 20, "18529": 20, "67681": 20, "6436": 19, "18724": 20, "67876": 20, "6496": 19, "18784": 20, "67936": 20, "1573": 19, "1633": 20, "5665": 20, "17953": 20, "67105": 20, "1636": 19, "5668": 20, "17956": 20, "67108": 20, "1321": 21, "1384": 20, "5416": 20, "17704": 20, "66856": 20, "132457": 21, "136489": 20, "148777": 20, "33046": 19, "33094": 21, "34054": 20, "37126": 20, "98566": 20, "41302": 20, "42262": 20, "106774": 20, "108886": 20, "34198": 19, "37270": 20, "98710": 20, "34150": 21, "37222": 20, "98662": 20, "35158": 20, "39238": 20, "100678": 20, "265": 20, "280": 20, "328": 20, "1288": 20, "4360": 20, "16648": 20, "65800": 20, "131353": 20, "131401": 20, "132361": 20, "135433": 20, "139609": 20, "140569": 20, "155929": 20, "1": 16, "4": 16, "256": 17, "37": 19, "97": 19, "289": 19, "1057": 20, "4129": 20, "16417": 20, "65569": 20, "8293": 19, "8485": 19, "9253": 20, "24613": 20, "73765": 20, "139621": 19, "140389": 19, "155749": 20, "131077": 19, "131140": 20, "131332": 20, "132100": 22, "135172": 20, "147460": 20, "131173": 19, "131365": 20, "132133": 20, "135205": 20, "147493": 20, "292": 20, "1312": 19, "4384": 20, "16672": 20, "65824": 19, "8545": 20, "9505": 20, "24865": 20, "74017": 20, "140581": 19, "155941": 20, "157093": 21, "32869": 19, "33121": 20, "33889": 20, "36961": 20, "98401": 20, "41317": 19, "42085": 20, "106597": 20, "1060": 20, "4132": 20, "16420": 20, "65572": 20, "8548": 22, "9316": 20, "24676": 20, "73828": 20, "140644": 22, "156004": 20, "2053": 19, "2068": 20, "2116": 20, "2308": 20, "6148": 20, "18436": 20, "67588": 20, "2149": 19, "2341": 20, "6181": 20, "18469": 20, "67621": 20, "517": 19, "532": 19, "580": 19, "4612": 19, "16900": 19, "66052": 20, "613": 19, "1573": 19, "4645": 20, "16933": 19, "66085": 20, "598": 19, "1558": 20, "4630": 20, "16918": 20, "66070": 20, "33046": 19, "33094": 20, "34054": 20, "37126": 20, "98566": 20, "41302": 19, "42262": 20, "106774": 20, "529": 19, "66049": 20, "601": 19, "1561": 20, "4633": 20, "16921": 20, "66073": 20, "1606": 19, "16966": 20, "66118": 20, "8470": 19, "8530": 20, "9490": 20, "24850": 20, "74002": 20, "1633": 20, "5665": 20, "17953": 20, "16996": 20, "21028": 20, "82468": 20, "2374": 20, "2386": 20, "6466": 20, "18754": 20, "67906": 20, "35158": 20, "39238": 20, "100678": 20, "108886": 20, "265": 20, "280": 20, "1288": 20, "4360": 20, "16648": 20, "65800": 20, "131353": 20, "131401": 20, "132361": 20, "135433": 20, "139609": 20, "140569": 20, "155929": 20}
|
1
iter1.json
Normal file
1
iter1.json
Normal file
@ -0,0 +1 @@
|
||||
{"1": 17, "4": 17, "256": 19, "37": 19, "97": 19, "289": 19, "1057": 19, "4129": 20, "16417": 20, "65569": 20, "2149": 19, "2341": 19, "6181": 20, "18469": 20, "67621": 20, "100": 19, "292": 19, "1060": 19, "4132": 20, "16420": 20, "65572": 20, "2404": 19, "6244": 20, "18532": 20, "67684": 20, "352": 19, "1312": 21, "4384": 20, "16672": 20, "65824": 20, "2401": 19, "6433": 20, "18721": 20, "67873": 20, "6241": 20, "18529": 20, "67681": 20, "6436": 19, "18724": 20, "67876": 20, "6496": 19, "18784": 20, "67936": 20, "1573": 19, "1633": 20, "5665": 20, "17953": 20, "67105": 20, "1636": 19, "5668": 20, "17956": 20, "67108": 20, "1321": 21, "1384": 20, "5416": 20, "17704": 20, "66856": 20, "132457": 21, "136489": 20, "148777": 20, "33046": 19, "33094": 21, "34054": 20, "37126": 20, "98566": 20, "41302": 20, "42262": 20, "106774": 20, "108886": 20, "34198": 19, "37270": 20, "98710": 20, "34150": 21, "37222": 20, "98662": 20, "35158": 20, "39238": 20, "100678": 20, "265": 20, "280": 20, "328": 20, "1288": 20, "4360": 20, "16648": 20, "65800": 20, "131353": 20, "131401": 20, "132361": 20, "135433": 20, "139609": 20, "140569": 20, "155929": 20, "1": 16, "4": 16, "256": 17, "37": 19, "97": 19, "289": 19, "1057": 20, "4129": 20, "16417": 20, "65569": 20, "8293": 19, "8485": 19, "9253": 20, "24613": 20, "73765": 20, "139621": 19, "140389": 19, "155749": 20, "131077": 19, "131140": 20, "131332": 20, "132100": 22, "135172": 20, "147460": 20, "131173": 19, "131365": 20, "132133": 20, "135205": 20, "147493": 20, "292": 20, "1312": 19, "4384": 20, "16672": 20, "65824": 19, "8545": 20, "9505": 20, "24865": 20, "74017": 20, "140581": 19, "155941": 20, "157093": 21, "32869": 19, "33121": 20, "33889": 20, "36961": 20, "98401": 20, "41317": 19, "42085": 20, "106597": 20, "1060": 20, "4132": 20, "16420": 20, "65572": 20, "8548": 22, "9316": 20, "24676": 20, "73828": 20, "140644": 22, "156004": 20, "2053": 19, "2068": 20, "2116": 20, "2308": 20, "6148": 20, "18436": 20, "67588": 20, "2149": 19, "2341": 20, "6181": 20, "18469": 20, "67621": 20, "517": 19, "532": 19, "580": 19, "4612": 19, "16900": 19, "66052": 20, "613": 19, "1573": 19, "4645": 20, "16933": 19, "66085": 20, "598": 19, "1558": 20, "4630": 20, "16918": 20, "66070": 20, "33046": 19, "33094": 20, "34054": 20, "37126": 20, "98566": 20, "41302": 19, "42262": 20, "106774": 20, "529": 19, "66049": 20, "601": 19, "1561": 20, "4633": 20, "16921": 20, "66073": 20, "1606": 19, "16966": 20, "66118": 20, "8470": 19, "8530": 20, "9490": 20, "24850": 20, "74002": 20, "1633": 20, "5665": 20, "17953": 20, "16996": 20, "21028": 20, "82468": 20, "2374": 20, "2386": 20, "6466": 20, "18754": 20, "67906": 20, "35158": 20, "39238": 20, "100678": 20, "108886": 20, "265": 20, "280": 20, "1288": 20, "4360": 20, "16648": 20, "65800": 20, "131353": 20, "131401": 20, "132361": 20, "135433": 20, "139609": 20, "140569": 20, "155929": 20}
|
206
main.py
Normal file
206
main.py
Normal file
@ -0,0 +1,206 @@
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
import json
|
||||
|
||||
class Ch(Enum):
|
||||
Empty = 0
|
||||
Cross = 1
|
||||
Circle = 2
|
||||
|
||||
def ch_str(ch: Ch, empty = " ") -> str:
|
||||
match ch:
|
||||
case Ch.Empty:
|
||||
return empty
|
||||
case Ch.Cross:
|
||||
return "X"
|
||||
case Ch.Circle:
|
||||
return "O"
|
||||
|
||||
CH_WIDTH = 2
|
||||
|
||||
class Board:
|
||||
def __init__(self, val = 0) -> None:
|
||||
self.val = val
|
||||
|
||||
def possible_plays(self) -> list[int]:
|
||||
return [i for i in range(9) if (self.val >> i * CH_WIDTH & 0b11) == 0]
|
||||
|
||||
def clone(self) -> Board:
|
||||
return Board(self.val)
|
||||
|
||||
def play(self, ch: Ch, i: int):
|
||||
self.val |= ch.value << i * CH_WIDTH
|
||||
|
||||
def with_play(self, ch: Ch, i: int) -> Board:
|
||||
board = self.clone()
|
||||
board.play(ch, i)
|
||||
return board
|
||||
|
||||
def rotate(self):
|
||||
idcs = [6, 3, 0, 7, 4, 1, 8, 5, 2]
|
||||
vals = [self.val >> i * CH_WIDTH & 0b11 for i in idcs]
|
||||
self.val = 0
|
||||
for i, v in enumerate(vals):
|
||||
self.val |= v << i * CH_WIDTH
|
||||
|
||||
def flip(self):
|
||||
idcs = [6, 7, 8, 3, 4, 5, 0, 1, 2]
|
||||
vals = [self.val >> i * CH_WIDTH & 0b11 for i in idcs]
|
||||
self.val = 0
|
||||
for i, v in enumerate(vals):
|
||||
self.val |= v << i * CH_WIDTH
|
||||
|
||||
def key(self) -> int:
|
||||
return self.val
|
||||
|
||||
def place(self, i: int) -> Ch:
|
||||
return Ch(self.val >> i * CH_WIDTH & 0b11)
|
||||
|
||||
def player_has_won(self, ch: Ch) -> bool:
|
||||
combos = [
|
||||
(0, 1, 2), (3, 4, 5), (6, 7, 8),
|
||||
(0, 3, 6), (1, 4, 7), (2, 5, 8),
|
||||
(0, 4, 8), (2, 4, 6),
|
||||
]
|
||||
for combo in combos:
|
||||
if all(self.place(p) == ch for p in list(combo)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_draw(self) -> bool:
|
||||
return len(self.possible_plays()) == 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# print(f"{self.val:b}".rjust(18, "0"))
|
||||
return "[" + "".join(ch_str(Ch(self.val >> i * CH_WIDTH & 0b11)) for i in range(9)).replace(" ", ".") + "]"
|
||||
|
||||
def print(self) -> None:
|
||||
s = [ch_str(Ch(self.val >> i * CH_WIDTH & 0b11), empty=f"\x1b[0;37m{i}\x1b[0m") for i in range(9)]
|
||||
print("#############")
|
||||
print(f"# {s[0]} | {s[1]} | {s[2]} #")
|
||||
print("#---+---+---#")
|
||||
print(f"# {s[3]} | {s[4]} | {s[5]} #")
|
||||
print("#---+---+---#")
|
||||
print(f"# {s[6]} | {s[7]} | {s[8]} #")
|
||||
print("#############")
|
||||
|
||||
START_WEIGHT = 20
|
||||
REWARD = 1
|
||||
PUNUSHMENT = 1
|
||||
|
||||
class AiPlayer:
|
||||
def __init__(self, ch: Ch) -> None:
|
||||
self.choices: dict[int, int] = {}
|
||||
self.current_choices: list[int] = []
|
||||
self.ch = ch
|
||||
|
||||
def clear_choices(self):
|
||||
self.current_choices = []
|
||||
|
||||
def reward(self):
|
||||
for choice in self.current_choices:
|
||||
self.choices[choice] += REWARD
|
||||
|
||||
def punish(self):
|
||||
for choice in self.current_choices:
|
||||
self.choices[choice] -= PUNUSHMENT
|
||||
|
||||
def interned_choice(self, choice: Board) -> Board:
|
||||
for _ in range(2):
|
||||
for _ in range(4):
|
||||
key = choice.key()
|
||||
if key in self.choices:
|
||||
return choice
|
||||
choice.rotate()
|
||||
choice.flip()
|
||||
key = choice.key()
|
||||
self.choices[key] = START_WEIGHT
|
||||
return choice
|
||||
|
||||
def make_play(self, board: Board) -> None:
|
||||
possible_choices = [(idx, board.with_play(self.ch, idx)) for idx in board.possible_plays()]
|
||||
candiate_weigth = 0
|
||||
candidate_idcs: list[tuple[int, int]] = []
|
||||
for idx, choice in possible_choices:
|
||||
choice = self.interned_choice(choice)
|
||||
key = choice.key()
|
||||
if self.choices[key] > candiate_weigth:
|
||||
candiate_weigth = self.choices[key]
|
||||
candidate_idcs = [(idx, key)]
|
||||
elif self.choices[key] == candiate_weigth:
|
||||
candidate_idcs.append((idx, key))
|
||||
(choice_idx, choice_key) = candidate_idcs[0]
|
||||
self.current_choices.append(choice_key)
|
||||
board.play(self.ch, choice_idx)
|
||||
|
||||
def save_to_file(self):
|
||||
with open("ai.json", "w") as file:
|
||||
file.write(json.dumps(self.choices))
|
||||
|
||||
def load_from_file(self):
|
||||
with open("ai.json", "r") as file:
|
||||
vs = json.loads(file.read())
|
||||
self.choices = {}
|
||||
for key in vs:
|
||||
self.choices[int(key)] = vs[key]
|
||||
|
||||
class Game:
|
||||
def __init__(self) -> None:
|
||||
self.current_plays = []
|
||||
self.weights = {}
|
||||
|
||||
|
||||
p0 = AiPlayer(Ch.Cross)
|
||||
|
||||
games = 0
|
||||
|
||||
while True:
|
||||
print(f"\n\nGame #{games}")
|
||||
board = Board()
|
||||
p0.clear_choices()
|
||||
while True:
|
||||
print("AI's turn")
|
||||
p0.make_play(board)
|
||||
board.print()
|
||||
if board.player_has_won(Ch.Cross):
|
||||
print("AI won!")
|
||||
print("Rewarding AI...")
|
||||
p0.reward()
|
||||
break
|
||||
print("Your turn (0..8)")
|
||||
if board.is_draw():
|
||||
print("Draw!")
|
||||
break
|
||||
possible_choices = board.possible_plays()
|
||||
should_restart = False
|
||||
while True:
|
||||
text = input("> ")
|
||||
if text == ".save":
|
||||
p0.save_to_file()
|
||||
continue
|
||||
elif text == ".load":
|
||||
p0.load_from_file()
|
||||
choice = 0
|
||||
should_restart = True
|
||||
continue
|
||||
elif text == ".restart":
|
||||
choice = 0
|
||||
should_restart = True
|
||||
break
|
||||
choice = int(text)
|
||||
if choice not in possible_choices:
|
||||
print("invalid choice")
|
||||
else:
|
||||
break
|
||||
if should_restart:
|
||||
break
|
||||
board.play(Ch.Circle, choice)
|
||||
board.print()
|
||||
if board.player_has_won(Ch.Circle):
|
||||
print("Player won!")
|
||||
print("Punishing AI...")
|
||||
p0.punish()
|
||||
break
|
||||
games += 1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user