# Cave Rangers - Exploring the Darkness

Your geologist buddy nearly kicked down your office door as he burst in, eyes wide in excitement, and asked you to come with him to a site he just discovered. On the way he explains that he thinks he literally just struck gold. Only problem is, it's buried deep underground in a cavern with a very unstable roof. It's too dangerous to go spelunking, so he wants you to program one of his cave exploring robots to collect as much gold as it can before he pulls it back up. He also mentions that he's probed the cavern and found some wildlife that could be harmful to the robots, and also that he'd dropped some equipment down there that might still be useable. Each robot is equipped with two arms and a range of sensors. When you arrive at the scene, he tells you he's planning on recruiting more coders, and whoever does the best job will be rewarded.

Now, down to the nitty-gritty. The sensors pass information to your program as ASCII characters. Here's a list of what each character means and descriptions for anything the bot might encounter in the cave:

Code    Name/Description

You do things

@       Other bots
They do other things

-       Ground
This doesn't do things

C       Centipede
These will bite you and leave a poison effect
The bite will cost 1 health
The poison effect will last for 3 turns, costing 2 health each turn

B       Bats
If bats end up in the same space you are, your bot runs in a random direction during its turn rather than what you told it to do

L       Lion (because reasons)
Lions deal heavy damage, 10 health, each time they attack

F       Food
Eating this will give you 5 health
Can only be used once

W       Water
Drinking this will cure poison effects early
Can only be used once

R       Revealer
This will increase the range of your visibility to an 11x11 grid
The extra range will only be 75% correct, but the original range won't be effected

K       Knife
You do twice as much damage to other bots if you have a knife

G       Gold
The whole reason you're doing this in the first place

N       Nurse Nina
She mend you good
Restores your health by 10 while you occupy the same space as her

}       Boulder
You can't walk over boulders, and neither can anything else

P       Pit
If you fall in a pit, you will be stuck for 3 turns


The size of the cavern grows based on how many bots are participating. It starts as a 30x30, and it gets an extra 10x10 for every bot. So 2 bots will explore a 50x50 cavern.

Bots start with 20 health, but they don't have a maximum limit on health.

Input:

You'll receive input through STDIN in the following format:

20,5,10,1,0,True,False    <-health, number gold pieces, number of turns your bot has lasted, number of until the poison wears off, number of turns until you are no longer stuck in a pit, if you have a revealer, if you have a knife
-----
-G}--
--Y-L
-C---
---B-


The first line contains information about your bot, and the rest is the grid that your bot can see. If your bot is against one of the 4 walls of the cavern, you will get a grid that looks more like this (in the case of being all the way to the West):

---
}--
Y--
---
---


The cavern does not wrap around, and neither does your vision. The walls of the cavern are not marked, the only indication your bot receives that it is nearing a wall is its view being diminished. With the Revealer, you might get something like this:

--------C--
LW--------B
---K-N-----
--------BR-
-F---------
--B--Y---@N
-W@---F----
------K-F--
----@-}----
R@---G}--}-
--------G-R


Output:

You get two moves per turn, which you output in the following format:

MNNANW    <- Moves are groups of 3 characters representing the action and the direction


Possible actions are the following:

M    Move - Move your bot in the specified direction
A    Attack - Attack the square in the specified direction
H    Hold - Do nothing


Possible directions are the following:

NN - North (up)
NE - Northeast (up-right)
EE - East (right)
SE - Southeast (down-right)
SS - South
SW - Southwest
WW - West
NW - Northwest


The moves are applied from left to right.

Turns:

Turns progress in the following fashion:

1. Poison effects are applied to any player who has been poisoned

2. Non-bots move and attack

2a. Lions, Centipedes, and Bats move randomly

2b. Lions and Centipedes will attack everything that is directly adjacent to it (including diagonally)

2c. The bat effect will only be applied to a bot if it is on the same space as the bat

2d. Nurse Nina will stay in a location for 3 turns, and then jump to a random location.

3. Bots move

3a. If your bot gives invalid output, it won't move

3b. Your bot will try to get as close to the space designated by the output as possible (see the note at the bottom for more detail)

3c. One attack to a Centipede, Lion, or Bat will kill it

3d. Attacking another bot without a knife will do 5 damage, and 10 with a knife

Rules:

1. Stick to common languages that can be run on OS X or Linux.

2. You can optionally write up to and not exceeding 1kb data to a file

Scoring:

Bots will only be in the cavern until only one remains, or until 50 turns have gone by, whichever comes first. Your bot will be judged on the sum of the number of gold coins it collected and how many turns it lasted.

Controller code can be downloaded for testing here (make a folder called "bots" in the same directory that you download it to, and put your bot inside "bots") You'll need NumPy to run it. Feel free to dig through it, but you'll have to excuse the mess...

Here's some code for a random bot:

#!/usr/bin/python
import random as r

a = ['M','A','H']
d = ['NN','NE','EE','SE','SS','SW','WW','NW']

print(a[r.randint(0,2)]+d[r.randint(0,7)]+a[r.randint(0,2)]+d[r.randint(0,7)])


****Your bot will always move in the general direction your output specifies, but if it is obstructed by a rock or a wall, the exact direction depends on the circumstance. For instance, if your bot is against a wall like so:

---
}--
Y--
---
---


MNWMSW


your bot will move one space down. It couldn't move North or West, so that move had no effect. It could move South (and did) but couldn't move West. However, if your bot tried to move Northeast, it would go directly to that space (diagonal movement is diagonal, not procedural)

These are the average scores of 4 games.

The bot of Survival:    54.75
Coward:                 52.25
Pufferfish:             50.00
Randombot:              50.00
Indiana Jones:          47.50
TheoremBot:             46.50

• How much health does each bot have? And what does the edge of the cavern look like? – Conor O'Brien Oct 23 '15 at 23:29
• They start with 20 and can collect as much as they want. Added this info above – The Beanstalk Oct 23 '15 at 23:32
• The edges of the cavern aren't marked, your program will only get the bits that you could potentially walk on. – The Beanstalk Oct 23 '15 at 23:42
• Do you really not know your health? – pppery Oct 24 '15 at 13:28
• Could you input the length and width of the bot's vision? – LegionMammal978 Oct 24 '15 at 18:00

# Indiana Jones, Python 2

This bot is not afraid of anything. It will try to get the gold; and if it can't find any, it will try to stab opponents with knives.

#!/usr/bin/env python
import sys
import random
health, gold, turns, poison_remaining, pit_remaining, revealer, knife = eval(data[0])
lines = data[1:]

myloc = [-1, -1]

width, height = len(lines[0]), len(lines)

for y, line in enumerate(lines):
if line.find('Y')>-1:
myloc = [line.index('Y'), y]
if myloc[0]<width/2:
lines = ['-'*padding+line for line in lines]
elif myloc[0]>width/2+1:
lines = [line+'-'*padding for line in lines]

if myloc[1]<height/2:
elif myloc[1]>height/2+1:

uddirections = {1:'N',0:'',-1:'S'}
lrdirections = {1:'E',0:'',-1:'W'}

golds = {}
for y, line in enumerate(lines):
if 'G' in line:
x = line.index('G')
direction = ((uddirections[max(min(myloc[1]-y,1),-1)]+lrdirections[max(min(x-myloc[0],1),-1)])*2)[:2]
distance = max(abs(myloc[0]-x), abs(myloc[1]-y))
golds[distance] = direction

bots = {}
for y, line in enumerate(lines):
if '@' in line:
x = line.index('@')
direction = ((uddirections[max(min(myloc[1]-y,1),-1)]+lrdirections[max(min(x-myloc[0],1),-1)])*2)[:2]
distance = max(abs(myloc[0]-x), abs(myloc[1]-y))
bots[distance] = direction

foods = {}
for y, line in enumerate(lines):
if 'F' in line:
x = line.index('F')
direction = ((uddirections[max(min(myloc[1]-y,1),-1)]+lrdirections[max(min(x-myloc[0],1),-1)])*2)[:2]
distance = max(abs(myloc[0]-x), abs(myloc[1]-y))
foods[distance] = direction

knives = {}
for y, line in enumerate(lines):
if 'K' in line:
x = line.index('K')
direction = ((uddirections[max(min(myloc[1]-y,1),-1)]+lrdirections[max(min(x-myloc[0],1),-1)])*2)[:2]
distance = max(abs(myloc[0]-x), abs(myloc[1]-y))
knives[distance] = direction

if golds:
direction = golds[min(golds.keys())]
elif not knife and knives:
direction = knives[min(knives.keys())]
elif health<20 and foods:
direction = foods[min(foods.keys())]
elif bots and knife:
direction = bots[min(bots.keys())]
if min(bots.keys())==1:
print ('A'+direction)*2
sys.exit(0)
elif min(bots.keys())==2:
print 'M'+direction+'A'+direction
sys.exit(0)
else:
print ('M'+random.choice('NS')+random.choice('NEWS'))*2
sys.exit(0)
print ('M'+direction)*2

