Source code for tests.testruns
"""Run tests of the Nim Game automatically
:func:`play_full_game` plays one full game automatically, doing the moves for
both parties, according to their set error rates in the :class:`Nim` instance
:func:`run_many_games` creates a :class:`Nim` instance and runs several games
automatically
"""
from source.core import Nim
from source.typedefs import ErrorRate, ErrorRate_T
[docs]def play_full_game(
nim: Nim
) -> dict:
"""Play one full game
There is no interaction, both players' moves are calculated by the program.
We just call the 1st player as Computer and the 2nd as Player.
It creates random heaps first. Then, figures out whether Computer is to
start.
This decision is based on the starting nimsum on Computer's favour, but with
the Computer's error rate. Player and Computer take turns until the game
ends. Moves are calculated with the same algorithm for the Computer and
Player, but with different error rates.
Args:
nim: the :class:`Nim` instance, created with the init arguments, like
`error_rate`
Returns:
The result of the game.
Keys in the returned dict:
- step_records: a list of statuses at each move
- computer_won: a bool stating whether the "Computer" won
"""
#create new random heaps
nim.setup_heaps()
#init compturn automatically
nim.set_start()
#init the step storage
step_records = []
step_records.append(
nim.get_heapstatus() + f' before {nim.activeplayer}'
)
#do moves in a loop, until game end
while not nim.game_end():
#make a good decision?
if nim.make_good_choice():
#figure out the best move (what heap and how many coins)
move = nim.figure_out_best_move()
else:
#set a random (most probably not good) move
move = nim.get_random_move()
#do and record a move
nim.do_move(move)
step_records.append(
nim.get_heapstatus() + f' before {nim.activeplayer}'
)
#Computer won if it was its turn after the last coin was taken
step_records.append(f'{nim.activeplayer} won')
return dict(
step_records=step_records,
computer_won=(nim.activeplayer=='Computer') ^ (not nim.misere)
)
[docs]def run_many_games(
gamenum: int,
error_rate: ErrorRate_T
) -> None:
"""Run several games automatically
This function starts with creating a :class:`Nim` instance with setting the
required error rates. It then calls :func:`play_full_game` for each game. It
prints a + when the "Computer" wins, and - when the "Player" wins.
Then it prints the percentage the "Computer" won.
Args:
gamenum: the number of games to run
error_rate: the required rate of bad decisions to be made by each player
"""
#Computer's success
won_games = 0
#Computer failure details, in case it's needed
lost_games = []
#instantiate Nim
nim = Nim(
error_rate=error_rate,
)
#do several games to collect statistics
for _ in range(gamenum):
#play one game
game_result = play_full_game(nim)
#Computer win?
if game_result['computer_won']:
won_games += 1
print('+', end='')
else:
lost_games.append(game_result['step_records'])
print('-', end='')
print("\n")
if won_games == gamenum:
print(f"Computer won all the {gamenum} games, Player had no chance")
else:
if isinstance(error_rate, ErrorRate):
error_rate = error_rate.Computer
if error_rate:
print(f"Computer won {won_games/gamenum*100}%")
else:
print(
f"Computer only won {won_games/gamenum*100}%, "
"in spite its error rate was 0%, WTF?!")
for lost_game in lost_games:
print('-'*40)
for move in lost_game:
print(move)