Commit c56e9589 authored by Deucе's avatar Deucе 👌🏾
Browse files

Add scripted manual combat to gtest (-c fight, -a ACTION)



New "fight" command runs Fight_Fight with AutoFight=false, requiring
player input for each combat round.  Input comes from either:

  -a ACTION   Fixed action (A=attack, R=run, I=parry) applied to
              every combat prompt via a GetAnswer hook
  -s SCRIPT   Script file with Choice=/Key= lines

Combined with -r (fixed random), -a gives fully deterministic manual
combat without a script file.  This makes it easy to test win, loss,
and run scenarios.

Change -r priority to check before script mode so -r and -s can be
combined — the script provides Choice=/Key= lines while -r handles
all Random= calls, avoiding the need to predict the exact number of
random calls per combat round.

Add 10 new integration tests: manual attack win, run away, and
deterministic reproducibility.  Total: 43 gtest tests.

Co-Authored-By: default avatarClaude Opus 4.6 (1M context) <[email protected]>
parent 0af8edcf
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -75,6 +75,8 @@ Usage
gtest requires a -c flag to select a command:

        gtest -c autofight [-l LEVEL] [-r VALUE | -R SEED] [-g GOLD]
        gtest -c fight [-l LEVEL] [-r VALUE] -a ACTION
        gtest -c fight [-l LEVEL] -s SCRIPT
        gtest -c levelup

autofight
@@ -86,6 +88,22 @@ autofight
    -l LEVEL    Mine level (default: 1).  Higher levels produce a
                tougher goblin.

fight
    Same enemy setup as autofight, but runs Fight_Fight with
    AutoFight=false.  Player input must come from either -a (fixed
    action) or -s (script file).

    -a ACTION   Every combat prompt uses this action:
                  A = attack first target
                  R = run away
                  I = parry (skip turn)
                When -a is combined with -r, the fight is fully
                deterministic with no script file needed.

    -s SCRIPT   Script file providing Choice= and Key= lines for
                each combat decision.  Use with -r to avoid needing
                Random= lines.

levelup
    Sets all test clan members to 1000 XP and runs Fight_CheckLevelUp.
    Useful for verifying level-up thresholds and training point awards.
+66 −4
Original line number Diff line number Diff line
@@ -181,6 +181,8 @@ int my_random(int limit)
{
	if (limit <= 0) return 0;
	if (limit == 1) return 0;
	if (g_fixed_rand_set)
		return g_fixed_rand % limit;
	if (script_is_active()) {
		const char *val = script_consume("Random");
		int n = atoi(val);
@@ -188,8 +190,6 @@ int my_random(int limit)
		if (n >= limit) n = limit - 1;
		return n;
	}
	if (g_fixed_rand_set)
		return g_fixed_rand % limit;
	return rand() % limit;
}

@@ -344,13 +344,16 @@ static void usage(void)
	        "Usage: gtest -c COMMAND [options]\n"
	        "\n"
	        "Commands:\n"
	        "  -c autofight   Run Fight_Monster with AI control (no input)\n"
	        "Commands:\n"
	        "  -c autofight   Run Fight_Fight with AI control (no input)\n"
	        "  -c fight       Run Fight_Fight with player input\n"
	        "  -c levelup     Run Fight_CheckLevelUp\n"
	        "\n"
	        "Options:\n"
	        "  -l LEVEL       Mine level for autofight (default: 1)\n"
	        "  -l LEVEL       Mine level (default: 1)\n"
	        "  -R SEED        Seed the PRNG for deterministic results\n"
	        "  -r VALUE       Fixed return value for my_random (mod limit)\n"
	        "  -a ACTION      Default combat action for fight (A/R/I)\n"
	        "  -g GOLD        Set starting gold\n"
	        "  -m LEVEL       Set starting mine level\n"
	        "  -s SCRIPT      Script file for input\n"
@@ -361,6 +364,23 @@ static void usage(void)
 * main
 * ========================================================================= */

static char g_fixed_action = 0;

static char hook_fixed_action(const char *szAllowableChars)
{
	char c = (char)toupper((unsigned char)g_fixed_action);
	for (const char *p = szAllowableChars; *p; p++) {
		if (toupper((unsigned char)*p) == c)
			return c;
	}
	/* Action not in allowable set — try Enter (default) */
	for (const char *p = szAllowableChars; *p; p++) {
		if (*p == '\r' || *p == '\n')
			return *p;
	}
	return szAllowableChars[0];
}