• Just had to change one thing to get this working -- line.index('Y') will throw an error if "Y" isn't in the line, but line.find('Y') will return -1 if "Y" isn't in the line. Otherwise, it's great! – The Beanstalk Oct 27 '15 at 22:21
• You bot cane sometimes output MSNMSN, which is invalid. – pppery Oct 29 '15 at 22:25

# Coward, python3

A coward always runs from potential threats.

However, if he feels super strong, he will suddenly run amok and stab everything near him.

The problem with the current implementation is that move commands are issued without knowledge whether it is the first or the second move.

#!/usr/bin/env python3.4

import sys, random

class Coward():
"""
A coward always runs from potential threats.

However, if he feels super strong, he will suddenly run amok
and stab everything near him.
"""
def __init__(self, hp, gold, turn, poison, pit, revealer, knife):
self.hp=int(hp)
self.gold=int(gold)
if knife=="True": self.knife=True
else: self.knife=False
self.pit=int(pit)
self.poison=int(poison)

self.grid=grid.split("\n")
@property
def _confidence(self):
return self.hp+5*self.knife-2*self.poison
@property
def _lineOfY(self):
for i, line in enumerate(self.grid):
if "Y" in line:
return i
@property
def _colOfY(self):
return self.grid[self._lineOfY].index("Y")
@property
def _maxX(self):
return len(self.grid)-1
@property
def _maxY(self):
return len(self.grid[0])-1
def move(self, step):
d = {'NN':(0,-1),'NE':(1,-1),'EE':(1,0),'SE':(1,1),'SS':(0,1),'SW':(-1,1),'WW':(-1,0),'NW':(-1,-1)}
c2d={(0,-1):'NN',(1,-1):'NE',(1,0):"EE",(1,1):"SE",(0,1):"SS",(-1,1):"SW",(-1,0):"WW",(-1,-1):"NW"}
#Don't move into wall/ boulder/ pit
#print(d, file=sys.stderr)
for k,v in list(d.items()):
x=self._lineOfY+v[0]
y=self._colOfY+v[1]
#print (k, v ,x , y, file=sys.stderr)
if x<0 or y<0 or x>self._maxX or y>self._maxY:
#print ("Out of bounds: ", k, file=sys.stderr)
del d[k]
elif self.grid[x][y]=="}" or self.grid[x][y]=="P":
del d[k]
#Always avoid bats, and enemys
for dx in range(-2,3):
for dy in range(-2,3):
x=self._lineOfY+dx
y=self._colOfY+dy
if x<0 or y<0 or x>self._maxX or y>self._maxY:
continue;
if self.grid[x][y] in ["B", "L", "C", "@"]:
for k in self._toDirection(dx, dy):
if k in d: del d[k] #Too many threats from all sides can paralyze the Coward: nowhere to go...
#print(d, file=sys.stderr)
tomove=[]
#Neighboring fields
for dx in [-1,1]:
for dy in [-1,1]:
x=self._lineOfY+dx
y=self._colOfY+dy
if x<0 or y<0 or x>self._maxX or y>self._maxY:
continue
if self.poison>0 and self.grid[x][y]=="W":
for k,v in d.items():
if v==(dx,dy):
tomove.append(k)
if self.grid[x][y]=="N": #Always go to nurse, even if dangerous
tomove.append(c2d[(x,y)])
if self.grid[x][y] in ["F","K","G"]: #Go to Food, Knife or Gold, if save
for k,v in d.items():
if v==(dx,dy):
tomove.append(k)
#Further away: Go towards food, knife and gold and Nina if save.
for target in ["N", "F", "G", "K"]:
for dx in [-2,2]:
for dy in [-2,2]:
x=self._lineOfY+dx
y=self._colOfY+dy
if x<0 or y<0 or x>self._maxX or y>self._maxY:
continue
if self.grid[x][y]==target:
l=[ k for k in self._toDirection(dx,dy) if k in d]
if l: tomove.append(random.choice(l))
s=list(d.keys())
random.shuffle(s)
tomove+=s
try:
return "M"+tomove[step-1]
except IndexError:
return ""
def attack(self, step):
c2d={(0,-1):'NN',(1,-1):'NE',(1,0):"EE",(1,1):"SE",(0,1):"SS",(-1,1):"SW",(-1,0):"WW",(-1,-1):"NW"}
#If Bot next to you: always attack
for k,v in c2d.items():
x=self._lineOfY+k[0]
y=self._colOfY+k[1]
if x<0 or y<0 or x>self._maxX or y>self._maxY:
continue
if self.grid[x][y]=="@":
return "A"+v
#If Bot or monster could come closer: attack potential new position
attDir={(-2,-2):["NW"], (-2,-1):["NW","WW"], (-2,0):["WW","NW","SW"], (-2,1):["WW","SW"], (-2,2):["SW"],(-1,-2):["NW","NN"], (-1,2):["SW","SS"], (0,2):["SW","SS","SE"],(0,-2):["NW","NN","NE"],(1,-2):["NN","NE"],(1,2):["SS","SE"],(2,-2):["NE"],(2,-1):["NE","EE"], (2,0):["NE","EE","SE"], (2,1):["EE","SE"], (2,2):["SE"]}
for k,v in attDir.items():
x=self._lineOfY+k[0]
y=self._colOfY+k[1]
if x<0 or y<0 or x>self._maxX or y>self._maxY:
continue
if self.grid[x][y] in ["@","L","C","B"]:
return "A"+random.choice(v)
return ""
def _toDirection(self,dx,dy):
if dx<0:
if dy<0:
return ["WW","NW","NN"]
elif dy==0:
return ["WW","NW","SW"]
elif dy>0:
return ["WW","SW","SS"]
elif dx>0:
if dy<0:
return ["EE","NE","NN"]
elif dy==0:
return ["EE","NE","SE"]
elif dy>0:
return ["EE","SE","SS"]
elif dx==0:
if dy<0:
return ["NN","NE","NW"]
elif dy==0:
return []
elif dy>0:
return ["SS","SE","SW"]
def _nearBat(self):
for dx in range(-2,3):
for dy in range(-2,3):
x=self._lineOfY+dx
y=self._colOfY+dy
if x<0 or y<0:
continue;
if self.grid[x][y]=="B":
return True
return False
def makeTurn(self):
try:
command=""
#If stuck, just attack
if self.pit:
command+=self.attack(1)+self.attack(2)
#Always run from bats
if self._nearBat:
command+=self.move(1)+self.move(2)
#If high-confidence: attack
if self._confidence>30:
command+=self.attack(1)+self.attack(2)
#Else: Move somewhere
command+=self.move(1)+self.move(2)
#Just in case, two random attacks
d = ['NN','NE','EE','SE','SS','SW','WW','NW']
a=random.choice(d)
b=random.choice([ x for x in d if x!=a])
command+="A"+a+"A"+b
return command[:6]
except Exception as e:
#print (e, file=sys.stderr)
#Attacking is better than nothing
d = ['NN','NE','EE','SE','SS','SW','WW','NW']
a=random.choice(d)
b=random.choice([ x for x in d if x!=a])
return "A"+a+"A"+b

