diff --git a/aimmo-game-worker/simulation/errors.py b/aimmo-game-worker/simulation/errors.py new file mode 100644 index 000000000..245e7102d --- /dev/null +++ b/aimmo-game-worker/simulation/errors.py @@ -0,0 +1,10 @@ +class Error(Exception): + """Base class for other exceptions""" + + pass + + +class NoNearbyArtefactsError(Error): + """Raised when there are no nearby artefacts""" + + pass diff --git a/aimmo-game-worker/simulation/utils.py b/aimmo-game-worker/simulation/utils.py new file mode 100644 index 000000000..a370da7cc --- /dev/null +++ b/aimmo-game-worker/simulation/utils.py @@ -0,0 +1,23 @@ +from typing import TypeVar, List + +from .errors import NoNearbyArtefactsError + + +T = TypeVar("T") + + +class NearbyArtefactsList(List[T]): + """ + A list-like object that raises NoNearbyArtefactsError when trying to access an element and the list is empty. + """ + + def __getitem__(self, i): + try: + return super().__getitem__(i) + except IndexError: + if len(self) == 0: + raise NoNearbyArtefactsError( + "There aren't any nearby artefacts, you need to move closer!" + ) from None + else: + raise diff --git a/aimmo-game-worker/simulation/world_map.py b/aimmo-game-worker/simulation/world_map.py index a801bfa93..37fb2f9a4 100644 --- a/aimmo-game-worker/simulation/world_map.py +++ b/aimmo-game-worker/simulation/world_map.py @@ -1,10 +1,11 @@ from collections import defaultdict, namedtuple from enum import Enum +from typing import Dict, List from .avatar_state import create_avatar_state from .location import Location -from typing import Dict, List from .pathfinding import astar +from .utils import NearbyArtefactsList # how many nearby artefacts to return SCAN_LIMIT = 3 @@ -168,7 +169,9 @@ def _scan_artefacts(self, start_location, radius): artefacts.append(cell) return artefacts - def scan_nearby(self, avatar_location, radius=SCAN_RADIUS) -> List[dict]: + def scan_nearby( + self, avatar_location, radius=SCAN_RADIUS + ) -> NearbyArtefactsList[dict]: """ From the given location point search the given radius for artefacts. Returns list of nearest artefacts (artefact/interactable represented as dict). @@ -197,7 +200,7 @@ def scan_nearby(self, avatar_location, radius=SCAN_RADIUS) -> List[dict]: if len(nearest) > SCAN_LIMIT: break - return nearest[:SCAN_LIMIT] + return NearbyArtefactsList(nearest[:SCAN_LIMIT]) def __repr__(self): return repr(self.cells) diff --git a/aimmo-game-worker/tests/tests_simulation/test_world_map.py b/aimmo-game-worker/tests/tests_simulation/test_world_map.py index 0c823b135..4a595c19a 100755 --- a/aimmo-game-worker/tests/tests_simulation/test_world_map.py +++ b/aimmo-game-worker/tests/tests_simulation/test_world_map.py @@ -1,7 +1,9 @@ import pytest +from simulation.errors import NoNearbyArtefactsError from simulation.location import Location -from simulation.world_map import WorldMap, WorldMapCreator, ARTEFACT_TYPES +from simulation.utils import NearbyArtefactsList +from simulation.world_map import ARTEFACT_TYPES, WorldMapCreator @pytest.fixture @@ -152,4 +154,14 @@ def test_scan_nearby(avatar_state_json): cells[4]["interactable"] = {"type": ARTEFACT_TYPES[-1]} map = WorldMapCreator.generate_world_map_from_cells_data(cells) artefacts = map.scan_nearby(Location(-1, 0)) + assert type(artefacts) == NearbyArtefactsList assert len(artefacts) == 1 + with pytest.raises(IndexError): + artefacts[1] + + # Test NoNearbyArtefactsError + artefacts = map.scan_nearby(Location(5, 5), radius=1) + assert type(artefacts) == NearbyArtefactsList + assert len(artefacts) == 0 + with pytest.raises(NoNearbyArtefactsError): + artefacts[0] diff --git a/game_frontend/src/pyodide/webWorker.ts b/game_frontend/src/pyodide/webWorker.ts index faa7ced10..3e22fb9ad 100644 --- a/game_frontend/src/pyodide/webWorker.ts +++ b/game_frontend/src/pyodide/webWorker.ts @@ -76,7 +76,9 @@ export function simplifyErrorMessageInLog(log: string): string { const regexToFindNextTurnErrors = /.*line (\d+), in next_turn\n((?:.|\n)*)/ const matches = log.match(regexToFindNextTurnErrors) if (matches?.length >= 2) { - return `Uh oh! Something isn't correct on line ${matches[1]}. Here's the error we got:\n${matches[2]}` + // get only the exception message line, removing potential traceback + const simpleError = matches[2].split('\n').slice(-2).join('') + return `Uh oh! Something isn't correct on line ${matches[1]}. Here's the error we got:\n${simpleError}` } // error not in next_turn function return log