Dungeon of botdom

DevilWorshipper

My first attempt at a KOTH challenge:

from base import BasePlayer
#from random import randint

class DevilWorshipper(BasePlayer):
    def reset(self):
        self.items = [0, 1, 2, 3, 4, 5]
        self.turns = 0
        self.demon = False
        self.dragon = False

    def __init__(self):
        self.reset()

    def start_turn(self, last_turn):
        if last_turn in self.items:
            self.items.remove(last_turn)

        if last_turn is not None:
            #self.demon = True if randint(1, 13 - self.turns) <= 2 else False
            self.turns += 1

        if (((self.demon == True and not (0 in self.items)) or (self.dragon == True)) and not (3 in self.items)):
            return 0
        if (len(self.items) <= 1):
            return 0
        return 1

    def play(self, card):
        self.turns += 1

        if (card == 9):
            self.dragon = True
            return 6

        if (card == 7):
            self.demon = True
            return 6

        for i in [3, 0, 2, 1, 5, 4]:
            if (i in self.items):
                self.items.remove(i)
                return i

        return 6

    def vorpal_choice(self, last_turn):
        return 5 #If it works for others maybe it will work for us

    def result(self, bot, result, dungeon, vorped):
        self.reset()

Essentially, we get rid of the pact and the vorpal dagger, wait for the demon to get into the deck, and pass. Every round the opponent may have drawn the demon, it has the % chance that the last card the opponent drew was a demon to just assume they played the demon already.

Let me know if there's anything I did wrong; I haven't messed with python in a while, this is my first KOTH, and it's 2 am, so there's bound to be something.

EDITS:

Taking out the randomness turns out to help it a lot. With the randomness in it is very dumb. Also, as said in the comments below, it tries to summon the demon or dragon.

Steve

from base import BasePlayer
from random import choice

class Steve(BasePlayer):

    def reset(self):
        self.items = [0, 1, 2, 3, 4, 5]
        self.turns = 0
        self.dungeon = []
        self.possibledungeon = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 9]
        self.lastDied = 0

    def __init__(self):
        self.TRIALS = 10 #How many dungeon runs to do each turn
        self.PASS = 8    #How many dungeon runs have to have died to pass
        self.SMASHITEMS = 4 #If less dungeon runs died, smash items.
        self.reset()


    def start_turn(self, last_turn):
        if (last_turn is not None):
            self.turns += 1

            if (last_turn in self.items):
                self.items.remove(last_turn)
            else:
                self.dungeon.append(-1)


        #Check if the dungeon is lethal
        died = 0
        total_hp = 3
        if (5 in self.items):
            total_hp += 5
        if (3 in self.items):
            total_hp += 3
        vorpal = self.vorpal_choice(None)
        for i in range(self.TRIALS):
            hp = total_hp
            temppossible = self.possibledungeon.copy()
            usedpotion = False
            killedDemon = False
            #Going for a crawl
            for j in self.dungeon[::-1]:
                if (killedDemon == True): #If last round we killed the Demon
                    killedDemon = False
                    j = 0
                if (j == -1): #If we don't know what this card is
                    j = choice(temppossible)
                    temppossible.remove(j)
                if (j == 7 and 0 in self.items): #If we kill demon with the pact
                    j = 0
                    killedDemon = True
                if (j % 2 == 0 and 2 in self.items) or (j == vorpal): #If we vorpal or grail
                    j = 0

                hp -= j

                if (hp <= 0):
                    if (not usedpotion and 1 in self.items):
                        hp = 3
                        usedpotion = True
                    else:
                        died += 1
                        break

        if (died >= self.PASS):
            return 0

        died = self.lastDied
        return 1


    def play(self, card):
        self.possibledungeon.remove(card)

        if (self.lastDied < self.SMASHITEMS):
            if (7 in self.dungeon) and (0 in self.items):
                self.items.remove(0)
                return 0
            if ( (9 in self.dungeon) or (5 in self.dungeon) ) and (3 in self.items):
                self.items.remove(3)
                return 3
            for i in [2, 1, 5, 4, 3, 0]:
                if (i in self.items):
                    self.items.remove(i)
                    return i

        self.dungeon.append(card)
        return 6

    def vorpal_choice(self, last_turn):
        if (last_turn is not None):
            self.turns += 1

            if (last_turn in self.items):
                self.items.remove(last_turn)
            else:
                self.dungeon.append(-1)

        if (self.dungeon.count(5) == 2):
            return 5
        if (9 in self.dungeon):
            return 9
        if (self.dungeon.count(4) == 2 and not 2 in self.items):
            return 4
        if (7 in self.dungeon and not 0 in self.items):
            return 7
        for i in range(6)[::-1]:
            if (i+1 in self.dungeon):
                return i+1
        return 5

    def result(self, bot, result, dungeon, vorped):
        self.reset()

Steve tries to guess whether or not the dungeon is lethal. If he thinks it is, he passes. Other than that, I tried to make him get rid of items smartly. He used to adjust his PASS threshold depending on if he died in the dungeon or the opponent lived, but it ended up making him a lot dumber so I got rid of it.

He still doesn't beat GrailThief on my machine, but at least comes closer.


GrailThief

An old and experienced dungeon crawler. He knows that most of the others hope, that the holy grail saves them, therefore he makes sure it disappears.

from base import BasePlayer
import copy