info=info.split(",")
bot=Coward(*info)
t=bot.makeTurn()
#print(t, file=sys.stderr)
print(t)


## The bot of survival - Python 2

from __future__ import print_function
health,gold,survived,poison,pit,revealer,knife = input()
if pit:
exit()
#Yes, this is python 2, despite the use of input()
lines = []
try:
while True:
lines.append(raw_input())
except EOFError:
pass
CMOVES={"NW":(-1,-1),"NN":(-1,+0),"NE":(-1,+1),
"WW":(+0,-1),             "EE":(-0,+1),
"SW":(+1,-1),"SS":(+1,+0),"SE":(+1,+1),
}
MOVES={v:k for k,v in CMOVES.iteritems()}
import sys
def get_your_pos():
for row,line in enumerate(lines):
for col,square in enumerate(line):
if square == "Y":
return row,col
raise ValueError("Your bot is not present.")
def isnearby(thing,p=None):
your_pos = p or get_your_pos()
for move in MOVES:
yp = your_pos[0]+move[0],your_pos[1]+move[1]
try:
if yp[0] >= 0 and yp[1] >= 0 and lines[yp[0]][yp[1]] == thing:
return move
except IndexError:
#Edge of cavern
pass
for turn in range(2):
import random
nprio = .5
if health > 25:
nprio -= .2
elif health < 10:
nprio += .3
if poison:
nprio += .18
#heal
motive = how = None
if random.random() < nprio:
nurse = isnearby("N")
if nurse:
motive = "M"
how = MOVES[nurse]
elif random.random() < nprio:
food = isnearby("F")
if food:
motive = "M"
how = MOVES[food]
#cure poison
if poison and not motive:
water = isnearby("W")
if water:
motive = "M"
how = MOVES[water]
if not motive:
#Kill lions, bats, and centipedes
for animal in ("L","B","C"):
animal = isnearby(animal)
if animal:
motive = "A"
how = MOVES[animal]
y = get_your_pos()
y = y[0]+animal[0],y[1]+animal[1]
lines = map(list,lines)
lines[y[0]][y[1]] = "-"
break
else:
#Pick up knives
if not knife:
knife = isnearby("K")
if knife:
motive = "M"
how = MOVES[knife]
#Attack other bots
else:
prey = isnearby("@")
if prey:
motive = "A"
how = MOVES[prey]
#Get gold
gold = isnearby("G")
if gold and not motive:
motive = "M"
how = MOVES[gold]
def isvalidmove(c):
c = CMOVES[c]
y = get_your_pos()
y=(y[0]+c[0],y[1]+c[1])
if y[0] >= 0 and y[1] >= 0:
try:
lines[y[0]][y[1]]
except LookupError:
pass
else:
return True
if turn and not motive:
motive = "M"
while not (how and how not in (isnearby("}"),isnearby("P"),isnearby("@"))\
and isvalidmove(how)):
how = random.choice(CMOVES.keys())
if not motive:break
if not turn and motive == "M":
lines = map(list,lines)
yp = get_your_pos()
lines[yp[0]][yp[1]] = "-"
yp=[yp[0]+CMOVES[how][0],yp[1]+CMOVES[how][1]]
lines[yp[0]][yp[1]] = "Y"
print(motive+how,end="")
else:
exit()
#Nothing found on first move
def isvaguelynearby(thing):
your_pos = get_your_pos()
for move in MOVES:
yp = your_pos[0]+move[0],your_pos[1]+move[1]
try:
if yp[0] >= 0 and yp[1] >= 0 and board[yp[0]][yp[1]] != "P":
dest = isnearby(thing,yp)
if dest:
return move,dest
except IndexError:
#Edge of cavern
pass
if random.random() < nprio:
dests = isvaguelynearby("N")
if not dests and random.random() < nprio:
dests = isvaguelynearby("F")
if dests:
m1,m2 = MOVES[dests[0]],MOVES[dests[1]]
print("M" + m1 + "M" + m2)
exit()
dests = (poison and isvaguelynearby("W")) or (not knife and isvaguelynearby("K"))\
or isvaguelynearby("G")
prey = isvaguelynearby("L") or isvaguelynearby("B") or isvaguelynearby("C") or \
(knife and isvaguelynearby("@"))
if dests:
m1,m2 = MOVES[dests[0]],MOVES[dests[1]]
print("M" + m1 + "M" + m2)
elif prey:
m1,m2 = MOVES[prey[0]],MOVES[prey[1]]
print("M" + m1 + "A" + m2)
else:
how = None
while not (how and how not in     (isnearby("}"),isnearby("P"),isnearby("@"))\
and isvalidmove(how)):
how = random.choice(CMOVES.keys())
print("M"+how,end="")
lines = map(list,lines)
yp = get_your_pos()
lines[yp[0]][yp[1]] = "-"
yp=[yp[0]+CMOVES[how][0],yp[1]+CMOVES[how][1]]
lines[yp[0]][yp[1]] = "Y"
while not (how and how not in (isnearby("}"),isnearby("P"),isnearby("@"))\
and isvalidmove(how)):
how = random.choice(CMOVES.keys())
print("M"+how)


# Pufferfish, Python 3+

I'm just that person.

#!/usr/bin/env python3.4
import random
def select():
return "A"+["NN","NE","EE","SE","SS","SW","WW","NW"][random.randint(0,7)]
print(select()+select())

• This bot has the purpose of self-sustenance. I might add golf-seeking later if I have time. – Conor O'Brien Oct 26 '15 at 15:18
• It'll make your code a little longer, but Pufferfish is able to attack twice in the same turn if it wanted to be more deadly – The Beanstalk Oct 27 '15 at 22:24
• @TheBeanstalk Ohhh sweet! – Conor O'Brien Oct 27 '15 at 22:27
• You realize that this bot will never get any gold, and not all other bots will wander into its "kill aura" – pppery Oct 30 '15 at 0:47
• @ppperry I do realize this; this was mainly to get the ball rolling; it is not purposed to win, but to make life slightly more difficult. As in, Black Hat Man – Conor O'Brien Oct 30 '15 at 2:08