-
Notifications
You must be signed in to change notification settings - Fork 516
Metroid ii wrapper #362
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
base: master
Are you sure you want to change the base?
Metroid ii wrapper #362
Changes from 7 commits
24a9476
328bf01
f9dfd63
43fc5b7
18f0d88
ed59aa0
89e3040
7436cf3
d1a9dc5
556da6f
2aa0eb8
8d6a06d
c15abf7
dee0ffd
18623f0
3cdb781
0bf4232
8f417f7
715cbb0
80c2df3
28b6711
20a960a
9822969
0ef64b1
17dc6f3
9053e4a
0ef41fb
daa2224
6089c29
6ef4a4a
b9279d4
56b1097
8b92512
58d39d0
baa4168
43090e8
e763918
aeec17e
55f9137
3c45e45
ce0f6e5
e4e1b2a
99b8e35
1631883
2d198f6
f99a4be
687224f
e5b2957
68f67da
c57e94e
0d3a22a
3792008
e85ab7a
9aa166e
603c456
a030553
084935e
18b656e
8d6a2eb
17fa2cd
293a5fc
1ba2744
1a8ef18
139f6ff
1b7fd56
0e8466a
e7bd4cd
c5e9747
63cfc5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # | ||
| # License: See LICENSE.md file | ||
| # GitHub: https://github.com/Baekalfen/PyBoy | ||
| # | ||
| cimport cython | ||
| from libc.stdint cimport uint8_t | ||
|
|
||
| from pyboy.logging.logging cimport Logger | ||
| from pyboy.plugins.base_plugin cimport PyBoyGameWrapper | ||
|
|
||
|
|
||
| cdef Logger logger | ||
|
|
||
| cdef int ROWS, COLS | ||
|
|
||
|
|
||
| cdef class GameWrapperMetroidII(PyBoyGameWrapper): | ||
| cdef readonly int _game_over | ||
| cdef readonly int x_pos_pixels | ||
| cdef readonly int y_pos_pixels | ||
| cdef readonly int x_pos_area | ||
| cdef readonly int y_pos_area | ||
| cdef readonly int pose | ||
|
|
||
| cdef readonly int samus_facing | ||
| cdef readonly int current_major_upgrades | ||
| cdef readonly int water_info | ||
| cdef readonly int current_beam | ||
| cdef readonly int current_e_tanks | ||
| cdef readonly int current_hp | ||
| cdef readonly int current_full_e_tanks | ||
| cdef readonly int current_missiles | ||
| cdef readonly int current_missile_capacity | ||
| cdef readonly int displayed_hp | ||
| cdef readonly int displayed_missiles | ||
| cdef readonly int global_metroid_count | ||
| cdef readonly int local_metroid_count | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| # | ||
| # License: See LICENSE.md file | ||
| # GitHub: https://github.com/Baekalfen/PyBoy | ||
| # | ||
| __pdoc__ = { | ||
| "GameWrapperMetroidII.cartridge_title": False, | ||
| "GameWrapperMetroidII.post_tick": False, | ||
| } | ||
|
|
||
| import pyboy | ||
| from pyboy import utils | ||
| from pyboy.utils import WindowEvent | ||
| # for memory | ||
| from pyboy.utils import bcd_to_dec | ||
|
|
||
| from .base_plugin import PyBoyGameWrapper | ||
|
|
||
| logger = pyboy.logging.get_logger(__name__) | ||
|
|
||
|
|
||
| class GameWrapperMetroidII(PyBoyGameWrapper): | ||
| """ | ||
| This class wraps Metroid II, and provides easy access for AIs. | ||
| If you call `print` on an instance of this object, it will show an overview of everything this object provides. | ||
| """ | ||
| cartridge_title = "METROID2" | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| self._game_over = False | ||
| # may need to change game area section. Copied from the kirby code | ||
|
||
| super().__init__(*args, game_area_section=(0, 0, 20, 16), game_area_follow_scxy=True, **kwargs) | ||
|
||
|
|
||
| def post_tick(self): | ||
| self._tile_cache_invalid = True | ||
| self._sprite_cache_invalid = True | ||
|
|
||
| # Access RAM for a ton of information about the game | ||
| # I'm getting all of the values from datacrystal | ||
| # https://datacrystal.tcrf.net/wiki/Metroid_II:_Return_of_Samus/RAM_map | ||
| # I've skipped some of the values like music related ones | ||
|
|
||
| # Constants from decomp project | ||
| # https://github.com/alex-west/M2RoS/blob/main/SRC/constants.asm | ||
|
||
|
|
||
|
|
||
| # Check screen for the word "GAME" (as in GAME OVER) | ||
| # this could be done much faster by checking health | ||
| # There's a death animation so the extra few seconds over thousands of | ||
| # runs *could* add up? | ||
|
||
| if self.tilemap_background[6:10, 8] == [342, 336, 348, 340]: | ||
| self._game_over = True | ||
| # No need to update the rest of the values, we're dead! | ||
| return | ||
|
|
||
| # X position within area (pixels/screen) | ||
| self.x_pos_pixels = self.pyboy.memory[0xD027] | ||
| self.y_pos_pixels = self.pyboy.memory[0xD029] | ||
| self.x_pos_area = self.pyboy.memory[0xD028] | ||
| self.y_pos_area = self.pyboy.memory[0xD02A] | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between "pixels" and "area"? |
||
|
|
||
| self.pose = self.pyboy.memory[0xD020] | ||
|
|
||
| self.samus_facing = self.pyboy.memory[0xD02B] | ||
| self.current_major_upgrades = self.pyboy.memory[0xD045] | ||
| # Yeah I have no idea, just says "related to interacting with water" | ||
| self.water_info = self.pyboy.memory[0xD048] | ||
|
||
|
|
||
| # 8 bit (0b0000 1000) is missles mode | ||
| self.current_beam = bcd_to_dec(self.pyboy.memory[0xD04D]) | ||
|
|
||
| self.current_e_tanks = bcd_to_dec(self.pyboy.memory[0xD050]) | ||
| self.current_hp = bcd_to_dec(self.pyboy.memory[0xD051]) | ||
| self.current_full_e_tanks = self.pyboy.memory[0xD052] | ||
| self.current_missiles = bcd_to_dec(self.pyboy.memory[0xD053]) | ||
|
|
||
| # Question mark next to this one on datacrystal | ||
| # self.num_sprites_onscreen = self.pyboy.memory[0xD064] | ||
|
||
|
|
||
| self.current_missile_capacity = bcd_to_dec(self.pyboy.memory[0xD081]) | ||
|
||
|
|
||
| self.displayed_hp = self.pyboy.memory[0xD084] | ||
| self.displayed_missiles = bcd_to_dec(self.pyboy.memory[0xD086]) | ||
|
Comment on lines
+94
to
+95
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would displayed and current HP/missiles be different? |
||
| self.global_metroid_count = bcd_to_dec(self.pyboy.memory[0xD09A]) | ||
| self.local_metroid_count = bcd_to_dec(self.pyboy.memory[0xD0A7]) | ||
|
|
||
| # TODO calculate health percent using E tank counts (?) | ||
|
||
|
|
||
|
|
||
|
|
||
| def start_game(self, timer_div=None): | ||
| """ | ||
| Call this function right after initializing PyBoy. This will navigate through menus to start the game at the | ||
| first playable state. | ||
|
|
||
| The state of the emulator is saved, and using `reset_game`, you can get back to this point of the game | ||
| instantly. | ||
|
|
||
| Kwargs: | ||
| * timer_div (int): Replace timer's DIV register with this value. Use `None` to randomize. | ||
| """ | ||
|
|
||
| # NOTE, I may be able to make these tick waits shorter | ||
| self.pyboy.tick(False) | ||
| self.pyboy.button("start") | ||
| self.pyboy.tick(300, False) | ||
|
|
||
| # start the game | ||
| self.pyboy.button("start") | ||
| # wait for samus's "blinking" to stop | ||
| self.pyboy.tick(500, False) | ||
|
|
||
|
||
| PyBoyGameWrapper.start_game(self, timer_div=timer_div) | ||
|
|
||
|
|
||
| def reset_game(self, timer_div=None): | ||
| """ | ||
| After calling `start_game`, you can call this method at any time to reset the game. | ||
|
|
||
| Kwargs: | ||
| * timer_div (int): Replace timer's DIV register with this value. Use `None` to randomize. | ||
| """ | ||
| # TODO implement me, I don't know if I really need to do anything here? | ||
|
||
|
|
||
| self._game_over = False | ||
|
|
||
| PyBoyGameWrapper.reset_game(self, timer_div=timer_div) | ||
|
|
||
| def game_area(self): | ||
| """ | ||
| Use this method to get a matrix of the "game area" of the screen. | ||
|
|
||
| ```text | ||
| 0 1 2 3 4 5 6 7 8 9 | ||
| ____________________________________________________________________________________ | ||
| 0 | 383 383 383 301 383 383 383 297 383 383 383 301 383 383 383 297 383 383 383 293 | ||
| 1 | 383 383 383 383 300 294 295 296 383 383 383 383 300 294 295 296 383 383 299 383 | ||
| 2 | 311 318 319 320 383 383 383 383 383 383 383 383 383 383 383 383 383 301 383 383 | ||
| 3 | 383 383 383 321 322 383 383 383 383 383 383 383 383 383 383 383 383 383 300 294 | ||
| 4 | 383 383 383 383 323 290 291 383 383 383 313 312 311 318 319 320 383 290 291 383 | ||
| 5 | 383 383 383 383 324 383 383 383 383 315 314 383 383 383 383 321 322 383 383 383 | ||
| 6 | 383 383 383 383 324 293 292 383 383 316 383 383 383 383 383 383 323 383 383 383 | ||
| 7 | 383 383 383 383 324 383 383 298 383 317 383 383 383 383 383 383 324 383 383 383 | ||
| 8 | 319 320 383 383 324 383 383 297 383 317 383 383 383 152 140 383 324 383 383 307 | ||
| 9 | 383 321 322 383 324 294 295 296 383 325 383 383 383 383 383 383 326 272 274 309 | ||
| 10 | 383 383 323 383 326 383 383 383 2 18 383 330 331 331 331 331 331 331 331 331 | ||
| 11 | 274 383 324 272 274 272 274 272 274 272 274 334 328 328 328 328 328 328 328 328 | ||
| 12 | 331 331 331 331 331 331 331 331 331 331 331 328 328 328 328 328 328 328 328 328 | ||
| 13 | 328 328 328 277 278 328 328 328 328 328 328 328 328 277 278 328 328 277 278 277 | ||
| 14 | 328 277 278 279 281 277 278 328 328 277 278 277 278 279 281 277 278 279 281 279 | ||
| 15 | 278 279 281 280 282 279 281 277 278 279 281 279 281 280 282 279 281 280 282 280 | ||
| ``` | ||
|
Comment on lines
+147
to
+166
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember to update this view, as this is copied from Kirby. Even better, you can make it a test as in the Super Mario Land wrapper (without the mapping): >>> pyboy = PyBoy(supermarioland_rom)
>>> pyboy.game_wrapper.start_game()
>>> pyboy.game_wrapper.game_area_mapping(pyboy.game_wrapper.mapping_compressed, 0)
>>> pyboy.game_wrapper.game_area()
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 14, 14, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 14, 14, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]], dtype=uint32) |
||
|
|
||
| Returns | ||
| ------- | ||
| memoryview: | ||
| Simplified 2-dimensional memoryview of the screen | ||
| """ | ||
| return PyBoyGameWrapper.game_area(self) | ||
|
|
||
| def game_over(self): | ||
| return self._game_over | ||
|
|
||
| def __repr__(self): | ||
| # yapf: disable | ||
| return ( | ||
| f"Metroid II:\n" + | ||
| # TODO add relevant variables to print for debugging purposes | ||
|
||
| f"XY Pixels: {(self.x_pos_pixels, self.y_pos_pixels)}\n" + | ||
| f"XY Area: {(self.x_pos_area, self.y_pos_area)}\n" + | ||
| f"Pose: {self.pose}\t" + | ||
| f"Facing: {self.samus_facing}\n" + | ||
| f"Upgrades: {self.current_major_upgrades}\n" + | ||
| f"Water Info: {self.water_info}\n" + | ||
| f"Curr Beam: {self.current_beam}\n" + | ||
| f"Curr E tanks: {self.current_e_tanks}\n" + | ||
| f"Curr HP: {self.current_hp}\n" + | ||
| f"Curr Full E Tanks: {self.current_full_e_tanks}\n" + | ||
| f"Curr missiles: {self.current_missiles}\n" + | ||
| f"Curr missile capacity: {self.current_missile_capacity}\n" + | ||
| f"Displayed HP: {self.displayed_hp}\n" + | ||
| f"Displayed Missiles: {self.displayed_missiles}\n"+ | ||
| f"GMC: {self.global_metroid_count}\n" + | ||
| f"LMC: {self.global_metroid_count}\n" | ||
| # I don't like seeing the huge grid whne trying to develop | ||
| # The sprite list can be messy too, since many things (especially | ||
| # samus) are composed of multiple sprites, so simply running causes | ||
| # the output to constantly change "shape", making it very difficult | ||
| # to read | ||
| # super().__repr__() | ||
|
||
| ) | ||
| # yapf: enable | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readonlymakes it available on the API. No need to have this, when we have thegame_overmethod as well.