Skip to content

Test custom minigame rewards #479

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
28 changes: 28 additions & 0 deletions tests/desmume/emulator_utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from abc import ABC, abstractmethod
from collections.abc import Callable, Generator
from contextlib import contextmanager
import logging
from pathlib import Path
import struct

from desmume.emulator import SCREEN_HEIGHT, SCREEN_WIDTH
import pytesseract

from ph_rando.patcher._items import ITEMS
from ph_rando.shuffler.aux_models import Area

logger = logging.getLogger(__name__)


class AbstractEmulatorWrapper(ABC):
video = None
Expand Down Expand Up @@ -109,6 +113,10 @@ def reset(self):
def load_battery_file(self, test_name: str, rom_path: Path):
raise NotImplementedError

@abstractmethod
def screenshot(self):
raise NotImplementedError

@property
def event_flag_base_addr(self) -> int:
addr = int.from_bytes(self.read_memory(start=0x27E0F74, stop=0x27E0F78), 'little')
Expand Down Expand Up @@ -282,3 +290,23 @@ def cancel_spawn(addr: int, size: int):

# Clear callback on context manager exit
emu_instance.set_exec_breakpoint(0x20C3FE8, None)


def assert_text_displayed(emu_instance: AbstractEmulatorWrapper, text: str) -> None:
"""
Asserts that the given text is displayed on the screen.
"""
from .desmume import DeSmuMEWrapper

if not isinstance(emu_instance, DeSmuMEWrapper):
logger.warning('Text assertion only supported for DeSmuME emulator.')
return

screenshot = emu_instance.screenshot()

# Check if the text is correct
ocr_text: str = pytesseract.image_to_string(screenshot.crop((24, 325, 231, 384))).replace(
'\u2019', "'"
)

assert text in ocr_text
3 changes: 3 additions & 0 deletions tests/desmume/melonds.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,6 @@ def reset(self):
def stop(self):
logger.debug('Stopping MelonDS')
self.destroy()

def screenshot(self):
pytest.skip('MelonDSWrapper does not support screenshots')
15 changes: 8 additions & 7 deletions tests/desmume/test_chest_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from desmume.emulator import SCREEN_WIDTH
from ndspy.rom import NintendoDSRom
import pytesseract
import pytest

from ph_rando.common import ShufflerAuxData
Expand All @@ -11,7 +10,12 @@
from ph_rando.shuffler.aux_models import Chest, Item

from .conftest import GOT_ITEM_TEXT, ITEM_MEMORY_OFFSETS
from .emulator_utils import AbstractEmulatorWrapper, assert_item_is_picked_up, start_first_file
from .emulator_utils import (
AbstractEmulatorWrapper,
assert_item_is_picked_up,
assert_text_displayed,
start_first_file,
)
from .melonds import MelonDSWrapper


Expand Down Expand Up @@ -71,11 +75,8 @@ def test_custom_chest_items(
chest_test_emu.wait(800)

# Check if the "got item" text is correct
if hasattr(chest_test_emu, 'screenshot') and item_id in GOT_ITEM_TEXT:
ocr_text: str = pytesseract.image_to_string(
chest_test_emu.screenshot().crop((24, 325, 231, 384))
).replace('\u2019', "'")
assert GOT_ITEM_TEXT[item_id] in ocr_text
if item_id in GOT_ITEM_TEXT:
assert_text_displayed(chest_test_emu, GOT_ITEM_TEXT[item_id])

chest_test_emu.touch_set_and_release((0, 0), 2)
chest_test_emu.wait(200)
Expand Down
Binary file not shown.
218 changes: 218 additions & 0 deletions tests/desmume/test_minigame_reward_chests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
from pathlib import Path

from desmume.emulator import SCREEN_HEIGHT, SCREEN_WIDTH
from ndspy.rom import NintendoDSRom
import pytest

from ph_rando.common import ShufflerAuxData
from ph_rando.patcher._items import ITEMS_REVERSED
from ph_rando.patcher._util import GD_MODELS, _patch_minigame_items
from ph_rando.shuffler.aux_models import Item, MinigameRewardChest
from tests.desmume.conftest import GOT_ITEM_TEXT, ITEM_MEMORY_OFFSETS
from tests.desmume.emulator_utils import assert_item_is_picked_up

from .emulator_utils import AbstractEmulatorWrapper, assert_text_displayed, start_first_file
from .melonds import MelonDSWrapper


@pytest.fixture
def minigame_reward_chest_emu(
rom_path: Path,
emulator: AbstractEmulatorWrapper,
request,
aux_data: ShufflerAuxData,
):
if isinstance(emulator, MelonDSWrapper):
pytest.skip('MelonDS not supported for this test yet')

rom = NintendoDSRom.fromFile(rom_path)
chests = [
chest
for area in aux_data.areas
for room in area.rooms
for chest in room.chests
if type(chest) is MinigameRewardChest
]
for chest in chests:
chest.contents = Item(name=ITEMS_REVERSED[request.param], states=set())

_patch_minigame_items(aux_data.areas, rom)

rom.saveToFile(rom_path)

emulator.open(str(rom_path))

return emulator


@pytest.mark.parametrize(
'minigame_reward_chest_emu',
[val for val in ITEM_MEMORY_OFFSETS.keys()],
ids=[f'{hex(val)}-{GD_MODELS[val]}' for val in ITEM_MEMORY_OFFSETS.keys()],
indirect=['minigame_reward_chest_emu'],
)
def test_minigame_reward_chests(minigame_reward_chest_emu: AbstractEmulatorWrapper, request):
item_id: int = request.node.callspec.params['minigame_reward_chest_emu']

start_first_file(minigame_reward_chest_emu)

# Skip "arriving in port" cutscene
minigame_reward_chest_emu.touch_set_and_release((SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2), 1)

# Walk left from boat
minigame_reward_chest_emu.touch_set_and_release((0, SCREEN_HEIGHT // 2), 160)

# Walk up
minigame_reward_chest_emu.touch_set_and_release((SCREEN_WIDTH // 2, 0), 50)

# Walk to goron
minigame_reward_chest_emu.touch_set_and_release((0, SCREEN_HEIGHT // 2), 150) # left
minigame_reward_chest_emu.touch_set_and_release((0, SCREEN_HEIGHT), 20) # left and down
minigame_reward_chest_emu.touch_set_and_release((0, SCREEN_HEIGHT // 2), 20) # left
minigame_reward_chest_emu.touch_set_and_release((SCREEN_WIDTH // 2, 0), 5) # up
minigame_reward_chest_emu.wait(20)

# Talk to goron
minigame_reward_chest_emu.touch_set_and_release((165, 65), 5) # touch goron
minigame_reward_chest_emu.wait(165)
minigame_reward_chest_emu.touch_set_and_release(
(SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5
) # advance dialog
minigame_reward_chest_emu.wait(130)
minigame_reward_chest_emu.touch_set_and_release((206, 90), 5) # click "Yes" to play game

# advance dialog
for _ in range(6):
minigame_reward_chest_emu.wait(150)
minigame_reward_chest_emu.touch_set_and_release((SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5)

minigame_reward_chest_emu.wait(20)
minigame_reward_chest_emu.touch_set_and_release((206, 90), 5) # click "Yes" to play game

# advance dialog
for _ in range(11):
minigame_reward_chest_emu.wait(150)
minigame_reward_chest_emu.touch_set_and_release((SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5)

minigame_reward_chest_emu.wait(300)

# Start rolling
for x in range(40):
minigame_reward_chest_emu.touch_set((SCREEN_WIDTH // 2) - x * 2, SCREEN_HEIGHT // 2)
minigame_reward_chest_emu.wait(1)

minigame_reward_chest_emu.wait(115)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(80)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(30)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(20)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(60)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(30)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(35)

minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(30)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(10)
minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(50)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(80)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(190)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(20)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(70)
minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(70)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(80)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(55)
minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up

minigame_reward_chest_emu.wait(23)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2 + 10) # Right
minigame_reward_chest_emu.wait(45)

minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(35)

minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(50)

minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(60)
minigame_reward_chest_emu.touch_release()

minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(100)

minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down

# # Wait for door to open
minigame_reward_chest_emu.wait(260)

minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(50)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, SCREEN_HEIGHT) # Down
minigame_reward_chest_emu.wait(50)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH, SCREEN_HEIGHT // 2) # Right
minigame_reward_chest_emu.wait(60)
minigame_reward_chest_emu.touch_set(SCREEN_WIDTH // 2, 0) # Up
minigame_reward_chest_emu.wait(40)
minigame_reward_chest_emu.touch_set(0, SCREEN_HEIGHT // 2) # Left
minigame_reward_chest_emu.wait(200)
minigame_reward_chest_emu.touch_release() # End of race

minigame_reward_chest_emu.wait(80)
minigame_reward_chest_emu.touch_set_and_release(
(SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5
) # advance dialog

minigame_reward_chest_emu.wait(200)
minigame_reward_chest_emu.touch_set_and_release(
(SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5
) # advance dialog
minigame_reward_chest_emu.wait(150)
minigame_reward_chest_emu.touch_set_and_release(
(SCREEN_HEIGHT, SCREEN_WIDTH // 2), 5
) # advance dialog
minigame_reward_chest_emu.wait(50)

minigame_reward_chest_emu.touch_set_and_release((SCREEN_WIDTH // 2, SCREEN_HEIGHT), 15) # Down
minigame_reward_chest_emu.touch_set_and_release((SCREEN_WIDTH, SCREEN_HEIGHT // 2), 30) # Right

with assert_item_is_picked_up(item_id, minigame_reward_chest_emu):
minigame_reward_chest_emu.wait(10)
minigame_reward_chest_emu.touch_set_and_release((107, 67), 2) # Open chest

# Wait for "Got item" text
minigame_reward_chest_emu.wait(800)

# Check if the "got item" text is correct
if item_id in GOT_ITEM_TEXT:
assert_text_displayed(minigame_reward_chest_emu, GOT_ITEM_TEXT[item_id])

minigame_reward_chest_emu.touch_set_and_release((0, 0), 2)
minigame_reward_chest_emu.wait(200)
minigame_reward_chest_emu.touch_set_and_release((0, 0), 2)
minigame_reward_chest_emu.wait(100)
minigame_reward_chest_emu.touch_set_and_release((0, 0), 2)
minigame_reward_chest_emu.wait(100)
Loading