27
6
Test Driver • Challenge Discussion • Submit Adventurer
Several rival adventurers are raiding the ruins for treasure, but they can only carry so much at a time and have their limits of endurance. They want to get the most valuable treasure and get out before they become too tired to continue. They are trying to become as rich as possible from their looting shenanigans.
Gameplay
Each adventurer begins in the first room of the dungeon with 1000 stamina points and 50kg of space in their backpack.
The game operates on a turn-based fashion, with all players resolving their turns simultaneously. Each turn, you can do one of the following actions:
- Move to the next room.
- Move to the previous room.
- Bid stamina to take a treasure.
- Drop a treasure.
Moving between rooms requires 10 stamina, plus 1 for every 5kg currently in your backpack, rounded up. For instance, an adventurer carrying 3kg of treasure requires 11 stamina to move and one carrying 47kg requires 20 stamina to move.
Dropping treasure requires 1 stamina regardless of the treasure dropped.
Upon exiting the ruins, no more turns will be taken by the player.
If a player cannot take any of these actions (due to shortage of stamina or absence of treasures), their adventurer dies of exhaustion, spilling their held treasure into the currently occupied room. Similarly, if a player attempts to do an invalid action, their adventurer will be killed by a trap instead, resulting in the same treasure spillage.
Bidding
The minimum bid for a treasure is 1 stamina per 1kg that the treasure weighs. You may also bid additional stamina points to be more likely to obtain the treasure. The stamina that was bid is consumed no matter what the outcome is.
In the event that multiple players have bid to take the same treasure, the player who bid highest gets the treasure. If more than one player made the highest bid, none of them will receive the treasure.
Win Condition
The player with the largest total value of treasures is the winner. In the unlikely event of a tie, ties go to the smallest total weight, then smallest number of treasures, then value of the most valuable treasure, second most valuable, third... until the tie is broken. In the near-impossible event that there is still a tie at this point, the test driver says "screw it" and the winner is thereby determined arbitrarily.
In the context of the tournament, players will be ranked with first place receiving 10 points, second place with 9 points, third place with 8 points, etc..., with dead players and adventurers with no treasures scoring 0 points.
About the Ruins
- Each room initially contains between \$\lfloor{r \over 3}\rfloor + 3\$ and \$\lfloor{r \over 2}\rfloor + 5\$ treasures. (Where \$r\$ is the room number)
- There are arbitrarily many rooms, limited only by adventurers' stamina and willingness to explore.
- Each treasure will have a monetary value (in whole $) and a weight (in whole kg).
- Treasures tend to be more valuable and plentiful as you go deeper into the ruins.
- The specific formulas for generating treasures are as follows: (using \$xdy\$ notation for dice rolls)
- Weight is generated first using the formula \$2d6 - 2\$ (minimum of 1)
- Treasure value then is generated via \$1d[10 * w] + 2d[5 * r+ 10]\$ (where \$r\$ is the room number and \$w\$ is the weight)
Information Visible to Players
At each turn, players get the following information:
- The number of the room they are currently in. This is 1-indexed, so conceptually the exit is at "room 0"
- A list of treasures currently in the room
- A list of other players who are also currently in the room.
- Your current inventory of treasures
- Your current stamina level
Coding
The test driver can be found here.
You should implement a subclass of this Adventurer
class:
class Adventurer:
def __init__(self, name, random):
self.name = name
self.random = random
def get_action(self, state):
raise NotImplementedError()
def enter_ruins(self):
pass
You only need to override the get_action
method. enter_ruins
is run before a game begins and is your chance to prepare whatever you wish to have ready for the game. You do not need to override __init__
, and you really shouldn't. If your __init__
crashes, you will be disqualified.
get_action
recieves a single argument which is a namedtuple
with the following fields (in this order, if you prefer destructuring):
room
: the number of the room you are currently intreasures
: the list of treasures in the roomplayers
: the list of other players in the room. You only get the player name this way, so you do not know what bot is controlling them or their inventory/stamina.inventory
: the list of treasures in your backpackstamina
: your current stamina level
This object additionally provides two utility properties:
carry_weight
: the total weight of all the treasures you are carryingtotal_value
: the total value of all the treasures you are carrying
The treasures
and inventory
lists contain namedtuple
s with these attributes:
name
: the treasure's name (for cosmetic purposes)value
: the monetary value of the treasure in $.weight
: the treasure's weight in kg
get_action
should return one of the following values/patterns:
'next'
or'previous'
to move to the next/previous rooms'take', <treasure index>, <bid>
(yes, as a tuple, though any sequence will technically work as well) to bid on the treasure at the given index in the room's treasure list. Both arguments should be integers. Floats will be rounded down.'drop', <inventory index>
to drop the carried treasure found at the given index. The index should (naturally) be an integer.
Other Restrictions
- You may only use the random instance provided to you during initialization for pseudorandomness.
- Anything else that might introduce behavioral nondeterminism is not allowed. The intent here is to make bots behave identically when given the same seed to aid in testing new bots (and potentially bugs in the test driver). Only cosmic radiation should cause any deviation/nondeterminism.
- Keep in mind that hash codes are randomized in Python 3, so using
hash
for any decision making is not allowed.dict
s are fine even when using iteration order for decisions since order has been guaranteed consistent since Python 3.6.
- You may not circumvent the test driver using
ctypes
hacks orinspect
stack voodoo (or any other method). There are some impressively scary things you can do with those modules. Please don't.- Each bot is sandboxed reasonably well via defensive copies and the natural immutability of
namedtuple
s, but there are some unpatchable loopholes/exploits. - Other functionality from
inspect
andctypes
may be used as long as neither is used to circumvent controller functionality. - Any method of grabbing instances of the other bots in your current game is not allowed.
- Each bot is sandboxed reasonably well via defensive copies and the natural immutability of
- Bots should operate solo and may not coordinate with any other bots in any way for any purpose. This includes creating two bots with different goals such that one sacrifices itself for the success of the other. Once there are more than 10 competitors, you won't actually be guaranteed to have the two bots in the same game and adventurer names don't give any indication of the bot class, so these types of strategies are limited anyway.
- There is currently no hard restriction on execution time, however I reserve the right to hard-restrict it in the future if tournaments start taking too long. Be reasonable and try to keep turn processing under 100ms, as I don't anticipate needing to restrict it below that threshold. (Tournaments will run in about 2 hours if all bots take about 100ms per turn.)
- Your bot class must be named uniquely among all submissions.
- You may not remember anything between games. (However, you can remember things between turns)
- Don't edit sys.modules. Anything outside instance variables should be treated as a constant.
- You may not modify any bot's code programmatically, including your own.
- This includes deleting and restoring your code. This is to make debugging and tournaments more streamlined.
- Any code that causes the controller to crash will be immediately disqualified. While most exceptions will be caught, some may slip through and segfaults are uncatchable. (Yes, you can segfault in Python thanks to
ctypes
)
Submissions
In order to aid answer scraping, indicate the name of your bot at the top of the answer with a #Header1
and ensure your answer includes at least one code block (only the first one in your answer will be used). You do not need to include any imports or docstrings, as they will be added automatically by the scraper.
I will be more inclined to upvote answers with detailed and understandable explanations. Others are likely to behave the same.
Roughly speaking, your answer should be formatted something like this:
# Name of Bot
Optional blurb
#imports go here
class BotName(Adventurer):
#implementation
Explanation of bot algorithm, credits, etc...
(rendered as)
Name of Bot
Optional blurb
#imports go here class BotName(Adventurer): #implementation
Explanation of bot algorithm, credits, etc...
Running the Test Driver Locally
You will need Python 3.7+ and I recommend you also install tabulate
via pip. Scraping this page for submissions additionally requires lxml
and requests
. You should also use a terminal with support for ANSI color escapes for best results. Info on how to set this up in Windows 10 can be found here.
Add your bot to a file in a subdirectory within the same directory as ruins.py
(ruins_bots
by default) and be sure to add from __main__ import Adventurer
to the top of the module. This is added to the modules when the scraper downloads your submission, and while it is definitely hacky, this is the most straightforward way of making sure your bot properly has access to Adventurer
.
All bots in that directory will be loaded dynamically at runtime, so no further changes are necessary.
Tournament
The ultimate victor will be determined in a series of games with up to 10 bots in each game. If there are more than 10 total submissions, the top 10 bots will be determined by systematically partitioning them into groups of 10 until every bot has played (exactly) 20 games. The top 10 bots will be selected from this group with reset scores and will play games until the first place bot has achieved a 50 point lead over the second place bot or until 500 games have been played.
Until there are at least 10 submissions, empty slots will be filled with "Drunkards" which wander randomly through the ruins and take (and occasionally drop) random treasures until they run out of stamina and have to beeline to the exit.
Tournaments will be re-run weekly if there are new submissions. This is an open KOTH challenge with no set end date.
Leaderboard
From run on May 4, 2019 at 4:25PM MDT: (2019-05-04 4:25 -6:00)
Seed: K48XMESC
Bot Class | Score | Mean Score
--------------+---------+--------------
BountyHunter | 898 | 7.301
Scoundrel | 847 | 6.886
Accountant | 773 | 6.285
Ponderer | 730 | 5.935
Artyventurer | 707 | 5.748
PlanAhead | 698 | 5.675
Sprinter | 683 | 5.553
Accomodator | 661 | 5.374
Memorizer | 459 | 3.732
Backwards | 296 | 2.407
Update - Apr 15: a couple rule updates/clarifications
Update - Apr 17: banning a couple of notable edge cases of nefarious actions such as modifying other bots' code.
Update - May 4: Bounty awarded to Sleafar for absolutely destroying Backwards. Congratulations!
1It's finally here! Guess I'll have to start making my bot now. – Belhenix – 2019-04-12T17:56:39.670
12Why the limit to one bot? I have several mutually exclusive ideas, and I'd rather not have to throw out a perfectly good bot each time I come up with a new one. – None – 2019-04-12T19:10:29.730
@Mnemonic, mostly it's to prevent displacing new submissions by using multiple near-identical bots. The other reason was to prevent bots from working together, but that is explicitly banned anyway. I'll consider allowing it. Those in favor of allowing multiple submissions, upvote Mnemonic's comment above. – Beefster – 2019-04-12T19:14:44.480
BUG REPORT: returning
['take', '5', '1']
causes an uncaught error. (I'm not saying it should be valid, just that the driver should instead kill with a trap) – Artemis still doesn't trust SE – 2019-04-13T02:30:37.420@ArtemisFowl I think I may have fixed that in one of my recent patches. Cannot reproduce. – Beefster – 2019-04-13T09:30:56.593
@Beefster Yeah I got the latest and it's gone. – Artemis still doesn't trust SE – 2019-04-13T13:01:29.797
Thanks for the rule change! – Artemis still doesn't trust SE – 2019-04-13T20:09:02.250
I don't have a few of the python modules installed (like tabulate and request) making it difficult to run the controller. Being someone who doesn't use Python except for KOTH challenges, could you give some some instructions? – Draco18s no longer trusts SE – 2019-04-13T21:30:57.280
1
@Draco18s If you have
– Artemis still doesn't trust SE – 2019-04-13T22:51:31.957pip
installed and onPATH
(which is default for newer installations AFAIK) then from windows you can runpip install modulename
in the command prompt. For other circumstances (which I don't know about), go to pip, search for the module needed and choose an option.1I'm guessing this will be a 'no', but are we allowed to save information through the tournament? (e.g when a bid worked) – Artemis still doesn't trust SE – 2019-04-13T22:54:19.847
I am not a python guy so i am wondering if a std in/out option is an option? From the questions's specifications, it doesn't seem like there is anything that really ties it to python. It would allow for more submissions form and likely to extend the longevity of the competition. – Moogie – 2019-04-13T23:17:06.190
@Moogie Currently, the driver makes it a definite no. (*I'm not the OP*, just saying what the code does and how easily it could be adapted) – Artemis still doesn't trust SE – 2019-04-13T23:19:38.423
@ArtemisFowl fair enough, I might translate the core parts of the harness into java, develop a bot and then translate my bot back into python. I imagine the act of porting will help me learn python – Moogie – 2019-04-14T00:14:50.760
@Moogie I've actually started work on a multi-language version of the driver now, though I don't know if the OP will agree to use it. Turns out python-only wasn't so deeply embedded in the driver as I thought. – Artemis still doesn't trust SE – 2019-04-14T00:19:00.647
Huh, there's a weird bug in the printing of the final scores. It doesn't actually include the points gained on the final run. I added in a printout of the same values at the top of the loop so I could see the rankings without having to wait for it to run to completion. https://pastebin.com/GtXb7wbr Note the perfect score at the top (130 in 13 rounds) and the final tally at the bottom (130 in 14 rounds, despite pulling in 10 more points).
– Draco18s no longer trusts SE – 2019-04-14T02:18:41.993I have also discovered that
Your bot class must be named uniquely
is not enforced. I found this out when I attempted to make a bot that targeted another bot. The import line (used to execute the target's code in order to follow them) in the saboteur caused a duplicate of the target to get loaded into the list of competitors. – Draco18s no longer trusts SE – 2019-04-14T16:12:18.163Not everybody use Phyton – Natural Number Guy – 2019-04-14T20:51:33.613
@Draco18s strange bug indeed. I'll investigate later tonight. – Beefster – 2019-04-15T16:38:15.613