Skip to content

Docs: minor updates to recommend modern PEP8 #2384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/apworld specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The zip can contain arbitrary files in addition what was specified above.

## Caveats

Imports from other files inside the apworld have to use relative imports.
Imports from other files inside the apworld have to use relative imports. e.g. `from .options import MyGameOptions`

Imports from AP base have to use absolute imports, e.g. Options.py and worlds/AutoWorld.py.
Imports from AP base have to use absolute imports, e.g. `from Options import Toggle` or
`from worlds.AutoWorld import World`
2 changes: 1 addition & 1 deletion docs/options api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ As an example, suppose we want an option that lets the user start their game wit
create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:

```python
# Options.py
# options.py
from dataclasses import dataclass

from Options import Toggle, PerGameCommonOptions
Expand Down
85 changes: 42 additions & 43 deletions docs/world api.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,11 @@ See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requireme
AP will only import the `__init__.py`. Depending on code size it makes sense to
use multiple files and use relative imports to access them.

e.g. `from .Options import MyGameOptions` from your `__init__.py` will load
`world/[world_name]/Options.py` and make its `MyGameOptions` accessible.
e.g. `from .options import MyGameOptions` from your `__init__.py` will load
`world/[world_name]/options.py` and make its `MyGameOptions` accessible.

When imported names pile up it may be easier to use `from . import Options`
and access the variable as `Options.MyGameOptions`.
When imported names pile up it may be easier to use `from . import options`
and access the variable as `options.MyGameOptions`.

Imports from directories outside your world should use absolute imports.
Correct use of relative / absolute imports is required for zipped worlds to
Expand All @@ -248,7 +248,7 @@ class MyGameItem(Item):
game: str = "My Game"
```
By convention this class definition will either be placed in your `__init__.py`
or your `Items.py`. For a more elaborate example see `worlds/oot/Items.py`.
or your `items.py`. For a more elaborate example see `worlds/oot/Items.py`.

### Your location type

Expand All @@ -260,15 +260,15 @@ class MyGameLocation(Location):
game: str = "My Game"

# override constructor to automatically mark event locations as such
def __init__(self, player: int, name = "", code = None, parent = None):
def __init__(self, player: int, name = "", code = None, parent = None) -> None:
super(MyGameLocation, self).__init__(player, name, code, parent)
self.event = code is None
```
in your `__init__.py` or your `Locations.py`.
in your `__init__.py` or your `locations.py`.

### Options

By convention options are defined in `Options.py` and will be used when parsing
By convention options are defined in `options.py` and will be used when parsing
the players' yaml files.

Each option has its own class, inherits from a base option type, has a docstring
Expand All @@ -284,7 +284,7 @@ For more see `Options.py` in AP's base directory.

#### Toggle, DefaultOnToggle

Those don't need any additional properties defined. After parsing the option,
These don't need any additional properties defined. After parsing the option,
its `value` will either be True or False.

#### Range
Expand All @@ -310,7 +310,7 @@ default = 0

#### Sample
```python
# Options.py
# options.py

from dataclasses import dataclass
from Options import Toggle, Range, Choice, PerGameCommonOptions
Expand Down Expand Up @@ -349,7 +349,7 @@ class MyGameOptions(PerGameCommonOptions):
# __init__.py

from worlds.AutoWorld import World
from .Options import MyGameOptions # import the options dataclass
from .options import MyGameOptions # import the options dataclass


class MyGameWorld(World):
Expand All @@ -366,9 +366,9 @@ class MyGameWorld(World):

import settings
import typing
from .Options import MyGameOptions # the options we defined earlier
from .Items import mygame_items # data used below to add items to the World
from .Locations import mygame_locations # same as above
from .options import MyGameOptions # the options we defined earlier
from .items import mygame_items # data used below to add items to the World
from .locations import mygame_locations # same as above
from worlds.AutoWorld import World
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification

Expand Down Expand Up @@ -427,7 +427,7 @@ The world has to provide the following things for generation
* additions to the regions list: at least one called "Menu"
* locations placed inside those regions
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
* applying `self.multiworld.push_precollected` for start inventory
* applying `self.multiworld.push_precollected` for world defined start inventory
* `required_client_version: Tuple[int, int, int]`
Optional client version as tuple of 3 ints to make sure the client is compatible to
this world (e.g. implements all required features) when connecting.
Expand All @@ -437,31 +437,32 @@ In addition, the following methods can be implemented and are called in this ord
* `stage_assert_generate(cls, multiworld)` is a class method called at the start of
generation to check the existence of prerequisite files, usually a ROM for
games which require one.
* `def generate_early(self)`
called per player before any items or locations are created. You can set
properties on your world here. Already has access to player options and RNG.
* `def create_regions(self)`
* `generate_early(self)`
called per player before any items or locations are created. You can set properties on your world here. Already has
access to player options and RNG. This is the earliest step where the world should start setting up for the current
multiworld as any steps before this, the multiworld itself is still getting set up
* `create_regions(self)`
called to place player's regions and their locations into the MultiWorld's regions list. If it's
hard to separate, this can be done during `generate_early` or `create_items` as well.
* `def create_items(self)`
* `create_items(self)`
called to place player's items into the MultiWorld's itempool. After this step all regions and items have to be in
the MultiWorld's regions and itempool, and these lists should not be modified afterwards.
* `def set_rules(self)`
* `set_rules(self)`
called to set access and item rules on locations and entrances.
Locations have to be defined before this, or rule application can miss them.
* `def generate_basic(self)`
* `generate_basic(self)`
called after the previous steps. Some placement and player specific
randomizations can be done here.
* `pre_fill`, `fill_hook` and `post_fill` are called to modify item placement
* `pre_fill(self)`, `fill_hook(self)` and `post_fill(self)` are called to modify item placement
before, during and after the regular fill process, before `generate_output`.
If items need to be placed during pre_fill, these items can be determined
and created using `get_prefill_items`
* `def generate_output(self, output_directory: str)` that creates the output
* `generate_output(self, output_directory: str)` that creates the output
files if there is output to be generated. When this is
called, `self.multiworld.get_locations(self.player)` has all locations for the player, with
attribute `item` pointing to the item.
`location.item.player` can be used to see if it's a local item.
* `fill_slot_data` and `modify_multidata` can be used to modify the data that
* `fill_slot_data(self)` and `modify_multidata(self, multidata: Dict[str, Any])` can be used to modify the data that
will be used by the server to host the MultiWorld.


Expand All @@ -478,17 +479,17 @@ def generate_early(self) -> None:
```python
# we need a way to know if an item provides progress in the game ("key item")
# this can be part of the items definition, or depend on recipe randomization
from .Items import is_progression # this is just a dummy
from .items import is_progression # this is just a dummy

def create_item(self, item: str):
def create_item(self, item: str) -> MyGameItem:
# This is called when AP wants to create an item by name (for plando) or
# when you call it from your own code.
classification = ItemClassification.progression if is_progression(item) else \
ItemClassification.filler
return MyGameItem(item, classification, self.item_name_to_id[item],
self.player)

def create_event(self, event: str):
def create_event(self, event: str) -> MyGameItem:
# while we are at it, we can also add a helper to create events
return MyGameItem(event, True, None, self.player)
```
Expand Down Expand Up @@ -581,7 +582,7 @@ def generate_basic(self) -> None:

```python
from worlds.generic.Rules import add_rule, set_rule, forbid_item
from Items import get_item_type
from .items import get_item_type


def set_rules(self) -> None:
Expand Down Expand Up @@ -650,12 +651,12 @@ Please do this with caution and only when necessary.
#### Sample

```python
# Logic.py
# logic.py

from worlds.AutoWorld import LogicMixin

class MyGameLogic(LogicMixin):
def mygame_has_key(self, player: int):
def mygame_has_key(self, player: int) -> bool:
# Arguments above are free to choose
# MultiWorld can be accessed through self.multiworld, explicitly passing in
# MyGameWorld instance for easy options access is also a valid approach
Expand All @@ -665,22 +666,22 @@ class MyGameLogic(LogicMixin):
# __init__.py

from worlds.generic.Rules import set_rule
import .Logic # apply the mixin by importing its file
import .logic # apply the mixin by importing its file

class MyGameWorld(World):
# ...
def set_rules(self):
def set_rules(self) -> None:
set_rule(self.multiworld.get_location("A Door", self.player),
lambda state: state.mygame_has_key(self.player))
```

### Generate Output

```python
from .Mod import generate_mod
from .mod import generate_mod


def generate_output(self, output_directory: str):
def generate_output(self, output_directory: str) -> None:
# How to generate the mod or ROM highly depends on the game
# if the mod is written in Lua, Jinja can be used to fill a template
# if the mod reads a json file, `json.dump()` can be used to generate that
Expand All @@ -695,12 +696,10 @@ def generate_output(self, output_directory: str):
# make sure to mark as not remote_start_inventory when connecting if stored in rom/mod
"starter_items": [item.name for item
in self.multiworld.precollected_items[self.player]],
"final_boss_hp": self.final_boss_hp,
# store option name "easy", "normal" or "hard" for difficuly
"difficulty": self.options.difficulty.current_key,
# store option value True or False for fixing a glitch
"fix_xyz_glitch": self.options.fix_xyz_glitch.value,
}

# add needed option results to the dictionary
data.update(self.options.as_dict("final_boss_hp", "difficulty", "fix_xyz_glitch"))
# point to a ROM specified by the installation
src = self.settings.rom_file
# or point to worlds/mygame/data/mod_template
Expand All @@ -724,7 +723,7 @@ data already exists on the server. The most common usage of slot data is to send
to be aware of.

```python
def fill_slot_data(self):
def fill_slot_data(self) -> Dict[str, Any]:
# in order for our game client to handle the generated seed correctly we need to know what the user selected
# for their difficulty and final boss HP
# a dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting
Expand Down Expand Up @@ -776,14 +775,14 @@ from . import MyGameTestBase


class TestChestAccess(MyGameTestBase):
def test_sword_chests(self):
def test_sword_chests(self) -> None:
"""Test locations that require a sword"""
locations = ["Chest1", "Chest2"]
items = [["Sword"]]
# this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
self.assertAccessDependency(locations, items)

def test_any_weapon_chests(self):
def test_any_weapon_chests(self) -> None:
"""Test locations that require any weapon"""
locations = [f"Chest{i}" for i in range(3, 6)]
items = [["Sword"], ["Axe"], ["Spear"]]
Expand Down