Three Number Monte
The Rules
In this game, each round, every player faces off against every other player in a round robin format. In each match, players pick 3 positive integers that sum to 360. Let's say player 1 picks [a, b, c]
and player 2 picks [x, y, z]
Now a
is compared to x
, b
is compared to y
, and c
is compared to z
. If a > x
, player 1 gets a tally, and vice versa, for all 3 numbers.
If either player gets at least 2 tallies, they win the match and receive 1 point - otherwise, both players receive half a point.
To make this game more interesting, each match, the players get a data
object that they can use for memory, and also a history
object that contains all of their opponent's previous choices.
Submission
Write a Python function that takes in two arguments, data
and history
, described above, and returns an array of positive integers that sums to 360. You can find some example players in the driver code.
Driver
import random, itertools, math
def onethird(data, history):
a = [120, 120, 120]
random.shuffle(a)
return a
def onehalf(data, history):
a = [180, 180, 0]
random.shuffle(a)
return a
def rand(data, history):
x = random.randint(0, 360)
y = random.randint(0, 360 - x)
return [x, y, 360 - x - y]
players = [onethird, onehalf, rand]
#Game logic
ROUNDS = 10000
pairs = list(itertools.combinations(players, 2))
data = {}
history = {}
scores = {}
for p in players:
data[p] = {}
history[p] = []
scores[p] = 0
for _ in range(ROUNDS):
random.shuffle(pairs)
for pair in pairs:
p1, p2 = pair
a1 = p1(data[p1], history[p2])
a2 = p2(data[p2], history[p1])
assert sum(a1) == 360, (p1, a1)
assert sum(a2) == 360, (p2, a2)
assert all(isinstance(i, int) for i in a1), (p1, a1)
assert all(isinstance(i, int) for i in a2), (p2, a2)
assert all(0 <= i <= 360 for i in a1), (p1, a1)
assert all(0 <= i <= 360 for i in a2), (p2, a2)
history[p1].append(a1)
history[p2].append(a2)
p1c, p2c = 0, 0
for i in range(3):
if a1[i] > a2[i]:
p1c += 1
elif a2[i] > a1[i]:
p2c += 1
if p1c >= 2:
scores[p1] += 1
elif p2c >= 2:
scores[p2] += 1
else:
scores[p1] += 0.5
scores[p2] += 0.5
print(scores)
The tentative deadline for this competition is 8/16/21 00:00 UTC.
2 answers
The "Uppers"
def oneup(data, history):
if len(history) > 0:
prev = history[-1]
if prev[2] > 1:
better = [prev[0] + 1, prev[1] + 1, prev[2] - 2]
elif prev[1] > 1:
better = [prev[0] + 1, prev[1] - 2, prev[2] + 1]
else:
better = [prev[0] - 2, prev[1] + 1, prev[2] + 1]
random.shuffle(better)
return better
return [[180, 180, 0], [180, 0, 180], [0, 180, 180]][random.randint(0, 2)]
def avgup(data, history):
if 't1' not in data:
data['t1'] = 0
data['t2'] = 0
data['t3'] = 0
if len(history) > 0:
prev = history[-1]
prev.sort()
data['t1'] += prev[0]
data['t2'] += prev[1]
data['t3'] += prev[2]
a1 = data['t1'] / len(history)
a2 = data['t2'] / len(history)
a3 = data['t3'] / len(history)
if math.ceil(a1) + math.ceil(a2) <= 360:
better = [math.ceil(a1), math.ceil(a2), 360 - math.ceil(a1) - math.ceil(a2)]
else:
better = [math.ceil(a1), 360 - math.ceil(a1), 0]
random.shuffle(better)
return better
return [[180, 180, 0], [180, 0, 180], [0, 180, 180]][random.randint(0, 2)]
Two bots based off a single concept that seem to perform reasonably well. One takes the last submission the opponent made, and "one-ups" it - increments 2 values by 1 and decrements 1 value by 2. The other does the same thing, except with the average of all previous submissions by the opponent.
0 comment threads
MoshiBot
from numpy.polynomial import Polynomial
def MoshiBot(data, history):
if len(history) < 10:
return [180, 180, 0]
lastmin = min(history[-1])
lastmid = sorted(history[-1])[1]
projectedmin = lastmin
projectedmid = lastmid
if len(history) >= 10:
cutoff = len(history) - 10
snapshot = history[cutoff:]
minvalues = list(map(min, snapshot))
midvalues = list(map(lambda vals: sorted(vals)[1], snapshot))
xpoints = range(cutoff, cutoff + len(snapshot))
minprojection = Polynomial.fit(xpoints, minvalues, deg=2)
midprojection = Polynomial.fit(xpoints, midvalues, deg=2)
projectedmin = minprojection(len(history))
projectedmid = midprojection(len(history))
safemin = max(0, min(360, math.ceil(projectedmin + 1)))
if safemin >= 180:
# The model broke down
return [180, 180, 0]
mid = math.floor(180 - safemin / 2)
if mid > projectedmid:
return [safemin, mid, 360 - safemin - mid]
else:
mid = max(safemin, min(360 - safemin, math.ceil(projectedmid + 1)))
return [safemin, mid, 360 - safemin - mid]
Attempts to predict the opponents next move using a regressive model.
0 comment threads