RPS: Initial code
This commit is contained in:
commit
d813412bd4
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "standard"
|
||||
}
|
160
main.py
Normal file
160
main.py
Normal file
@ -0,0 +1,160 @@
|
||||
from collections import defaultdict
|
||||
import random
|
||||
from typing import Callable
|
||||
|
||||
|
||||
BAT = "🦇"
|
||||
SNAKE = "🐍"
|
||||
ROCK = "🗿"
|
||||
PAPER = "🗞"
|
||||
SCISSORS = "🔪"
|
||||
CHOICES = ROCK, PAPER, SCISSORS
|
||||
|
||||
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]
|
||||
|
||||
def beats(x: str, y: str):
|
||||
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():
|
||||
counter = 0
|
||||
|
||||
player_score = 0
|
||||
adversary_score = 0
|
||||
|
||||
adversary = Adversary()
|
||||
|
||||
while True:
|
||||
print(f"== Round {counter + 1} ==")
|
||||
print(f"SCORE: {player_score}:{adversary_score}")
|
||||
print()
|
||||
print(f"{SNAKE}: Hsss!")
|
||||
player_choice = input_choice(f"{BAT}", {"R": ROCK, "P": PAPER, "S": SCISSORS})
|
||||
adversary_choice = adversary.pick()
|
||||
|
||||
if beats(player_choice, adversary_choice):
|
||||
player_score += 1
|
||||
msg = "VICTORY!"
|
||||
elif beats(adversary_choice, player_choice):
|
||||
adversary_score += 1
|
||||
msg = "DEFEAT!"
|
||||
else:
|
||||
msg = "DRAW!"
|
||||
|
||||
print()
|
||||
print(f"{BAT}: {player_choice}! {VERBS[player_choice]}")
|
||||
print(f"{SNAKE}: {adversary_choice}! {VERBS[adversary_choice]}")
|
||||
print(msg)
|
||||
print()
|
||||
|
||||
adversary.notify(player_choice, adversary_choice)
|
||||
counter += 1
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user