Dungeon of botdom

8

2

– of crisis and martyrdom

(that's the subtitle because subtitles are cool)

In this challenge in (yup; you need go no farther to know you may not submit in java), you need to create a bot that plays a game very similar to welcome to the dungeon

Game Rules

(note that this is not the original game)

There is a deck, an item set, and some reward cards and death cards. Base HP is 3. The deck consists of 13 monster cards numbered 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 9 to denote their strengths.

Item List

  1. Demonic pact: Defeat the demon (strength 7 monster), and the monster below it on the dungeon pile. - (just defeats the demon if the demon was the last in the dungeon)

  2. Health potion: When you fall to 0 HP, defeat the monster and return to 3 HP.

  3. Holy grail: Defeat monsters of even-numbered strength (in the game, these are undead). If an even-numbered monster occurs after the demonic pact has been used, that acts first, and you will not get an extra-pact kill after this monster.

  4. Vorpal dagger: Choose one monster before entering the dungeon; this type of monster is defeated. If the targeted monster occurs after the demonic pact has been used, that acts first, and you will not get an extra-pact kill after this monster.

  5. Shield: Add 3 to total HP before spelunking. This does not affect usage of health potion, which will always return health to 3.

  6. Armour: Add 5 to total HP before spelunking. This does not affect usage of health potion, which will always return health to 3.

Reward cards are used to keep track of who has succeeded in the dungeon. Death cards track who has failed in the dungeon.

Drawing Phase

Before the drawing phase begins all monster cards are returned to the deck, both players are restored to 3 HP, and all discarded items are restored such that there is one of each.

The first player decides whether to draw a card from the deck, hiding it from the other player. If so, they must choose to either place it on top of the dungeon pile or discard it along with an item of their choice. Discarded items and cards will be unavailable to either player until the next round.

After player one takes their turn, player two does the same. The players alternately decide whether to draw and what to do with the drawn card, until someone decides not to draw or a player takes the last card from the deck. If a player decides not to draw, or draws the last card, the drawing phase ends and the other player now has to enter the dungeon and begin spelunking.

Spelunking Phase

If the Vorpal dagger has not been discarded, the spelunking player must now decide which card to apply it to. There are no active decisions to be made for the rest of this phase.

The first player takes the top card; that is, the last card placed in the dungeon, and see its strength number. If demonic pact is active from the previous turn, the drawn card is discarded. Otherwise, the player's items will be checked in the order 'demonic pact', 'holy grail', 'Vorpal dagger'. The first un-discarded item capable of defeating the drawn card will then be used, and the card discarded. If demonic pact is used, it will now be active for the next card. The used item is not discarded.

If there is no applicable item available, the strength of the card is subtracted from the player's health. If their health is no longer positive they will be restored to 3 HP and the potion discarded if available, otherwise the dungeon crawl ends and they take a death card.

While the player is not defeated and there are cards remaining in the dungeon, this process of drawing the top card is repeated. Upon successfully defeating all cards in the dungeon the dungeon crawl ends and the spelunking player collects a reward card.

Full Game Description

A game consists of a series of rounds, each having a drawing phase and then a spelunking phase. At the end of each round one player will have collected either a death card or a reward card; once a player accumulates 5 of either type the game ends. If they have 5 death cards they lose the game. If they have 5 reward cards, they win. Either way, the other player receives the opposite result. If neither player has 5 cards of one type, play progresses to the next round and the player who went second in the previous round now goes first and vice versa.

KOTH details

Each bot will play 400 games against every other bot according to the rules described above. Which bot is player one (and so goes first in the first round) alternates each game, and all state is reset between games.

Here are the items again:

  1. Demonic pact: Defeat the demon (strength 7 monster), and the monster below it on the dungeon pile. - (just defeats the demon if the demon was the last in the dungeon)

  2. Health potion: When you fall to 0 HP, defeat the monster and return to 3 HP.

  3. Holy grail: Defeat monsters of even-numbered strength (in the game, these are undead). If an even-numbered monster occurs after the demonic pact has been used, that acts first, and you will not get an extra-pact kill after this monster.

  4. Vorpal dagger: Choose one monster before entering the dungeon; this type of monster is defeated. If the targeted monster occurs after the demonic pact has been used, that acts first, and you will not get an extra-pact kill after this monster.

  5. Shield: Add 3 to total HP before spelunking. This does not affect usage of health potion, which will always return health to 3.

  6. Armour: Add 5 to total HP before spelunking. This does not affect usage of health potion, which will always return health to 3.

and the deck: 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 9.

You must implement a bot class that does not use class variables, deriving from the following base class:

class BasePlayer:
    def start_turn(self, last_turn):
        raise NotImplementedError

    def play(self, card):
        raise NotImplementedError

    def vorpal_choice(self, last_turn):
        raise NotImplementedError

    def result(self, bot, result, dungeon, vorped):
        raise NotImplementedError

This base class shows methods your class needs to implement, and the number of arguments taken by each.

Method Argument Descriptions

  • last_turn in vorpal_choice and start_turn is an integer or a None value. A value of 0 to 5 indicates that the enemy discarded the drawn card along with the item indicated by that value (see the list of items above). A value of 6 indicates that the enemy placed the card in the dungeon. A None value indicates that the bot is playing first in this round (not possible for vorpal_choice). In vorpal_choice last_turn is likely to be 7, indicating that they passed that turn. The only circumstance in which it is not 7 is when the enemy drew the last card.

  • card is a number representing the strength of one of the cards from the deck as enumerated above.

Now, the arguments for result are a little bit more complex:

  • bot indicates the bot which entered the dungeon. 0 indicates entering the dungeon, and 1 indicates that the enemy entered the dungeon.

  • result indicates the success of the trip. False indicates the spelunking bot succeeded, while True indicates they failed.

  • dungeon is a list of cards/ints representing the cards that were in the dungeon. The dungeon is ordered by order placed; the first card placed in the dungeon is first in the list, and the last card placed in is at the end. You will not receive any info about discarded cards; they are secret from the other bot.

  • vorped is an integer representing the vorpal_choice made by the spelunking bot. If bot==0, you already know this, but if bot==1, this can be useful information.

I'll be honest, I don't entirely recall why I made winning result False, but I think it was a good idea at the time.

Return Values

  • start_turn: Return 1 to draw a card, or 0 to pass.

  • play: Return 0 to 5 to discard the corresponding item and the drawn card, or 6 to place the card in the dungeon (consistent with last_turn input, except for passing, which is done during start_turn).

  • vorpal_choice: Return the number of the card you wish to eliminate with the Vorpal dagger (1 to eliminate 1s, 5 to eliminate 5s). Picking a non-existent card kills you (8 is illegal, 10 is illegal, 0 is illegal).

  • result: You may return anything, because this is an informing function to update the bot's data.

You can check out the controller here

Additional clarifications, or just repeating some small details you might have skipped past and might want to know quickly:

Bots play 400 games with each other bot.

No class variables

No targeting specific other bots

No propping up other bots

No reflection things like modifying the random module or other bots.

6 bots maximum (per person), unless it is obvious that all bots are worth including in the KOTH (but still probably don't make a bunch of bots please)


There is no specific end time for this KOTH, except the end of the bounty for what that is worth. Just try to be winning at each time.

Results so far (sorry for being rather lazy with these guys :P)

1 GrailThief 2732 0.98
2 Steve 2399 0.86
3 DevilWorshipper 1854 0.66
4 RunAway 1336 0.48
5 BoringPlayer 1190 0.42
6 SlapAndFlap 783 0.28
7 DareDevilDumDum 750 0.27
8 RandomMandom 156 0.06

Grailthief "steals" the bounty. not really, because it earned it. Good work, Sleafar!

Destructible Lemon

Posted 2017-06-15T11:41:57.047

Reputation: 5 908

2I've actually played this game once or twice. Pretty challenging sometimes. – Draco18s no longer trusts SE – 2017-06-15T13:55:59.327

Nice challenge, but would you mind including the controller. I wouldn't mind doing some testing before submitting a bot. Additionally, if I were you, I'd put a limit on the number of bots. Finally, the no random module rule is kinda weird. So our bots must do the same thing every time, with no random responses? – Gryphon – 2017-06-15T16:40:21.960

(there is a link down the bottom. do I need to make it big and obvious?) – Destructible Lemon – 2017-06-15T23:04:40.673

@gryphon wait when did I write that wtf how did I miss that in my review EDIT: oh I was just being unclear. I meant editing the random module to exploit it – Destructible Lemon – 2017-06-15T23:05:44.393

OK, thanks for fixing those. :) – Gryphon – 2017-06-15T23:18:07.870

YOu might want to say that the bot limit is per person (if that's what you mean), because people might take this to mean 8 bots total. Also, I would decrease it if it is per person. If you get 40+ bots, this will take FOREVER to run. – Gryphon – 2017-06-16T00:15:52.030

1ehhh, tbh I doubt it will be that popular :P – Destructible Lemon – 2017-06-16T00:17:02.447

22. place the item in the dungeon. The item goes in the dungeon (duh) appears to be a typo; there isn't an item mentioned at that point (you just drew a card from the monster deck). The rules should probably be clarified a bit. – None – 2017-06-16T00:40:28.083

1@ais523 or any information known only to one player. am i being unclear again? the dungeon is only revealed at the end, so cards drawn by a bot is known only to one bot. as a bonus, cards discarded are not revealed ever. if you think "oh well then there is a probabilistic best strategy", opponent prediction is still greatly important so this is also invalid – Destructible Lemon – 2017-06-16T00:46:57.060

1Ah, it's currently unclear from the rules that you don't know the current contents of the dungeon (only the influence that you've personally had on it). That should almost certainly be clarified. – None – 2017-06-16T00:59:25.447

1@myself ("ehhh, tbh I doubt it will be that popular :P") I HATE BEING RIGHT – Destructible Lemon – 2017-06-19T04:32:40.297

It's likely not popular because the game is complicated. There's no clear strategies to attempt due to the "hunch" nature of the gameplay. For example, in Battleships (because that was posted recently, but I'm too lazy to write up a bot) I found the ultimate strategy (years ago): start a diagonal, hit every square on that diagonal. Then move over 4 spaces and do that diagonal. Repeat until you have to fill in like a chess board. You will, guaranteed, locate every ship before you finish. – Draco18s no longer trusts SE – 2017-06-20T03:31:12.093

@Draco18s this is specifically why I made it 5 games to win or lose; if your enemy tends to go lightly on the dungeon, you can try to win with that knowledge – Destructible Lemon – 2017-06-20T04:26:06.883

Oh sure. I just meant that is difficult to formulate a strategy compared to other ai writing challenges. Not that it might be tough to win. – Draco18s no longer trusts SE – 2017-06-20T13:18:15.040

I'm really willing to create a bot for this game but I really don't understand the rules completely. Would it be possible to clarify the game a little bit? Or am I too dense? – Masclins – 2017-06-20T15:18:19.293

@AlbertMasclans I think tonight I will rewrite the post, with this format: Description of original game, description of bot building, description of i/o in game – Destructible Lemon – 2017-06-20T23:06:46.593

@AlbertMasclans updated if you care to answer now – Destructible Lemon – 2017-06-22T02:30:30.720

@DestructibleLemon thanks a lot! I'll try indeed. I only have one last question. The cards of the dungeon will be kept in the same order as put into the dungeon? or will be shuffled? If it's the first case, it will be FIFO (queue) or FILO (stack)? – Masclins – 2017-06-22T06:58:00.913

LIFO (stack). edit made – Destructible Lemon – 2017-06-22T07:10:28.793

@DestructibleLemon Are you sure this line is correct: elif (not card % 2 == 0 and items[self.GRAIL]) and (card is not vorped):? If I'm not missing something, this should be actually elif (not card % 2 == 0 or not items[self.GRAIL]) and (card is not vorped):. – Sleafar – 2017-06-23T22:04:28.220

No, I am not. @sleafar – Destructible Lemon – 2017-06-23T23:08:54.453

Are we allowed to implement part of our submission in another language? – user1502040 – 2017-06-24T20:52:15.100

@user1502040 I'd rather not have to run stuff in a different language, and using subprocess.Popen tends to slow things down. if you are able to have an interpreter in that program that works reasonably fast, I guess you could – Destructible Lemon – 2017-06-24T22:30:51.197

Answers

3

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()

Sleafar

Posted 2017-06-15T11:41:57.047

Reputation: 2 722

Results on my box. – Sleafar – 2017-06-24T16:22:41.380

I must say, good work! thank you for joining this koth – Destructible Lemon – 2017-06-24T22:33:03.213

3

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.

J. Dingo

Posted 2017-06-15T11:41:57.047

Reputation: 31

you know that 9 is the dragon, not the demon? – Destructible Lemon – 2017-06-24T05:57:49.737

...oh. I hadn't realised that. Well, this code seems to be doing fine as is at least – J. Dingo – 2017-06-24T05:58:41.283

I've changed the code to attempt to summon the demon or dragon. – J. Dingo – 2017-06-24T06:05:38.403

I would like to say: nice job and also thanks for joining the koth – Destructible Lemon – 2017-06-24T06:09:17.357

I will attempt to execute this some time in the future, but I currently don't have my preferred computer so it may be hard... maybe I'll ask someone else nicely to execute it for me? – Destructible Lemon – 2017-06-24T06:29:04.657

Results on my machine – J. Dingo – 2017-06-25T00:15:33.480

2

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!

Dead Possum

Posted 2017-06-15T11:41:57.047

Reputation: 3 256

2Well, the first thing I'm going to slap you hard for is that it is KOTH, not KOHL. – Gryphon – 2017-06-15T16:33:42.117

2@Gryphon That one hurts :( – Dead Possum – 2017-06-15T16:35:42.103

2

RandomMandom

The obligatory random bot. Appropriately, he loses hard to the default bots, which is good, because it means that the game has at least some strategy.

from base import BasePlayer
from random import randint
from random import choice
class RandomMandom(BasePlayer):
    def __init__(self):
        self.items = [0,1,2,3,4,5]

    def start_turn(self, last_turn):
        if last_turn in self.items:
            self.items.remove(last_turn)
        if len(self.items) > 0:
            return randint(0,1)
        return 1

    def play(self, card):            
        if len(self.items) > 0:
            if randint(0,1) == 1:
                selection = choice(self.items)
                self.items.remove(selection)
                return selection
        return 6

    def vorpal_choice(self, last_turn):
        return choice([1,2,3,4,5,6,7,9])

    def result(self, bot, result, dungeon, vorped):
        # Never learns
        pass

Andrew U Baker

Posted 2017-06-15T11:41:57.047

Reputation: 41

1I'm confused... why does the bot always keep drawing when all items are gone? also you might want to weight it, otherwise it has a 50/50 chance of not placing anything in the dungeon and then you just look silly. PS nice name of bot – Destructible Lemon – 2017-06-18T03:12:06.573

(when I said not placing anything I meant not playing any turns) – Destructible Lemon – 2017-06-19T04:21:01.767

I didn't analyze the controller very closely, because I assumed that it wouldn't present me with a choice if none existed. Also, it's a super-naive bot, because I wanted to get at least one entry in before my real entry. I will use your feedback in the next bot. – Andrew U Baker – 2017-06-19T04:52:44.907

I think you confused the deck with items. you can have no items but still have a deck and have another turn. it is unlikely given how bots will probably play to not be stupid. – Destructible Lemon – 2017-06-19T04:54:57.247

also you have 75% chance of winning the bounty at this point if you do make that new bot so that'll probably be fun for you (mostly because of people not answering) – Destructible Lemon – 2017-06-19T05:02:47.857

1

DareDevilDumDum

kinda clear. never backs down. the only way you can (consistently; RunAway loses sometimes but it still beats this most of the time) lose to this bot is if you don't remove any items or are a super coward. think of this bot as a reminder to remove items, otherwise even this can win.

from base import BasePlayer


class DareDevilDumDum(BasePlayer):
    def start_turn(self, last_turn):
        return 1  # damn squiggles. Draw a card

    def play(self, card):
        return 6+card*0  # put the card in the dungeon, and use card to avoid squiggles :P

    def vorpal_choice(self, last_turn):
        return 9+last_turn*0  # dragon

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

And

RunAway

pretty much they remove armour and then run away some time before the end. as daredevildumdum, it doesn't remember anything, except for the number of cards in the deck (which tbh wouldn't be remembered in the actual game (you'd just check)) and whether someone removed the armour (mostly same as before).

from base import BasePlayer


class RunAway(BasePlayer):

    def __init__(self):
        self.cards = 13
        self.armoured = True

    def start_turn(self, last_turn):
        if last_turn is not None:
            self.cards -= 1  # opponents play
        if last_turn is 5:
            self.armoured = False

        if self.cards < 4:
            return 0 * last_turn  # avoid the ---noid--- squiggles
        else:
            return 1

    def play(self, card):
        if self.cards > 11 and self.armoured:  # if it is the first turn and armour has not been removed
            choice = 5  # remove armour
        else:
            choice = 6  # put the card in the dungeon
        self.cards -= 1  # this play
        return choice

    def vorpal_choice(self, last_turn):
        return 5  # without using more memory, this is the best choice statistically

    def result(self, bot, result, dungeon, vorped):
        self.cards = 13
        self.armoured = True

Also because I am special challenge poster, these bots do not count toward my bot count, because they are example bots that are dumb

Destructible Lemon

Posted 2017-06-15T11:41:57.047

Reputation: 5 908

Fixed the second code snippet (the beginning of it) – Mr. Xcoder – 2017-06-15T14:11:53.690

1

BoringPlayer

The opposite of RandomMandom, BoringPlayer always makes the same choices. The problem is that it appears that it is too successful for such a simple bot. It scores 3800+ in my local test.

from base import BasePlayer

class BoringPlayer(BasePlayer):
    def start_turn(self, last_turn):
        return 1

    def play(self, card):
        return 6

    def vorpal_choice(self, last_turn):
        return 5

    def result(self, bot, result, dungeon, vorped):
        # Never learns
        pass

Andrew U Baker

Posted 2017-06-15T11:41:57.047

Reputation: 41

This is almost identical to Daredevil except it chooses the golem for its vorpal sword. weird that you get 3800+. this seems like an obscure bug – Destructible Lemon – 2017-06-20T03:05:26.187

ok, it seems like picking 5 over nine is actually a really big advantage... O_o. I guess that this is not a bug – Destructible Lemon – 2017-06-20T04:35:10.477

I think that 5 over 9 is significant against runaway (because 9 was overkill anyway), and the other bots kind of need more fine tuning with their numbers anyway – Destructible Lemon – 2017-06-20T04:45:09.223

I didn't intend for it to be that close to DareDevil. I was actually working on a more sophisticated bot that tracked the cards more closely, and this was just the stub values. However, since it outperformed everything else, I decided I should submit it anyway. I will try to get my more sophisticated bot working to see if I can come up with a better bot. – Andrew U Baker – 2017-06-20T05:02:42.120

Note to people seeing this: This bot does not mean that this simplistic strategy will dominate; the other bots are also very simple and this is why this bot wins – Destructible Lemon – 2017-06-20T05:04:16.883

Yeah, come at me, bot! – Andrew U Baker – 2017-06-21T04:20:07.837

even if noone else answers, can you please make a better bot so I don't have to award the bounty to this? :P – Destructible Lemon – 2017-06-22T02:31:04.397

I'm trying to, but I just spent over an hour on two new bots with more complicated strategies, and they perform terribly. – Andrew U Baker – 2017-06-22T04:55:21.367