Last active
March 28, 2021 22:28
-
-
Save SciresM/967853862da6bdf4e079b4548199df1d to your computer and use it in GitHub Desktop.
Ushabti predictor for Spelunky 2 seeded runs.
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
import sys | |
# Spelunky 2 ushabti prediction for seeded runs, author SciresM. | |
def u32(v): | |
return v & 0xFFFFFFFF | |
def u64(v): | |
return v & 0xFFFFFFFFFFFFFFFF | |
def rotate_left64(a, b): | |
return u64((((a) << (b)) | ((a) >> (64 - (b))))) | |
def expand_32_bits(seed): | |
x = u32(seed) | |
x = u64(-x) if x != 0 else 1 | |
x = u64(0x9E6C63D0676A9A99 * x) | |
x = (((x >> 28) ^ x) >> 23) ^ x | |
x = u64(0x9E6C63D0676A9A99 * x) | |
y = (((x >> 28) ^ x) >> 23) ^ x | |
x = u64(0x9E6C63D0676A9A99 * rotate_left64(x, 27)) | |
return (x, y) | |
def advance(x, y): | |
return u64(0xD3833E804F4C574B * y), rotate_left64(u64(y - x), 27) | |
def rand(state, limit): | |
return (u32(state[0]) * limit) >> 32 | |
def generate_initial_randomness(seed): | |
# Generate initial randomness sources. | |
x, y = expand_32_bits(seed) | |
x |= 1 | |
y = u64(0xD3833E804F4C574B * y) | |
y = u64(-y) if y != 0 else 1 | |
y = u64(0x9E6C63D0676A9A99 * y) | |
y = (((y >> 28) ^ y) >> 23) ^ y | |
y = u64(0x9E6C63D0676A9A99 * y) | |
y = u64(0x9E6C63D0676A9A99 * rotate_left64(y, 27)) | |
return (x, u64(x + y)) | |
def generate_random_table(seed): | |
table = [] | |
x, y = expand_32_bits(seed) | |
# Spelunky 2 1.19.7a added an extra entry to the table, changing the seeds used for generation. | |
# For pre-1.19.7a, edit this length to 9. | |
while len(table) != 10: | |
table.append(expand_32_bits(x)) | |
x, y = advance(x, y) | |
return table[::-1] | |
def get_style(u): | |
assert 0 <= u and u < 100 | |
return [['cracked', 'simple'], ['smiling', 'tall']][u // 50][(u % 10) // 5] | |
def get_material(u): | |
assert 0 <= u and u < 100 | |
return ['clay', 'gold', 'jade', 'wood', 'onyx'][u % 5] | |
def get_symbol(u): | |
assert 0 <= u and u < 100 | |
return ['eye', 'ankh', 'snake', 'vortex', 'bat'][(u // 10) % 5] | |
def generate_ushabti(seed): | |
# Generate initial randomness. | |
init_rnd = generate_initial_randomness(seed) | |
# Derive the game seed. | |
game_seed = (u32(init_rnd[1]) ^ u32(init_rnd[1] >> 32)) | |
# Populate the random tables for the game seed. | |
tables = generate_random_table(game_seed) | |
# Generate the winning ushabti. | |
return rand(tables[2], 100) | |
def main(argc, argv): | |
if argc != 2: | |
print 'Usage: %s seed' % argv[0] | |
return 1 | |
seed = int(argv[1], 16) | |
u = generate_ushabti(seed) | |
print '%08X: A %ls servant of %ls marked by the %ls shall open the door for the Aspirant.' % (seed, get_style(u), get_material(u), get_symbol(u)) | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main(len(sys.argv), sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment