Skip to content

Instantly share code, notes, and snippets.

@ekimekim
Created August 3, 2021 18:05
Show Gist options
  • Save ekimekim/4c2160d989f9b406cda006feba96fd54 to your computer and use it in GitHub Desktop.
Save ekimekim/4c2160d989f9b406cda006feba96fd54 to your computer and use it in GitHub Desktop.
hacked together brute force solver for necrodancer blood shop throws
from collections import namedtuple
class Point(namedtuple('_Point', ['x', 'y'])):
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
shopkeeper = Point(2, 1)
rune = Point(2, 5)
size = Point(5, 7)
gold_weapon = Point(1, 3)
def tiles():
for x in range(size.x):
for y in range(size.y):
yield Point(x, y)
def throwable():
return [
t for t in tiles()
if (t.x == shopkeeper.x or t.y == shopkeeper.y) and (t != shopkeeper)
]
def teleports(pos):
return [
t for t in tiles()
if abs(t.x - pos.x) > 1 or abs(t.y - pos.y) > 1
]
def check_goal(pos, goal, shop_pos):
first = True
while True:
# shop moves
shop_pos += diag_step(shop_pos, pos)
# if shop just moved into you, you are dead
if shop_pos == pos:
return False
# if shop didn't hit you, and you reached goal, you live
# but if it's the first turn, this means you started on goal, which doesn't count.
# we simulate stepping off in a safe direction, then back, as not allowing win on first.
if pos == goal and not first:
return True
# you move
pos += step(pos, goal)
first = False
def diag_step(fr, to):
return Point(cmp(to.x, fr.x), cmp(to.y, fr.y))
def step(fr, to):
diag = diag_step(fr, to)
if diag.x:
return Point(diag.x, 0)
else:
return Point(0, diag.y)
def check(pos, weapon, shop_pos):
dagger = throw_to(pos)
goals = [rune, dagger, gold_weapon]
if weapon is not None:
goals.append(weapon)
return any(check_goal(pos, goal, shop_pos) for goal in goals)
def throw_to(pos):
if pos.x == shopkeeper.x:
x = pos.x
y = size.y - 1 if pos.y < shopkeeper.y else 0
else:
y = pos.y
x = size.x - 1 if pos.x < shopkeeper.x else 0
return Point(x, y)
def win_rate(pos, weapon):
tps = teleports(pos)
total = len(tps)
wins = 0
for tp in tps:
if check(pos, weapon, tp):
wins += 1
return float(wins) / total, wins, total
def weaponable():
return [
t for t in tiles()
if (t.x == 0 or t.x == size.x - 1 or t.y == 0 or t.y == size.y - 1)
]
def find_best(weapon=True):
return max((
(throw_pos, weapon_pos, win_rate(throw_pos, weapon_pos))
for throw_pos in throwable()
for weapon_pos in (weaponable() if weapon else [None])
), key=lambda (tp, wp, (r, w, t)): r)
print "classic strat"
print win_rate(Point(2,2), None)
print "classic with 1 weapon to side"
print win_rate(Point(2,2), Point(4,2))
print "top corner weapon"
print win_rate(Point(4,1), Point(4,0))
print "Best with weapon?"
print find_best()
print "Best without?"
print find_best(weapon=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment