Skip to content

Commit

Permalink
randomly generate EotU coordinates, put them in slot data, and unit t…
Browse files Browse the repository at this point in the history
…est the generation
  • Loading branch information
Ixrec committed Feb 5, 2024
1 parent 1e196cb commit a97c43e
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
71 changes: 71 additions & 0 deletions worlds/outer_wilds/Coordinates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import List
from random import Random


two_point_coordinates = 6 * 5
three_point_coordinates = 6 * 5 * 4
four_point_coordinates = 6 * 5 * 4 * 3
five_point_coordinates = 6 * 5 * 4 * 3 * 2
six_point_coordinates = 6 * 5 * 4 * 3 * 2 * 1

total_possible_coordinates = (two_point_coordinates + three_point_coordinates + four_point_coordinates +
five_point_coordinates + six_point_coordinates)


def generate_random_coordinates(random: Random) -> List[List[int]]:
selections = [
random.randint(0, total_possible_coordinates - 1),
random.randint(0, total_possible_coordinates - 1),
random.randint(0, total_possible_coordinates - 1),
]
selected_coordinates = list(map(get_coordinate_for_number, selections))
map(validate_coordinate, selected_coordinates)
return selected_coordinates


def validate_coordinate(coordinate: List[int]) -> None:
assert len(coordinate) >= 2
assert len(coordinate) <= 6
assert len(set(coordinate)) == len(coordinate)


# Here, the number represents an index into the list of all possible coordinates of any length
def get_coordinate_for_number(coord_index: int) -> List[int]:
assert coord_index < total_possible_coordinates

if coord_index < two_point_coordinates:
return get_coordinate_points_for_number(2, two_point_coordinates, coord_index)
coord_index -= two_point_coordinates

if coord_index < three_point_coordinates:
return get_coordinate_points_for_number(3, three_point_coordinates, coord_index)
coord_index -= three_point_coordinates

if coord_index < four_point_coordinates:
return get_coordinate_points_for_number(4, four_point_coordinates, coord_index)
coord_index -= four_point_coordinates

if coord_index < five_point_coordinates:
return get_coordinate_points_for_number(5, five_point_coordinates, coord_index)
coord_index -= five_point_coordinates

return get_coordinate_points_for_number(6, six_point_coordinates, coord_index)


# Now the number represents an index into the list of all possible coordinates of one specific length
def get_coordinate_points_for_number(point_count: int, possible_coords: int, coord_index: int) -> List[int]:
points_not_taken = [0, 1, 2, 3, 4, 5]
coord_points = []
bucket_size = possible_coords

while len(coord_points) < point_count:
bucket_size //= len(points_not_taken)
point_choice = coord_index // bucket_size

point_num = points_not_taken[point_choice]
points_not_taken.remove(point_num)
coord_points.append(point_num)

coord_index %= bucket_size

return coord_points
8 changes: 7 additions & 1 deletion worlds/outer_wilds/Options.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass

from Options import Choice, Toggle, PerGameCommonOptions
from Options import Choice, DefaultOnToggle, Toggle, PerGameCommonOptions


class Goal(Choice):
Expand All @@ -12,6 +12,11 @@ class Goal(Choice):
option_song_of_six = 1


class RandomizeCoordinates(DefaultOnToggle):
"""Randomize the Eye of the Universe coordinates needed to reach the end of the game."""
display_name = "Randomize Coordinates"


class DeathLink(Choice):
"""When you die, everyone dies. Of course the reverse is true too.
The "default" option will not include deaths to meditation, the supernova or the time loop ending."""
Expand All @@ -30,5 +35,6 @@ class Logsanity(Toggle):
@dataclass
class OuterWildsGameOptions(PerGameCommonOptions):
goal: Goal
randomize_coordinates: RandomizeCoordinates
death_link: DeathLink
logsanity: Logsanity
3 changes: 3 additions & 0 deletions worlds/outer_wilds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .LocationsAndRegions import (all_non_event_locations_table, location_name_groups,
create_regions, get_locations_to_create)
from .Options import OuterWildsGameOptions
from .Coordinates import generate_random_coordinates


class OuterWildsWebWorld(WebWorld):
Expand Down Expand Up @@ -86,6 +87,8 @@ def set_rules(self) -> None:

def fill_slot_data(self):
slot_data = self.options.as_dict("goal", "death_link", "logsanity")
slot_data["eotu_coordinates"] = generate_random_coordinates(self.random) \
if self.options.randomize_coordinates else "vanilla"
# Archipelago does not yet have apworld versions (data_version is deprecated),
# so we have to roll our own with slot_data for the time being
slot_data["apworld_version"] = "0.2.0-dev"
Expand Down
62 changes: 62 additions & 0 deletions worlds/outer_wilds/test/test_coordinates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import json
import unittest

from ..Coordinates import get_coordinate_for_number, total_possible_coordinates, validate_coordinate


class TestCoordinateGeneration(unittest.TestCase):
def test_generate_specific_coordinates(self):
self.assertListEqual(get_coordinate_for_number(0), [0, 1])
self.assertListEqual(get_coordinate_for_number(1), [0, 2])
self.assertListEqual(get_coordinate_for_number(2), [0, 3])
self.assertListEqual(get_coordinate_for_number(3), [0, 4])
self.assertListEqual(get_coordinate_for_number(4), [0, 5])
self.assertListEqual(get_coordinate_for_number(5), [1, 0])
self.assertListEqual(get_coordinate_for_number(6), [1, 2])

# jump to where we switch from 2-point to 3-point coords
self.assertListEqual(get_coordinate_for_number(29), [5, 4])
self.assertListEqual(get_coordinate_for_number(30), [0, 1, 2])

# jump to the very end
self.assertListEqual(get_coordinate_for_number(1949), [5, 4, 3, 2, 1, 0])

def test_generate_every_coordinate(self):
all_possible_coordinates = set()

for coordinate_number in range(0, total_possible_coordinates):
coordinate = get_coordinate_for_number(coordinate_number)
all_possible_coordinates.add(json.dumps(coordinate))
# every coordinate is valid
validate_coordinate(coordinate)

# every coordinate is unique
self.assertEqual(len(all_possible_coordinates), total_possible_coordinates)

# an attempt was made to iterate through all 1950 coordinates and compare to the get_*() outputs, but
# it turns out it was far easier to make get_*() correct than this test correct so I abandoned that
# in case I change my mind, here's how far I got:
#
# coordinate = [0, 1]
# for coordinate_number in range(1, total_possible_coordinates):
# index = 1
# value = coordinate[-index]
# while value in coordinate and index <= len(coordinate):
# value += 1
# if value > 5:
# index += 1
# if index > len(coordinate):
# break
# value = coordinate[-index]
# if index <= len(coordinate):
# coordinate[-index] = value
# for index in range(index - 1, 0, -1):
# coordinate[-index] = 0
# while len(set(coordinate)) != len(coordinate):
# coordinate[-index] += 1
# else:
# coordinate = list(range(0, index))
#
# validate_coordinate(coordinate)
# self.assertListEqual(get_coordinate_for_number(coordinate_number), coordinate,
# f"coordinate number {coordinate_number} did not match get_coordinate_for_number")

0 comments on commit a97c43e

Please sign in to comment.