class GrailThief(BasePlayer):
    class Stats:
        def __init__(self):
            self.deck = [1, 2, 3, 4, 5] * 2 + [6, 7, 9]
            self.items = {0, 1, 2, 3, 4, 5}
            self.dungeon_known = []
            self.dungeon_unknown = 0
            self.monsters_safe = {2, 4, 6, 7}
            self.update()

        def update(self):
            self.dungeon_total = len(self.dungeon_known) + self.dungeon_unknown
            deck_factor = float(self.dungeon_unknown) / len(self.deck) if len(self.deck) > 0 else 1.0
            self.dungeon_weighted = [(i, 0.0 if i in self.monsters_safe else 1.0) for i in self.dungeon_known] + [(i, 0.0 if i in self.monsters_safe else deck_factor) for i in self.deck]
            dungeon_weighted_sums = dict.fromkeys(self.dungeon_known + self.deck, 0.0)
            for i in self.dungeon_weighted:
                dungeon_weighted_sums[i[0]] += i[0] * i[1]
            self.vorpal = max(dungeon_weighted_sums, key = dungeon_weighted_sums.get)
            if 3 in self.items:
                self.dungeon_weighted = [(i[0], 0.0 if i[0] == self.vorpal else i[1]) for i in self.dungeon_weighted]

        def discard_item(self, item, card):
            new = copy.copy(self)
            new.items = {i for i in new.items if i != item}
            if item == 0:
                new.monsters_safe = {i for i in new.monsters_safe if i != 7}
            elif item == 2:
                new.monsters_safe = {i for i in new.monsters_safe if i not in {2, 4, 6}}
            if card is not None:
                new.deck = list(new.deck)
                new.deck.remove(card)
            new.update()
            return new

        def to_dungeon(self, card):
            new = copy.copy(self)
            if card is None:
                new.dungeon_unknown += 1
            else:
                new.deck = list(new.deck)
                new.deck.remove(card)
                new.dungeon_known = list(new.dungeon_known)
                new.dungeon_known.append(card)
            new.update()
            return new

        def effective_hp(self):
            hp = 3.0
            if 1 in self.items:
                hp += 3.0
                if self.dungeon_total > 0:
                    hp += sum([(i[0] - 1) * i[1] for i in self.dungeon_weighted]) / self.dungeon_total
            if 4 in self.items:
                hp += 3.0
            if 5 in self.items:
                hp += 5.0
            return hp

        def effective_damage(self):
            damage = sum([i[0] * i[1] for i in self.dungeon_weighted])
            if 0 in self.items:
                if self.dungeon_total > 1:
                    damage -= damage / (self.dungeon_total - 1)
            return damage

    def __init__(self):
        self.stats = self.Stats()

    def process_last_turn(self, last_turn):
        if last_turn in [0, 1, 2, 3, 4, 5]:
            self.stats = self.stats.discard_item(last_turn, None)
        elif last_turn == 6:
            self.stats = self.stats.to_dungeon(None)

    def start_turn(self, last_turn):
        self.process_last_turn(last_turn)
        if self.stats.effective_hp() > self.stats.effective_damage() + 1.5:
            return 1
        else:
            return 0

    def play(self, card):
        if 2 in self.stats.items:
            self.stats = self.stats.discard_item(2, card)
            return 2
        else:
            self.stats = self.stats.to_dungeon(card)
            return 6

    def vorpal_choice(self, last_turn):
        self.process_last_turn(last_turn)
        return self.stats.vorpal

    def result(self, bot, result, dungeon, vorped):
        self.stats = self.Stats()

SlapAndFlap

First time in KOTH, so slap me hard for mistakes.

This one simpleton is always trying to remove all good items with low-strength monsters, while keep powerful ones and then just forces opponent to play.
It beats RunAway and DumDum at least :D
My other bot in deleted answer for some time, I need to fix him by tommorow

from base import BasePlayer

class SlapAndFlap(BasePlayer):

    def reset(self):
        # Monsters that you pushed in
        self.know_monster = list(self.deck)

        # Items still in game
        self.items_in_game = [True, True, True, True, True, True]

        # List of items, sorted by value
        self.valuables = [3,1,5,0,2,4]

        # Counter
        self.cards = 13

    def __init__(self):
        # Deck
        self.deck = (1,1,2,2,3,3,4,4,5,5,6,7,9)
        # Indexes of item cards
        self.items = (0, 1, 2, 3, 4, 5)

        self.reset()

    def start_turn(self, last_turn):
        if last_turn is not None:
            self.cards -= 1

        # Sneak peak at items removed by opponent
        if last_turn is not None and  last_turn < 6:
            self.items_in_game[last_turn] = False
            self.valuables.remove(last_turn)

        # Flap!
        if self.cards < 6:
            return 0
        return 1

    def play(self, card):
        if card < 6 and any(self.items_in_game):
            self.know_monster.remove(card)
            to_return = self.valuables[0]   # remove the best of the rest
            self.valuables = self.valuables[1:]
            self.items_in_game[to_return] = False
            return to_return
        else:
            return 6

    def vorpal_choice(self, last_turn):
        # We can just guess what monster will be there
        # But we know ones, we removed

        # If we have pact, no need to remove demon
        if self.items_in_game[0]:
            self.know_monster.remove(7)
        # If we have grail, no need to remove even monsters (kinda)
        if self.items_in_game[2]:
            self.know_monster = [i for i in self.know_monster if i%2]

        # Find what threatens us the most, counting its strength multiplied by number
        weight = [i * self.know_monster.count(i) for i in self.know_monster]
        return weight.index(max(weight)) + 1


    def result(self, bot, result, dungeon, vorped):
        self.reset()  # we live for the thrill, not the result!