Improve code quality _as_ demo code
Include two adversary implementations
This commit is contained in:
239
main.py
239
main.py
@@ -1,125 +1,21 @@
|
|||||||
from collections import defaultdict
|
|
||||||
import random
|
import random
|
||||||
from typing import Callable
|
from typing import Literal
|
||||||
|
|
||||||
|
|
||||||
BAT = "🦇"
|
BAT = "🦇"
|
||||||
SNAKE = "🐍"
|
SNAKE = "🐍"
|
||||||
ROCK = "🗿"
|
|
||||||
PAPER = "🗞"
|
|
||||||
SCISSORS = "🔪"
|
|
||||||
CHOICES = ROCK, PAPER, SCISSORS
|
|
||||||
|
|
||||||
VERBS = {
|
Choice = Literal["🗿", "🗞", "🔪"]
|
||||||
ROCK: "THUD!",
|
ROCK: Choice = "🗿"
|
||||||
PAPER: "WHACK!",
|
PAPER: Choice = "🗞"
|
||||||
SCISSORS: "STAB!",
|
SCISSORS: Choice = "🔪"
|
||||||
}
|
|
||||||
|
|
||||||
def input_choice[T](prompt: str, options: dict[str, T]) -> T:
|
CHOICES: tuple[Choice, ...] = ROCK, PAPER, SCISSORS
|
||||||
hint = f"({", ".join(options.keys())})"
|
|
||||||
choice = input(f"{prompt} {hint}: ").upper()
|
|
||||||
while choice not in options:
|
|
||||||
choice = input(f"{hint}): ").upper()
|
|
||||||
return options[choice]
|
|
||||||
|
|
||||||
def beats(x: str, y: str):
|
def beats(x: Choice, y: Choice):
|
||||||
return (x, y) in {(PAPER, ROCK), (SCISSORS, PAPER), (ROCK, SCISSORS)}
|
return (x, y) in {(PAPER, ROCK), (SCISSORS, PAPER), (ROCK, SCISSORS)}
|
||||||
|
|
||||||
|
|
||||||
class Predictor(object):
|
|
||||||
def __init__(self, context_size: int, include_player_moves: bool, include_adversary_moves: bool):
|
|
||||||
self._context_size = context_size
|
|
||||||
self._include_player_moves = include_player_moves
|
|
||||||
self._include_adversary_moves = include_adversary_moves
|
|
||||||
|
|
||||||
def predict(self, history: list[tuple[str, str]]) -> dict[str, float]:
|
|
||||||
context_size = self._context_size
|
|
||||||
|
|
||||||
def _pluck_context(i: int) -> tuple[str, ...]:
|
|
||||||
raw_context = history[i:i + context_size]
|
|
||||||
context_items: list[str] = []
|
|
||||||
for player, adversary in raw_context:
|
|
||||||
if self._include_player_moves:
|
|
||||||
context_items.append(player)
|
|
||||||
if self._include_player_moves:
|
|
||||||
context_items.append(adversary)
|
|
||||||
|
|
||||||
return tuple(context_items)
|
|
||||||
|
|
||||||
observations: dict[str, int] = {c: 1 for c in CHOICES}
|
|
||||||
|
|
||||||
final_context = _pluck_context(len(history) - context_size)
|
|
||||||
# print(f"final context {self._context_size, self._include_player_moves, self._include_adversary_moves}: {final_context}")
|
|
||||||
# print(f"history: {history} context size: {context_size}")
|
|
||||||
|
|
||||||
for i in range(len(history) - context_size):
|
|
||||||
context = _pluck_context(i)
|
|
||||||
observation, _ = history[i + context_size]
|
|
||||||
# print(f"context: {context} final context: {final_context} observation: {observation}")
|
|
||||||
if context == final_context:
|
|
||||||
observations[observation] += 1
|
|
||||||
|
|
||||||
sum_count = 0
|
|
||||||
for r in CHOICES:
|
|
||||||
sum_count += observations[r]
|
|
||||||
|
|
||||||
return {
|
|
||||||
r: observations[r] / sum_count
|
|
||||||
for r in CHOICES
|
|
||||||
}
|
|
||||||
|
|
||||||
class Adversary(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._history: list[tuple[str, str]] = []
|
|
||||||
|
|
||||||
def notify(self, player: str, adversary: str):
|
|
||||||
self._history.append((player, adversary))
|
|
||||||
|
|
||||||
def pick(self):
|
|
||||||
player_probability = self.predict()
|
|
||||||
expected_value = {c: 0.0 for c in CHOICES}
|
|
||||||
for adversary in CHOICES:
|
|
||||||
for player in CHOICES:
|
|
||||||
expected_value[adversary] += (
|
|
||||||
1.0 if beats(adversary, player) else
|
|
||||||
-1.0 if beats(player, adversary) else
|
|
||||||
0.0
|
|
||||||
) * player_probability[player]
|
|
||||||
print(f"SNAKE PREDICTIONS: {player_probability}")
|
|
||||||
print(f"SNAKE EV: {expected_value}")
|
|
||||||
max_expected_value = max(expected_value.values())
|
|
||||||
best_choices = [c for c, v in expected_value.items() if v == max_expected_value]
|
|
||||||
return random.choice(best_choices)
|
|
||||||
|
|
||||||
def predict(self) -> dict[str, float]:
|
|
||||||
def _merge(tables: list[dict[str, float]]):
|
|
||||||
out = {}
|
|
||||||
for c in CHOICES:
|
|
||||||
sum_ = 0.0
|
|
||||||
for t in tables:
|
|
||||||
sum_ += t[c]
|
|
||||||
sum_ /= len(tables)
|
|
||||||
out[c] = sum_
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
tables = []
|
|
||||||
for p in [
|
|
||||||
Predictor(0, include_player_moves=False, include_adversary_moves=False),
|
|
||||||
Predictor(1, include_player_moves=True, include_adversary_moves=False),
|
|
||||||
Predictor(1, include_player_moves=False, include_adversary_moves=True),
|
|
||||||
Predictor(1, include_player_moves=True, include_adversary_moves=True),
|
|
||||||
# Predictor(2, include_player_moves=True, include_adversary_moves=False),
|
|
||||||
# Predictor(2, include_player_moves=False, include_adversary_moves=True),
|
|
||||||
# Predictor(2, include_player_moves=True, include_adversary_moves=True),
|
|
||||||
Predictor(1, include_player_moves=False, include_adversary_moves=True),
|
|
||||||
]:
|
|
||||||
tables.append(p.predict(self._history))
|
|
||||||
|
|
||||||
return _merge(tables)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
||||||
@@ -133,8 +29,8 @@ def main():
|
|||||||
print(f"SCORE: {player_score}:{adversary_score}")
|
print(f"SCORE: {player_score}:{adversary_score}")
|
||||||
print()
|
print()
|
||||||
print(f"{SNAKE}: Hsss!")
|
print(f"{SNAKE}: Hsss!")
|
||||||
player_choice = input_choice(f"{BAT}", {"R": ROCK, "P": PAPER, "S": SCISSORS})
|
player_choice: Choice = input_choice(f"{BAT}", {"R": ROCK, "P": PAPER, "S": SCISSORS})
|
||||||
adversary_choice = adversary.pick()
|
adversary_choice: Choice = adversary.pick()
|
||||||
|
|
||||||
if beats(player_choice, adversary_choice):
|
if beats(player_choice, adversary_choice):
|
||||||
player_score += 1
|
player_score += 1
|
||||||
@@ -155,6 +51,123 @@ def main():
|
|||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
|
|
||||||
|
VERBS = {
|
||||||
|
ROCK: "THUD!",
|
||||||
|
PAPER: "WHACK!",
|
||||||
|
SCISSORS: "STAB!",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def input_choice[T](prompt: str, options: dict[str, T]) -> T:
|
||||||
|
hint = f"({", ".join(options.keys())})"
|
||||||
|
choice = input(f"{prompt} {hint}: ").upper()
|
||||||
|
while choice not in options:
|
||||||
|
choice = input(f"{hint}: ").upper()
|
||||||
|
return options[choice]
|
||||||
|
|
||||||
|
|
||||||
|
class Adversary(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._history: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def notify(self, player: str, adversary: str):
|
||||||
|
self._history.append((player, adversary))
|
||||||
|
|
||||||
|
def pick(self) -> Choice:
|
||||||
|
return random.choice(CHOICES)
|
||||||
|
|
||||||
|
|
||||||
|
class Predictor(object):
|
||||||
|
def __init__(self, context_size: int, include_player_moves: bool, include_adversary_moves: bool):
|
||||||
|
self._context_size = context_size
|
||||||
|
self._include_player_moves = include_player_moves
|
||||||
|
self._include_adversary_moves = include_adversary_moves
|
||||||
|
|
||||||
|
def predict(self, history: list[tuple[str, str]]) -> dict[str, float]:
|
||||||
|
context_size = self._context_size
|
||||||
|
|
||||||
|
context_size_in_items = context_size
|
||||||
|
if self._include_player_moves and self._include_adversary_moves:
|
||||||
|
context_size_in_items /= 2
|
||||||
|
|
||||||
|
def _pluck_context(i: int) -> str:
|
||||||
|
raw_context = history[i:i + context_size]
|
||||||
|
context: str = ""
|
||||||
|
for player, adversary in raw_context:
|
||||||
|
if self._include_player_moves:
|
||||||
|
context += player
|
||||||
|
if self._include_adversary_moves:
|
||||||
|
context += adversary
|
||||||
|
|
||||||
|
return context[-self._context_size:]
|
||||||
|
|
||||||
|
observations: dict[str, int] = {c: 1 for c in CHOICES}
|
||||||
|
|
||||||
|
final_context = _pluck_context(len(history) - context_size)
|
||||||
|
|
||||||
|
for i in range(len(history) - context_size):
|
||||||
|
context = _pluck_context(i)
|
||||||
|
observation, _ = history[i + context_size]
|
||||||
|
if context == final_context:
|
||||||
|
observations[observation] += 1
|
||||||
|
|
||||||
|
sum_count = 0
|
||||||
|
for r in CHOICES:
|
||||||
|
sum_count += observations[r]
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: observations[r] / sum_count
|
||||||
|
for r in CHOICES
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Adversary(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._history: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def notify(self, player: str, adversary: str):
|
||||||
|
self._history.append((player, adversary))
|
||||||
|
|
||||||
|
def pick(self) -> Choice:
|
||||||
|
player_probability = self.predict()
|
||||||
|
expected_value: dict[Choice, float] = {c: 0.0 for c in CHOICES}
|
||||||
|
for adversary in CHOICES:
|
||||||
|
for player in CHOICES:
|
||||||
|
expected_value[adversary] += (
|
||||||
|
1.0 if beats(adversary, player) else
|
||||||
|
-1.0 if beats(player, adversary) else
|
||||||
|
0.0
|
||||||
|
) * player_probability[player]
|
||||||
|
print(f"SNAKE PREDICTIONS: {player_probability}")
|
||||||
|
print(f"SNAKE EV: {expected_value}")
|
||||||
|
max_expected_value = max(expected_value.values())
|
||||||
|
best_choices: list[Choice] = [c for c, v in expected_value.items() if v == max_expected_value]
|
||||||
|
return random.choice(best_choices)
|
||||||
|
|
||||||
|
def predict(self) -> dict[Choice, float]:
|
||||||
|
def _merge(tables: list[dict[Choice, float]]):
|
||||||
|
out = {}
|
||||||
|
for c in CHOICES:
|
||||||
|
sum_ = 0.0
|
||||||
|
for t in tables:
|
||||||
|
sum_ += t[c]
|
||||||
|
sum_ /= len(tables)
|
||||||
|
out[c] = sum_
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
tables = []
|
||||||
|
for p in [
|
||||||
|
Predictor(0, include_player_moves=False, include_adversary_moves=False),
|
||||||
|
Predictor(1, include_player_moves=True, include_adversary_moves=False),
|
||||||
|
Predictor(1, include_player_moves=False, include_adversary_moves=True),
|
||||||
|
Predictor(2, include_player_moves=True, include_adversary_moves=True),
|
||||||
|
]:
|
||||||
|
tables.append(p.predict(self._history))
|
||||||
|
|
||||||
|
return _merge(tables)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
Reference in New Issue
Block a user