# Maximize points won by flipping coins

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?

• Yo, I noticed this is your only unaccepted question. Is there something the answer is missing that you want answered is particular? If so I may be able to help, :) – Peilonrayz Jun 26 '17 at 20:46
• @Peilonrayz It's just that, as stated in the comments, I really dislike the hit on readability of the current answer. Otherwise, I don't have any particular wish, any and all aspects, as usual ;) – 301_Moved_Permanently Jun 27 '17 at 10:33

I'm going to expound on this answer later (when I'm on my Python 3 machine). To begin with, there's a lot of repeated logic, especially in the one, two, ... C methods. You can avoid this with a class method to generate these instance methods.

class Player:

@classmethod
def make_flipper(cls, name, val):
def flipper(self):
self.coins -= val
self.my_state[UNFLIPPED] += val
return -val
setattr(cls, name, flipper)

method_dict = {
'N': 0,
'one': 1,
'two': 2,
'three': 3,
'A': -1,
'B': -2,
'C': -3
}

for name, val in method_dict.items():
make_flipper(name, val)


Note that this is untested, I'll fix it when I get home if it's broken.

• I do agree that we can do something to reduce the amount of repeated code, but I don't find this solution that readable. Plus the help on the class will be messed up, if we ever need to add docstrings... – 301_Moved_Permanently May 5 '16 at 7:25
• @MathiasEttinger I agree with the readability comment. Regarding the docstring, this can be done programmatically on the wrapper function by setting __doc__ IIRC. I'll try to think of another approach though, didn't end up having any time last night. – Jared Goguen May 5 '16 at 15:34