Created
August 3, 2021 18:05
-
-
Save ekimekim/4c2160d989f9b406cda006feba96fd54 to your computer and use it in GitHub Desktop.
hacked together brute force solver for necrodancer blood shop throws
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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