int main(int argc, char *argv[])
{
	char command[32]     = {0};
@@ -406,6 +426,13 @@ int main(int argc, char *argv[])
			g_fixed_rand_set = true;
			g_fixed_rand = atoi(argv[++i]);
			break;
		case 'a':
			if (i + 1 >= argc) {
				fprintf(stderr, "gtest: -a requires an action\n");
				return 1;
			}
			g_fixed_action = argv[++i][0];
			break;
		case 'g':
			arg_gold = atol(a + 2);
			break;
@@ -445,6 +472,10 @@ int main(int argc, char *argv[])
		script_install_hooks();
	}

	/* ---- fixed action hook (overrides script GetAnswer hook) ---- */
	if (g_fixed_action)
		Console_SetGetAnswerHook(hook_fixed_action);

	/* ---- initialise ---- */
	gtest_init();

@@ -486,6 +517,37 @@ int main(int argc, char *argv[])
		PClan.FightsLeft--;
		Fight_CheckLevelUp();
	}
	else if (plat_stricmp(command, "fight") == 0) {
		/* Same as autofight but with AutoFight=false — player input
		   comes from the script via Choice=/Key= hooks. */
		struct clan EnemyClan = {0};
		struct pc *enemy = calloc(1, sizeof(struct pc));
		strlcpy(EnemyClan.szName, "Monsters", sizeof(EnemyClan.szName));
		EnemyClan.Member[0] = enemy;
		strlcpy(enemy->szName, "Goblin", sizeof(enemy->szName));
		enemy->HP = enemy->MaxHP = (int16_t)(20 + mine_level * 5);
		enemy->SP = enemy->MaxSP = 0;
		enemy->Status = Here;
		enemy->Attributes[ATTR_STRENGTH]  = (int8_t)(3 + mine_level);
		enemy->Attributes[ATTR_DEXTERITY] = (int8_t)(3 + mine_level);
		enemy->Attributes[ATTR_AGILITY]   = (int8_t)(3 + mine_level);
		enemy->Attributes[ATTR_WISDOM]    = 1;
		enemy->MyClan = &EnemyClan;
		for (int j = 0; j < MAX_SPELLS_IN_EFFECT; j++)
			enemy->SpellsInEffect[j].SpellNum = -1;

		PClan.FightsLeft = Game.Data.MineFights;
		fight_result = Fight_Fight(&PClan, &EnemyClan, false, true, false);
		Fight_Heal(&PClan);
		Spells_ClearSpells(&PClan);
		FreeClanMembers(&EnemyClan);
		if (fight_result == FT_WON)
			PClan.Points += 5;
		else
			PClan.Points -= 3;
		PClan.FightsLeft--;
		Fight_CheckLevelUp();
	}
	else if (plat_stricmp(command, "levelup") == 0) {
		/* Set XP high enough to trigger level-up */
		for (int i = 0; i < 4; i++) {
+17 −0
Original line number Diff line number Diff line
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
Choice=A
End
+35 −0
Original line number Diff line number Diff line
@@ -151,6 +151,41 @@ assert_succeeds "autofight level 10 runs" \
assert_contains "level 10 has result"   "$STATE" "FightResult="
assert_contains "level 10 has HP"       "$STATE" "Member0.HP="

# =========================================================================
# Manual fight — attack
# =========================================================================

assert_succeeds "fight -a A wins" \
	run_gtest fight -l 1 -r 1 -a A
assert_contains "fight attack wins"     "$STATE" "FightResult=WON"
assert_contains "fight attack points"   "$STATE" "Points=5"
assert_not_contains "fight attack XP"   "$STATE" "Member0.XP=0"

# =========================================================================
# Manual fight — run
# =========================================================================

assert_succeeds "fight -a R runs" \
	run_gtest fight -l 1 -r 1 -a R
assert_contains "fight run result"      "$STATE" "FightResult=RAN"
assert_contains "fight run loses pts"   "$STATE" "Points=-3"

# =========================================================================
# Manual fight — deterministic
# =========================================================================

assert_succeeds "fight -a A -r 1 first" \
	run_gtest fight -l 1 -r 1 -a A
cp "$STATE" "$T/fight1.txt"

assert_succeeds "fight -a A -r 1 second" \
	run_gtest fight -l 1 -r 1 -a A
if diff -q "$T/fight1.txt" "$STATE" >/dev/null 2>&1; then
	pass_test "manual fight deterministic"
else
	fail_test "manual fight deterministic"
fi

# =========================================================================
# Level-up
# =========================================================================