So, there is this king-of-the-hill challenge going on at PPCG SE to which I submitted my participation. I won't change it further in terms of behaviour, so I wanted to know what you think of the code.
The challenge
It is very well described in the original post, I'll just summarize the key ideas here.
Setup
- A pile of \$C\$ unflipped coins at the center of the table;
- \$N\$ players around the table with, each, \$F_i\$ flipped coins and \$U_i\$ unflipped coins (\$i \in [0;N[\$)
Input of the program
Single argument formated following:
$$R;I;C;P_i\_S_i\_F_i\_U_i;P_i\_S_i\_F_i\_U_i;\ldots$$
Where \$R\$ is the current round, \$I\$ is the id of our player, \$P_i\$ is the id of the player being described by the \$P\_S\_F\_U\$ data and \$S_i\$ is its score.
Expected output
A 3-character string, nothing else, representing the actions of our player for this turn. The actions can be:
N
: Do Nothing
1
: Take 1 coin from the central pile [Effects: +1 local unflipped / -1 point / -1 global unflipped]
2
: Take 2 coins from the central pile [Effects: +2 local unflipped / -2 points / -2 global unflipped]
3
: Take 3 coins from the central pile [Effects: +3 local unflipped / -3 points / -3 global unflipped]
A
: Put back 1 coin from your pile [Effects: -1 local unflipped / +1 point / +1 global unflipped]
B
: Put back 2 coins from your pile [Effects: -2 local unflipped / +2 points / +2 global unflipped]
C
: Put back 3 coins from your pile [Effects: -3 local unflipped / +3 points / +3 global unflipped]
X
: Remove 1 coin from your pile [Effects: -1 local unflipped / 0 point]
Y
: Remove 2 coins from your pile [Effects: -2 local unflipped / 0 point]
Z
: Remove 3 coins from your pile [Effects: -3 local unflipped / 0 point]
R
: Rotate coins to previous player [Effects: -1 point per unflipped received, +2 points per flipped received / applies to all players]
T
: Rotate coins to next player [Effects: -1 point per unflipped received, +2 points per flipped received / applies to all players]
F
: Flip 1 coin [Effects: -1 local unflipped / +1 local flipped / +2 point]
U
: Unflip 1 coin [Effects: +1 local unflipped / -1 local flipped / -2 point]
The code
The submission tries every one of the 2744 possible move and output the (first) one with the maximum amount of points gained.
import sys
import itertools
from copy import deepcopy
MOVES_REQUIRED = 3
FLIPPED = 0
UNFLIPPED = 1
def filter_neighbors(neighbors, me, size):
limit = size - MOVES_REQUIRED
for data in neighbors:
i, _, flipped, unflipped = map(int, data.split('_'))
if MOVES_REQUIRED < (me - i) % size < limit:
continue # Skip neighbors that are too far away
yield i, [flipped, unflipped]
class Player:
def __init__(self, raw_data):
_, me, coins, *data = raw_data.split(';')
self.num_players = len(data)
self._me = int(me)
self._coins = int(coins)
self._state = dict(filter_neighbors(data, self._me, self.num_players))
def reset(self):
self.me = self._me
self.coins = self._coins
self.state = deepcopy(self._state)
self.my_state = self.state[self.me]
def invalid_move(self, move):
if move in 'NRT':
return False
if move in '123'[:self.coins]:
return False
flipped, unflipped = self.my_state
if flipped and move == 'U':
return False
if unflipped and move == 'F':
return False
if move in 'AXBYCZ'[:2 * unflipped]:
return False
return True
def N(self):
return 0
def one(self):
self.coins -= 1
self.my_state[UNFLIPPED] += 1
return -1
def two(self):
self.coins -= 2
self.my_state[UNFLIPPED] += 2
return -2
def three(self):
self.coins -= 3
self.my_state[UNFLIPPED] += 3
return -3
def A(self):
self.coins += 1
self.my_state[UNFLIPPED] -= 1
return 1
def B(self):
self.coins += 2
self.my_state[UNFLIPPED] -= 2
return 2
def C(self):
self.coins += 3
self.my_state[UNFLIPPED] -= 3
return 3
def X(self):
self.my_state[UNFLIPPED] -= 1
return 0
def Y(self):
self.my_state[UNFLIPPED] -= 2
return 0
def Z(self):
self.my_state[UNFLIPPED] -= 3
return 0
def R(self):
self.me = (self.me + 1) % self.num_players
flipped, unflipped = self.my_state = self.state[self.me]
return 2 * flipped - unflipped
def T(self):
self.me = (self.me - 1) % self.num_players
flipped, unflipped = self.my_state = self.state[self.me]
return 2 * flipped - unflipped
def F(self):
self.my_state[FLIPPED] += 1
self.my_state[UNFLIPPED] -= 1
return 2
def U(self):
self.my_state[FLIPPED] -= 1
self.my_state[UNFLIPPED] += 1
return -2
setattr(Player, '1', Player.one)
setattr(Player, '2', Player.two)
setattr(Player, '3', Player.three)
def scenarii(player):
for tries in itertools.product('N123ABCXYZRTFU', repeat=MOVES_REQUIRED):
player.reset()
points = 0
for try_ in tries:
if player.invalid_move(try_):
break
points += getattr(player, try_)()
else:
yield points, ''.join(tries)
if __name__ == '__main__':
player = Player(sys.argv[1])
print(max(scenarii(player))[1])
What do you think?