Cached player state
[menace.git] / nim-trinket / menace.py
1 import collections
2 import random
3
4 INITIAL_GAME_SIZE = 9
5 MAX_TAKE = 3
6 INITIAL_BEAD_COUNT = 3
7
8 def new_menace():
9 boxes = {'human?': False}
10 for b in range(1, INITIAL_GAME_SIZE+1):
11 box = collections.Counter()
12 for i in range(1, MAX_TAKE+1):
13 if b >= i:
14 box[i] = INITIAL_BEAD_COUNT
15 boxes[b] = box
16 return boxes
17
18 def menace_move(box):
19 return random.choice(list(box.elements()))
20
21 def new_human():
22 return {'human?': True}
23
24 def human_move(game):
25 if game['history']:
26 print('Opponent took', game['history'][-1]['move'], 'pieces.')
27 else:
28 print('You play first.')
29 print('There are', game['tokens'], 'pieces left.')
30
31 max_move = min(MAX_TAKE, game['tokens'])
32 valid_input = False
33
34 while not valid_input:
35 user_input = input('Your move (1-{})? '.format(max_move))
36 if user_input.isnumeric():
37 move = int(user_input)
38 if move in range(1, max_move+1):
39 valid_input = True
40 else:
41 print('Number not a valid move.')
42 else:
43 print('Please enter a number.')
44 return move
45
46 def new_game(player1, player2):
47 return {'tokens': INITIAL_GAME_SIZE,
48 'player1': player1,
49 'player2': player2,
50 'player1_active': True,
51 'history': []}
52
53 def game_finished(game):
54 return game['tokens'] == 0
55
56 def winner(game):
57 if game['history'][-1]['player1?']:
58 return game['player2']
59 else:
60 return game['player1']
61
62 def loser(game):
63 if game['history'][-1]['player1?']:
64 return game['player1']
65 else:
66 return game['player2']
67
68
69 def make_move(game):
70 if game['player1_active']:
71 active = game['player1']
72 else:
73 active = game['player2']
74 if active['human?']:
75 move = human_move(game)
76 else:
77 move = menace_move(active[game['tokens']])
78 game['history'] += [{'player1?': game['player1_active'], 'move': move, 'tokens': game['tokens']}]
79 game['tokens'] -= move
80 game['player1_active'] = not game['player1_active']
81
82 def play_game(game):
83 while not game_finished(game):
84 make_move(game)
85
86 def winning_moves(game):
87 moves = []
88 player1_won = game['history'][-1]['player1?']
89 for m in game['history']:
90 if m['player1?'] != player1_won:
91 moves += [m]
92 return moves
93
94 def losing_moves(game):
95 moves = []
96 player1_won = game['history'][-1]['player1?']
97 for m in game['history']:
98 if m['player1?'] == player1_won:
99 moves += [m]
100 return moves
101
102 def update_winner(game):
103 player = winner(game)
104 moves = winning_moves(game)
105 for m in moves:
106 player[m['tokens']][m['move']] += 1
107
108 def update_loser(game, allow_drop_move=False):
109 player = loser(game)
110 moves = losing_moves(game)
111 for m in moves:
112 if allow_drop_move:
113 if len(list(player[m['tokens']].elements())) > 1:
114 player[m['tokens']][m['move']] -= 1
115 else:
116 if player[m['tokens']][m['move']] > 1:
117 player[m['tokens']][m['move']] -= 1
118
119
120 def game_with_players(p1, p2, report_result_for=None):
121 if random.choice([True, False]):
122 g = new_game(p1, p2)
123 else:
124 g = new_game(p2, p1)
125 play_game(g)
126 if report_result_for:
127 if winner(g) == report_result_for:
128 print('You won')
129 else:
130 print('You lost')
131 return g
132
133 def train_players(p1, p2, rounds=10000, allow_drop_move=False):
134 for _ in range(rounds):
135 g = game_with_players(p1, p2)
136 update_winner(g)
137 update_loser(g, allow_drop_move=allow_drop_move)
138 return p1, p2
139