def chanceOpponentHasProtection(self, team, attack): # Chance that a particular opponent has protection from a particular attack in their hand. safety = Cards.attackToSafety(attack) remedy = Cards.attackToRemedy(attack) if safety in team.safeties: self.debug("Team %d has already played safety v. %s", team.number, Cards.cardToString(attack)) return 1.0 # Odds based on number of the card lurking out there somewhere. odds = self.percentOfCardsRemaining(safety, remedy) # Boost likelihood by 50% for each remedy they've discarded. remedyDiscards = 0 for player in team.playerNumbers: for _ in xrange(self.interestingRemedyDiscardsByPlayer[player][remedy]): remedyDiscards += 1 odds *= self.__class__.constants.remedy_discard_boost self.debug("Team %d protection odds %r v. %s, based on %d safety %d remedy %d discards.", team.number, odds, Cards.cardToString(attack), self.cardsUnseen[safety], self.cardsUnseen[remedy], remedyDiscards) return odds
def chanceOpponentHasProtection(self, team, attack): # Chance that a particular opponent has protection from a particular attack in their hand. safety = Cards.attackToSafety(attack) remedy = Cards.attackToRemedy(attack) if safety in team.safeties: self.debug("Team %d has already played safety v. %s", team.number, Cards.cardToString(attack)) return 1.0 # Odds based on number of the card lurking out there somewhere. odds = self.percentOfCardsRemaining(safety, remedy) # Boost likelihood by 50% for each remedy they've discarded. remedyDiscards = 0 for player in team.playerNumbers: for _ in xrange( self.interestingRemedyDiscardsByPlayer[player][remedy]): remedyDiscards += 1 odds *= self.__class__.constants.remedy_discard_boost self.debug( "Team %d protection odds %r v. %s, based on %d safety %d remedy %d discards.", team.number, odds, Cards.cardToString(attack), self.cardsUnseen[safety], self.cardsUnseen[remedy], remedyDiscards) return odds
def __str__(self): ret = "Team " + str(self.number) + ': ' + str(self.mileage) + " miles\n" if self.needRemedy != None: ret += ' Needs ' + Cards.cardToString(self.needRemedy) + "\n" if self.moving: ret += " Moving\n" if self.speedLimit: ret += " Under a speed limit\n" for safety in self.safeties: ret += ' ' + Cards.cardToString(safety) + "\n" return ret
def __str__(self): ret = "Team " + str(self.number) + ': ' + str( self.mileage) + " miles\n" if self.needRemedy != None: ret += ' Needs ' + Cards.cardToString(self.needRemedy) + "\n" if self.moving: ret += " Moving\n" if self.speedLimit: ret += " Under a speed limit\n" for safety in self.safeties: ret += ' ' + Cards.cardToString(safety) + "\n" return ret
def cardValue(self, card, cardIdx, cards): # cardIdx and cards let us disambiguate between two equal cards in our hand. # # All equally worthless: # * Safeties in play or elsewhere in our hand # * Remedies for safeties in play or elsewhere in our hand # * 200mi if we've already maxed out # * Mileage > distance remaining (assuming extension will be played) # TODO: ...but what if an extension *won't* be played? # * Safeties we have in our hand # How many of this card do we already have in our hand? numDuplicates = len([c for c in cards if c == card]) # Make this card less valuable if we have more if it on our hand. # The obvious thing to do is a straight divisor: # If we have 2 dupes, value each at 1/2; if 3, value each at 1/3. # But that's too severe, having 2 200km is more valuable than having # 1 200km! So, we take the "duplicate fraction", 1/numDuplicates, # and we want to scale down *the inverse of that* to bring it # nearer to 1 (to reduce the severity of the penalty), and then # invert again to cancel out the inversion. dupeFrac = 1 / numDuplicates dupePenaltyFactor = self.__class__.constants.dupe_penalty_factor dupeCoefficient = 1 - (1 - dupeFrac) / dupePenaltyFactor cardType = Cards.cardToType(card) if cardType == Cards.MILEAGE: mileage = Cards.cardToMileage(card) mileageRemaining = 1000 - self.gameState.us.mileage if mileage > mileageRemaining: return 0.0 * dupeCoefficient elif mileage == 200 and self.gameState.us.twoHundredsPlayed >= 2: return 0.0 * dupeCoefficient elif mileage == 25 and cards.index(card) == cardIdx: # Try to hold onto a single 25km card in case we need it to finish. return 1.0 * dupeCoefficient else: return self.mileageCardValue(card) * dupeCoefficient elif cardType == Cards.REMEDY: # Attacks that could necessitate this card. if card == Cards.REMEDY_GO: relevantAttacks = Cards.ATTACKS[:] else: relevantAttacks = [Cards.remedyToAttack(card)] relevantAttacks = [ c for c in relevantAttacks if not Cards.attackToSafety(c) in self.gameState.us.safeties + cards ] # Factor in: # 1. Value of taking a turn. # 2. Likelihood of getting hit with a relevant attack. # (Number of attacks remaining / number of teams in game.) # NB: If no attacks are relevant, this will be 0. # TODO: Also add likelihood of drawing another remedy (or the safety.) turnPoints = self.expectedTurnPoints(self.gameState.us) turnPointValue = self.valueOfPoints(turnPoints, self.gameState.us) # Hold onto at least one of the card if we already know we need it! if (cards.index(card) == cardIdx and ((not self.gameState.us.moving and card == Cards.REMEDY_GO) or (self.gameState.us.speedLimit and card == Cards.REMEDY_END_OF_LIMIT) or (self.gameState.us.needRemedy and card == self.gameState.us.needRemedy))): self.debug("We need %s, attack on us odds == 1.0!", Cards.cardToString(card)) attackOnUsOdds = 1.0 else: attackOdds = self.percentOfCardsRemaining( *relevantAttacks) * self.expectedTurnsLeft() attackOnUsOdds = attackOdds / (len(self.gameState.opponents) + 1) value = turnPointValue * attackOnUsOdds self.debug( "Card %s val: %r = %r * %r = val(%d) * pctLeft(%s)/#teams", Cards.cardToString(card), value, turnPointValue, attackOnUsOdds, turnPoints, ",".join([Cards.cardToString(c) for c in relevantAttacks])) return value * dupeCoefficient elif cardType == Cards.SAFETY: # Never discard a safety! # (Assuming default deck composition of 1 of each type...) return 1.0 * dupeCoefficient elif cardType == Cards.ATTACK: safety = Cards.attackToSafety(card) remedy = Cards.attackToRemedy(card) valuesPerTarget = [] for target in self.gameState.opponents: valuesPerTarget.append( (1 - self.chanceOpponentHasProtection(target, card)) * (1 - self.percentOfCardsRemaining(safety, remedy)) * self.valueOfPoints(self.expectedTurnPoints(target), target)) return sum(valuesPerTarget) / len( valuesPerTarget) * dupeCoefficient else: raise Exception("Unknown card type for %r: %r" % (card, cardType))
def cardSeen(self, card): self.cardsUnseen[card] -= 1 self.numCardsUnseen -= 1 self.debug("After seeing %s, unseen cards:\n%s", Cards.cardToString(card), self.unseenCardsToString)
readTranscript = None writeTranscript = None #readTranscript = "/tmp/millegame" #writeTranscript = "/tmp/millegame" # End configurable parameters if readTranscript: reader = TranscriptReader( readTranscript, lambda *args: stdout.write("Transcript game start!\n"), lambda *args: stdout.write("Transcript hand start!\n"), lambda game, player, drawnCard, move: stdout.write( "Transcript move: %d drew %s %s\n" % ( player, Cards.cardToString(drawnCard) if drawnCard else "nothing", move)), lambda *args: stdout.write("Transcript hand end!\n"), lambda game: stdout.write("Transcript game end! Scores: %s\n" % ( " ".join(map(lambda team: "%d" % team.totalScore, game.teams))))) reader.read(debug = True) exit(0) players = [] competitor = 0 for i in range(numPlayers): players.append(competitors[competitor]()) competitor += 1 if competitor >= len(competitors): competitor = 0
def cardValue(self, card, cardIdx, cards): # cardIdx and cards let us disambiguate between two equal cards in our hand. # # All equally worthless: # * Safeties in play or elsewhere in our hand # * Remedies for safeties in play or elsewhere in our hand # * 200mi if we've already maxed out # * Mileage > distance remaining (assuming extension will be played) # TODO: ...but what if an extension *won't* be played? # * Safeties we have in our hand # How many of this card do we already have in our hand? numDuplicates = len([c for c in cards if c == card]) # Make this card less valuable if we have more if it on our hand. # The obvious thing to do is a straight divisor: # If we have 2 dupes, value each at 1/2; if 3, value each at 1/3. # But that's too severe, having 2 200km is more valuable than having # 1 200km! So, we take the "duplicate fraction", 1/numDuplicates, # and we want to scale down *the inverse of that* to bring it # nearer to 1 (to reduce the severity of the penalty), and then # invert again to cancel out the inversion. dupeFrac = 1/numDuplicates dupePenaltyFactor = self.__class__.constants.dupe_penalty_factor dupeCoefficient = 1-(1-dupeFrac)/dupePenaltyFactor cardType = Cards.cardToType(card) if cardType == Cards.MILEAGE: mileage = Cards.cardToMileage(card) mileageRemaining = 1000 - self.gameState.us.mileage if mileage > mileageRemaining: return 0.0 * dupeCoefficient elif mileage == 200 and self.gameState.us.twoHundredsPlayed >= 2: return 0.0 * dupeCoefficient elif mileage == 25 and cards.index(card) == cardIdx: # Try to hold onto a single 25km card in case we need it to finish. return 1.0 * dupeCoefficient else: return self.mileageCardValue(card) * dupeCoefficient elif cardType == Cards.REMEDY: # Attacks that could necessitate this card. if card == Cards.REMEDY_GO: relevantAttacks = Cards.ATTACKS[:] else: relevantAttacks = [Cards.remedyToAttack(card)] relevantAttacks = [c for c in relevantAttacks if not Cards.attackToSafety(c) in self.gameState.us.safeties + cards] # Factor in: # 1. Value of taking a turn. # 2. Likelihood of getting hit with a relevant attack. # (Number of attacks remaining / number of teams in game.) # NB: If no attacks are relevant, this will be 0. # TODO: Also add likelihood of drawing another remedy (or the safety.) turnPoints = self.expectedTurnPoints(self.gameState.us) turnPointValue = self.valueOfPoints(turnPoints, self.gameState.us) # Hold onto at least one of the card if we already know we need it! if (cards.index(card) == cardIdx and ((not self.gameState.us.moving and card == Cards.REMEDY_GO) or (self.gameState.us.speedLimit and card == Cards.REMEDY_END_OF_LIMIT) or (self.gameState.us.needRemedy and card == self.gameState.us.needRemedy))): self.debug("We need %s, attack on us odds == 1.0!", Cards.cardToString(card)) attackOnUsOdds = 1.0 else: attackOdds = self.percentOfCardsRemaining(*relevantAttacks) * self.expectedTurnsLeft() attackOnUsOdds = attackOdds / (len(self.gameState.opponents) + 1) value = turnPointValue * attackOnUsOdds self.debug("Card %s val: %r = %r * %r = val(%d) * pctLeft(%s)/#teams", Cards.cardToString(card), value, turnPointValue, attackOnUsOdds, turnPoints, ",".join([Cards.cardToString(c) for c in relevantAttacks])) return value * dupeCoefficient elif cardType == Cards.SAFETY: # Never discard a safety! # (Assuming default deck composition of 1 of each type...) return 1.0 * dupeCoefficient elif cardType == Cards.ATTACK: safety = Cards.attackToSafety(card) remedy = Cards.attackToRemedy(card) valuesPerTarget = [] for target in self.gameState.opponents: valuesPerTarget.append( (1-self.chanceOpponentHasProtection(target, card)) * (1-self.percentOfCardsRemaining(safety, remedy)) * self.valueOfPoints(self.expectedTurnPoints(target), target)) return sum(valuesPerTarget)/len(valuesPerTarget) * dupeCoefficient else: raise Exception("Unknown card type for %r: %r" % (card, cardType))
debug = False readTranscript = None writeTranscript = None #readTranscript = "/tmp/millegame" #writeTranscript = "/tmp/millegame" # End configurable parameters if readTranscript: reader = TranscriptReader( readTranscript, lambda *args: stdout.write("Transcript game start!\n"), lambda *args: stdout.write("Transcript hand start!\n"), lambda game, player, drawnCard, move: stdout.write( "Transcript move: %d drew %s %s\n" % (player, Cards.cardToString(drawnCard) if drawnCard else "nothing", move)), lambda *args: stdout.write("Transcript hand end!\n"), lambda game: stdout.write("Transcript game end! Scores: %s\n" % ( " ".join(map(lambda team: "%d" % team.totalScore, game.teams))))) reader.read(debug=True) exit(0) players = [] competitor = 0 for i in range(numPlayers): players.append(competitors[competitor]()) competitor += 1 if competitor >= len(competitors): competitor = 0