Created
December 2, 2012 01:05
-
-
Save bonnici/4186279 to your computer and use it in GitHub Desktop.
Quick-and-Dirty Brute Force Risk vs Reward bot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Quick-and-Dirty Brute Force Risk vs Reward bot | |
# Before each roll (except the first), do a brute force calculation of risk vs reward. In this case, | |
# risk is the probability of having 3 shotgun dice after the roll, and reward is the probability of | |
# getting an extra 1, 2, or 3 brains. If P(death) - rewardBonus[1]*P(1 brain) - | |
# rewardBonus[2]*P(2 brain) - rewardBonus[3]*P(3 brain) > rvrCutoff, then roll. | |
# If behindBonus and/or aheadPenalty are not zero, then those amounts are added to or subtracted from | |
# the risk vs reward depending on weather the bot is winning or losing, encouraging risky play when | |
# behind and safer play when ahead | |
# Based purely on trial-and-error, the following configurations seem to work best: | |
# ZombieBot_QaDBFRvR('QaDBFRvR A', 0.125, [0.11,0.11,0.11], 0.025, 0), | |
# ZombieBot_QaDBFRvR('QaDBFRvR B', 0.19, [0.1,0.15,0.2], 0, 0.1), | |
# ZombieBot_QaDBFRvR('QaDBFRvR C', 0.19, [0.1,0.1,0.1], 0, 0.1), | |
class ZombieBot_QaDBFRvR(object): | |
def __init__(self, name, rvrCutoff, rewardBonus, behindBonus, aheadPenalty): | |
self.name = name | |
self.rvrCutoff = rvrCutoff | |
self.rewardBonus = rewardBonus | |
self.behindBonus = behindBonus | |
self.aheadPenalty = aheadPenalty | |
# Precalcualted roll probabilities, given colour and face | |
self.rollProbabilities = { | |
GREEN: {BRAINS: 0.5, FOOTSTEPS: 1.0/3, SHOTGUN: 1.0/6}, | |
YELLOW: {BRAINS: 1.0/3, FOOTSTEPS: 1.0/3, SHOTGUN: 1.0/3}, | |
RED: {BRAINS: 1.0/6, FOOTSTEPS: 1.0/3, SHOTGUN: 0.5} | |
} | |
def initState(self): | |
self.diceLeft = {GREEN: 6, YELLOW: 4, RED: 3} | |
self.shotguns = 0 | |
self.footsteps = [] # list of dice colours | |
self.brains = [] # list of dice colours | |
self.rollNumber = 1 | |
def updateState(self, results): | |
oldFootsteps = self.footsteps[:] | |
self.footsteps = [] | |
for i in results: | |
if i[ICON] == SHOTGUN: | |
self.shotguns += 1 | |
if i[ICON] == FOOTSTEPS: | |
self.footsteps += [i[COLOR]] | |
if i[ICON] == BRAINS: | |
self.brains += [i[COLOR]] | |
self.diceLeft[i[COLOR]] -= 1 | |
# "Add back" the old set of footsteps, since they were already in our hand and were not | |
# taken out of the dice jar | |
for colour in oldFootsteps: | |
self.diceLeft[colour] += 1 | |
def printState(self, gameState): | |
print('%s round %d: Shotguns: %d, Footsteps: %s, Dice Left: G=%d, Y=%d, R=%d' % ( | |
self.name, gameState['round'], self.shotguns, self.footsteps, | |
self.diceLeft[GREEN], self.diceLeft[YELLOW], self.diceLeft[RED])) | |
def putBackBrainDice(self): | |
for colour in self.brains: | |
self.diceLeft[colour] += 1 | |
self.brains = [] | |
# Generate a list of all possible combinations of the colours of dice that can come out of a roll. | |
# This only calculates the combinations of the dice left over when you take into account the | |
# footstep dice that are left over from the last roll | |
def generateColourCombinations(self): | |
dice = [GREEN]*self.diceLeft[GREEN] + [YELLOW]*self.diceLeft[YELLOW] + [RED]*self.diceLeft[RED] | |
return itertools.combinations(dice, 3 - len(self.footsteps)) | |
# No indices because I'm lazy, | |
# 0 = probability of having 3 or more shotguns after the roll | |
# 1 = probability of having 1 additional brain after the roll | |
# 2 = probability of having 2 additional brains after the roll | |
# 3 = probability of having 3 additional brains after the roll | |
def calculateProbabilites(self): | |
probabilities = [0, 0, 0, 0] | |
# All possible permutations of a roll with repeated elements (e.g. SSS, SSB, SBS, BSS) | |
rollPermutations = itertools.product([SHOTGUN, FOOTSTEPS, BRAINS], repeat=3) | |
# All possible combinations of dice colours | |
colourCombinations = self.generateColourCombinations() | |
# For each possible combination of dice colours, add to the probabilites | |
for combination in colourCombinations: | |
colours = self.footsteps + list(combination) | |
# Calculate the probability of the roll, given the colours | |
for roll in rollPermutations: | |
probability = 1; | |
for (colour, die) in zip(colours, roll): | |
x = self.rollProbabilities[colour][die] | |
probability *= x | |
# Chance of death | |
shotgunsInRoll = roll.count(SHOTGUN) | |
if (shotgunsInRoll + self.shotguns >= 3): | |
probabilities[0] += probability | |
# Chance of any number of brains | |
brainsInRoll = roll.count(BRAINS) | |
if (brainsInRoll > 0): | |
probabilities[brainsInRoll] += probability | |
return probabilities | |
def turn(self, gameState): | |
self.initState() | |
shouldRoll = True | |
while shouldRoll and self.shotguns < 3: | |
# Before the roll, make sure we have enough dice left over | |
numDiceLeft = self.diceLeft[GREEN] + self.diceLeft[YELLOW] + self.diceLeft[RED] | |
numDiceNeeded = 3 - len(self.footsteps) | |
if (numDiceNeeded > numDiceLeft): | |
self.putBackBrainDice() | |
results = roll() | |
self.rollNumber += 1 | |
self.updateState(results) | |
probabilities = self.calculateProbabilites() | |
risk = probabilities[0] | |
reward = (self.rewardBonus[0]*probabilities[1] + | |
self.rewardBonus[1]*probabilities[2] + | |
self.rewardBonus[2]*probabilities[3]) | |
maxScore = max(gameState[SCORES].items()) | |
myScore = gameState[SCORES][self.name] | |
if (myScore > maxScore): | |
# Add risk (play safer) | |
risk += self.aheadPenalty | |
elif (myScore < maxScore): | |
# Add reward (play riskier) | |
reward += self.behindBonus | |
shouldRoll = (risk - reward) < self.rvrCutoff |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment