Skip to content
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

[WIP] FIX: Incomplete objects tree for some games #54

Open
wants to merge 85 commits into
base: master
Choose a base branch
from

Conversation

MarcCote
Copy link
Collaborator

@MarcCote MarcCote commented Nov 10, 2021

This PR improves support for the games in Jericho.

Here are the main modifications/improvements of this PR.

  • Updating the objects count that was off from some games. Having the wrong number of objects leads to an incomplete object tree which is used to represent the game state, hence to track world_changed. Furthermore, this affects valid actions detection which uses the game state to know whether an action had any effect in the world.

  • Instead of relying on world_diff, world_changed now relies on

    • the difference between the objects tree before and after the execution of a command
    • special addresses in the RAM
    • a different Returning Program Counter (RetPC)
    • victory() > 0
    • game_over() > 0.
  • Re-validating all the games to make sure that relevant commands from the walkthrough lead to a world_changed.

  • (in progress) Re-validating all the games to make sure that "noop" commands (e.g., look, inventory, examine OBJ) do not trigger world_changed.

Bonus additions:

  • As a bonus, some of the walkthroughs were improved (i.e., achieved a higher score).
  • curses.z5 max score is actually 554 instead of 550 (due to a bug in the game, see comment curses.c)
  • murdac.z5 will report 250/250 upon victory (even though the RAM says 249).

Here's the current progress of re-validating the games:

python tools/test_games.py ./roms/* --check

Game Status Noop check
905.z5 OK Done
acorncourt.z5 OK Done
advent.z5 OK Done
adventureland.z5 OK Done
afflicted.z8 OK Done
anchor.z8 OK Done but score 99/100 Done
awaken.z5 OK Done
balances.z5 FAIL Done but score 50/51 Done
ballyhoo.z3 OK Done
curses.z5 OK Done
cutthroat.z3 OK Done
deephome.z5 OK Done
detective.z5 OK Done
dragon.z5 OK Done
enchanter.z3 OK Done
enter.z5 OK Done
gold.z5 OK Done
hhgg.z3 OK Done
hollywood.z3 OK Done
huntdark.z5 OK Done
infidel.z3 OK Done
inhumane.z5 OK Done
jewel.z5 OK Done
karn.z5 OK Done
library.z5 OK Done
loose.z5 OK Done
lostpig.z8 OK Done
ludicorp.z5 OK Done
lurking.z3 OK Done
moonlit.z5 OK Done
murdac.z5 OK Done
night.z5 OK Done
omniquest.z5 OK Done
partyfoul.z8 OK Done
pentari.z5 OK
planetfall.z3 OK
plundered.z3 OK
reverb.z5 OK
seastalker.z3 OK
sherlock.z5 OK
snacktime.z8 OK
sorcerer.z3 OK
spellbrkr.z3 OK
spirit.z5 OK
temple.z5 OK
theatre.z5 OK
trinity.z4 OK
tryst205.z5 OK
weapon.z5 OK
wishbringer.z3 OK
yomomma.z8 OK
zenon.z5 OK
zork1.z5 OK
zork2.z5 OK
zork3.z5 OK
ztuu.z5 OK

@MarcCote MarcCote requested a review from mhauskn November 10, 2021 21:55
@MarcCote
Copy link
Collaborator Author

@mhauskn this PR is really messed at the moment. I'm going to clean it up, but I wanted you to have an update on the fix. I now believe after this PR, we might want to do a major release (Jericho 4.0.0).

Things to look at, changes in the frotz_interface.c (assume all commented code will be deleted), and all the games c files.

Also, I would like you to try out the script that you used to validate world changed when playing the walkthroughs.

@mhauskn
Copy link
Contributor

mhauskn commented Nov 11, 2021

I'm getting the following error trying to install:

src/interface/frotz_interface.c:27:10: fatal error: md5.h: No such file or directory
 #include "md5.h"
          ^~~~~~~
compilation terminated.

I think we used to use the md5.c file to provide standalone md5 hashing, without the need for other dependencies. Is the PR intending to include a md5.h file?

@MarcCote
Copy link
Collaborator Author

My bad. I pushed the missing file. That will enable computing the md5 hash for a game state directly in C instead of having to get the state from Python, then call the hashing function from hashlib (i..e, less data moving around).

@mhauskn
Copy link
Contributor

mhauskn commented Nov 13, 2021

Hey Marc - I've done some small fixes around the get_cleaned_world_diff() - see them here: d59a6c3.

The motivation for these changes is to have a consistent world_diff returned by jericho._get_world_diff() and jericho._filter_candidate_actions(). This change makes both return a 256-length tuple that is comparable.

However, I'm detecting some issues with Zork1's walkthrough:

  • Step 162. NoWorldChange gold_act: Press yellow button, obs: Click.
  • Step 165. NoWorldChange gold_act: Turn bolt with wrench, obs: The sluice gates open and water pours through the dam.

Both of these seem to be issues with world_state not changing when we expect it should.

Here is the script I'm running to detect these issues: https://gist.github.com/mhauskn/861e4983f54a435013f66e9ab44ea308#file-test_walkthrough-py

@MarcCote
Copy link
Collaborator Author

Oh, I should have mentioned I have yet to test the valid_action code. I was focusing on making sure the commands from the walkthroughs appropriately trigger world_changed.

Also, I was thinking of removing the whole world_diff, since now I keep a copy of the previous objects tree that is used to compare against the current objects tree. The valid_action code still needs to be changed to use that.

For Zork1, I have yet to validate it. I'm currently doing planetfall.z3 (see list above). If you want to help, you can start from the bottom and work your way up. Here's my workflow:

  1. Start with test_games.py game.z5 --check --no-precheck (as in my original post above).
  2. If for some commands the world_changed is not triggered when it should have been, run the tools/find_special_ram game.z5 --index no_command which will detect all ram locations that have been changed by that command as well as listing all commands that have changed each detected ram location throughout the walkthrough.
  3. Add the special address to the relevant game.c file or add an exception to SKIP_CHECK_STATE for that command in test_games.py.

*n = 1;
return seastalker_ram_addrs;
*n = 5;
return seastalker_special_ram_addrs;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong variable was returned.

@@ -43,7 +49,7 @@ char* partyfoul_clean_observation(char* obs) {
if (pch != NULL) {
*(pch-2) = '\0';
}
return obs+1;
return obs + strspn(obs, "\n "); // Skip leading newlines and whitespaces.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes a character was missing.

*n = 0;
return NULL;
*n = 1;
return plundered_intro;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to press "Enter" to start the game.

Comment on lines +57 to +60
// Winning messages change depending on the score.
// *** Grunk bring pig back to farm *** // 6 out of 7 points.
// *** Grunk bring pig back to farm and make new friend *** // 7 out of 7 points.
char *victory_text = "*** Grunk bring pig back to farm "; // Works for both 6 and 7 points.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different winning messages depending on how many points we got.

@@ -50,7 +51,7 @@ char* deephome_clean_observation(char* obs) {
}

int deephome_victory() {
char *death_text = "**** You have won ****";
char *death_text = "*** You have won ***";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Death message had too many *

int i;
char mask;
mask = ~(1 << 7);
// Clear attr 24
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says attr 24, but above the attr_idx == 25 was used.

Comment on lines +94 to +97
// return 550;
// Due to a bug, the max score is 554 points instead of 550.
// ref: https://raw.githubusercontent.com/heasm66/walkthroughs/main/curses_walkthrough.txt
return 554;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the max score for curses.

Comment on lines +63 to +64
//char *death_text = "**** You have won ****";
char *death_text = "*** You have won... for now ***";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect winning message?

@@ -522,6 +522,7 @@ void dumb_init_output(void)
{
if (h_version == V3) {
h_config |= CONFIG_SPLITSCREEN;
h_config |= CONFIG_NOSTATUSLINE;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the option to disable status line for V3 games. This simplifies the clean_observation_... functions.

@@ -1554,7 +1554,7 @@ void z_show_status (void)
/* One V5 game (Wishbringer Solid Gold) contains this opcode by
accident, so just return if the version number does not fit */

if (h_version >= V4)
if (h_version >= V4 || h_config & CONFIG_NOSTATUSLINE)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip printing the status line when the corresponding option is set. This simplifies the clean_observation_... functions.

@MarcCote
Copy link
Collaborator Author

@mhauskn I'm back working on it. I've done the initial validation for a few more games.

Also, I've left a couple of comments (see above) to show some bug fixes I found. Those should probably go in a separate PR to be merged in the current version of Jericho (i.e., v3).

@MarcCote MarcCote force-pushed the fix_obj_num branch 2 times, most recently from edbb67d to ec936df Compare November 24, 2021 13:56
Comment on lines +60 to +63
// Extract time from status line. E.g.
// " Vestibule [...] Saturday 5:01:00 a.m. Score: 0"
// ^ ^
// 88 117
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the time to the observation. I think it is crucial for Sherlock.z5

@MarcCote MarcCote force-pushed the fix_obj_num branch 2 times, most recently from 49cde86 to 0bd314d Compare November 25, 2021 16:32
@MarcCote
Copy link
Collaborator Author

However, I'm detecting some issues with Zork1's walkthrough:

  • Step 162. NoWorldChange gold_act: Press yellow button, obs: Click.
  • Step 165. NoWorldChange gold_act: Turn bolt with wrench, obs: The sluice gates open and water pours through the dam.

Now detected correctly (see ab86cb5).

@@ -1756,21 +2030,7 @@ int filter_candidate_actions(char *candidate_actions, char *valid_actions, zword
act_newline = malloc(strlen(act) + 2);
strcpy(act_newline, act);
strcat(act_newline, "\n");

// Step: Code is copied due to inexplicable segfault when calling step() directly; Ugh!
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhauskn I figured out the segfault. There's a step function in regexp.c (see https://code.woboq.org/userspace/glibc/misc/regexp.c.html#50). It was clashing with the one in Jericho, so I renamed it.

@mhauskn
Copy link
Contributor

mhauskn commented Dec 16, 2021

Hey Marc wanted to touch base on this PR. Is it still in-progress or in a finished state? If the latter - I will be happy to run some checks on it.

@MarcCote
Copy link
Collaborator Author

I finally have some time now to tidy it up. One thing that is still missing is to check whether no-op action (e.g., inventory, examine obj, look) doesn't change the state of the environment (e.g., thief is moving around).

@mhauskn
Copy link
Contributor

mhauskn commented Dec 20, 2021

I tested for false-positive world changes by attempting a combination of valid and invalid actions. Invalid actions included things like "z/wait/inventory/x me/navigating-into-a-dead-end" etc. Below are my findings:

curses, ballyhoo, gold, hhgg, inhumane, lostpig, omniquest, pentari, planetfall, tryst205, zork3, ztuu - all actions are triggering world changed.

huntdark - after going "left", all actions started triggering world changes (maybe there is a timer for bleeding out). Doesn't seem to happen if going "right".
moonlit.z5 - many of the failed navigation actions are triggering false-positive world changes.
weapon - going south results in false-positive world changed.

Also a handful of games are giving false-positive world changes the first time "inventory" command is issued, but not on subsequent invokations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants