384 lines
14 KiB
C#
384 lines
14 KiB
C#
using Godot;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
public partial class TestRunner : Node
|
|
{
|
|
private int passedTests = 0;
|
|
private int failedTests = 0;
|
|
|
|
public override void _Ready()
|
|
{
|
|
Run("Inventory adds, stacks and removes items", TestInventoryStacksAndRemovesItems);
|
|
Run("Inventory crafting checks stacked totals", TestInventoryCanCraftAcrossStacks);
|
|
Run("Survival consumes stored food and water", TestSurvivalConsumesFoodAndWater);
|
|
Run("Survival death disables movement", TestSurvivalDeathDisablesMovement);
|
|
Run("Robot research effects change robot stats", TestRobotResearchEffects);
|
|
Run("Research completion applies effects once", TestResearchCompletionAppliesEffectsOnce);
|
|
Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes);
|
|
Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged);
|
|
Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
|
|
Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
|
|
Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState);
|
|
Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
|
|
Run("Split save files include one file per saved layer", TestSplitSaveFilesIncludeLayerFiles);
|
|
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
|
|
Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
|
|
Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
|
|
Run("Item data readable names are stable", TestItemDataReadableNames);
|
|
Run("Resource files load core game data", TestResourceFilesLoadCoreData);
|
|
|
|
GD.Print($"Tests passed: {passedTests}, failed: {failedTests}");
|
|
GetTree().Quit(failedTests == 0 ? 0 : 1);
|
|
}
|
|
|
|
private void Run(string name, Action test)
|
|
{
|
|
try
|
|
{
|
|
GameData.ResetRunState();
|
|
test();
|
|
passedTests++;
|
|
GD.Print("[PASS] " + name);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
failedTests++;
|
|
GD.PrintErr("[FAIL] " + name + ": " + exception.Message);
|
|
}
|
|
}
|
|
|
|
private void TestInventoryStacksAndRemovesItems()
|
|
{
|
|
ItemData stone = GameData.availableItems["stone"];
|
|
GameData.inventory.AddItem(new Item { data = stone }, stone.StackSize + 5);
|
|
|
|
AssertEqual(stone.StackSize + 5, GameData.inventory.GetItemAmount("stone"), "stone amount");
|
|
AssertEqual(2, GameData.inventory.items.Count, "stone stack count");
|
|
|
|
bool removed = GameData.inventory.TryRemoveItem("stone", stone.StackSize);
|
|
|
|
AssertTrue(removed, "remove should succeed");
|
|
AssertEqual(5, GameData.inventory.GetItemAmount("stone"), "remaining stone amount");
|
|
}
|
|
|
|
private void TestInventoryCanCraftAcrossStacks()
|
|
{
|
|
ItemData stone = GameData.availableItems["stone"];
|
|
stone.StackSize = 5;
|
|
GameData.inventory.AddItem(new Item { data = stone }, 8);
|
|
|
|
List<Ingredient> ingredients = new List<Ingredient>
|
|
{
|
|
new Ingredient { Item = "stone", Amount = 8 }
|
|
};
|
|
|
|
AssertTrue(GameData.inventory.CanCraft(ingredients, 1), "crafting should see both stacks");
|
|
}
|
|
|
|
private void TestSurvivalConsumesFoodAndWater()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["mushroom"] }, 1);
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 1);
|
|
GameData.survival.hunger = 30f;
|
|
GameData.survival.thirst = 30f;
|
|
|
|
GameData.survival.Update(0.1);
|
|
|
|
AssertTrue(GameData.survival.hunger > 60f, "hunger should recover");
|
|
AssertTrue(GameData.survival.thirst > 65f, "thirst should recover");
|
|
AssertEqual(0, GameData.inventory.GetItemAmount("mushroom"), "mushroom consumed");
|
|
AssertEqual(0, GameData.inventory.GetItemAmount("water"), "water consumed");
|
|
}
|
|
|
|
private void TestSurvivalDeathDisablesMovement()
|
|
{
|
|
GameData.canMove = true;
|
|
GameData.survival.energy = 0.01f;
|
|
|
|
GameData.survival.Update(2.0);
|
|
|
|
AssertTrue(GameData.survival.isDead, "survival should be dead");
|
|
AssertFalse(GameData.canMove, "movement should be disabled");
|
|
}
|
|
|
|
private void TestRobotResearchEffects()
|
|
{
|
|
RobotStats stats = new RobotStats();
|
|
List<ResearchEffect> effects = new List<ResearchEffect>
|
|
{
|
|
new ResearchEffect { Stat = "robot_speed_bonus", Value = 0.25f },
|
|
new ResearchEffect { Stat = "robot_energy_use_reduction", Value = 0.20f },
|
|
new ResearchEffect { Stat = "robot_cooling_bonus", Value = 0.50f }
|
|
};
|
|
|
|
stats.Apply(effects);
|
|
|
|
AssertClose(12.5f, stats.GetMovementSpeed(10f), 0.001f, "movement speed");
|
|
AssertClose(8f, stats.GetEnergyUse(10f), 0.001f, "energy use");
|
|
AssertClose(15f, stats.GetCoolingRate(10f), 0.001f, "cooling");
|
|
}
|
|
|
|
private void TestResearchCompletionAppliesEffectsOnce()
|
|
{
|
|
ResearchData data = new ResearchData
|
|
{
|
|
Id = "test_research",
|
|
Inputs = new List<Ingredient>(),
|
|
Research = "basics",
|
|
CraftTime = 1.0,
|
|
Texture = "",
|
|
Effects = new List<ResearchEffect>
|
|
{
|
|
new ResearchEffect { Stat = "robot_speed_bonus", Value = 0.10f }
|
|
}
|
|
};
|
|
|
|
Research research = new Research(data);
|
|
research.Complete();
|
|
research.Complete();
|
|
|
|
AssertClose(11f, GameData.robotStats.GetMovementSpeed(10f), 0.001f, "research effect applied once");
|
|
}
|
|
|
|
private void TestResourceSaveRoundtrip()
|
|
{
|
|
GameResource resource = new GameResource("stone");
|
|
resource.Extract(2.0);
|
|
|
|
ResourceSaveData saveData = resource.CreateSaveData();
|
|
GameResource loadedResource = GameResource.FromSaveData(saveData);
|
|
|
|
AssertEqual(saveData.Name, loadedResource.CreateSaveData().Name, "resource name");
|
|
AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount");
|
|
}
|
|
|
|
private void TestRobotSaveRoundtrip()
|
|
{
|
|
Robot robot = new Robot
|
|
{
|
|
Name = "Ada",
|
|
Position = new Vector3(1f, 2f, 3f),
|
|
heat = 44f,
|
|
maintenance = 55f,
|
|
isBroken = true,
|
|
isCoolingDown = true,
|
|
currentProgram = "Mining",
|
|
currentMessage = "Needs care",
|
|
robotType = "bronze_robot"
|
|
};
|
|
|
|
RobotSaveData saveData = robot.CreateSaveData();
|
|
Robot loadedRobot = new Robot();
|
|
loadedRobot.LoadSaveData(saveData);
|
|
|
|
AssertEqual("Ada", loadedRobot.Name.ToString(), "robot name");
|
|
AssertEqual("Mining", loadedRobot.currentProgram, "robot program");
|
|
AssertEqual("bronze_robot", loadedRobot.robotType, "robot type");
|
|
AssertClose(44f, loadedRobot.heat, 0.001f, "robot heat");
|
|
AssertClose(55f, loadedRobot.maintenance, 0.001f, "robot maintenance");
|
|
AssertTrue(loadedRobot.isBroken, "robot broken state");
|
|
AssertTrue(loadedRobot.isCoolingDown, "robot cooling state");
|
|
}
|
|
|
|
private void TestSaveDataRestoresGlobalState()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12);
|
|
GameData.survival.hunger = 42f;
|
|
GameData.survival.thirst = 43f;
|
|
GameData.survival.energy = 44f;
|
|
GameData.lowestLayer = 3;
|
|
GameData.availableResearch["stoneage"].Complete();
|
|
|
|
SaveGameData saveData = SaveGameManager.CreateSaveData();
|
|
|
|
GameData.ResetRunState();
|
|
SaveGameManager.ApplyWorldData(saveData);
|
|
|
|
AssertEqual(12, GameData.inventory.GetItemAmount("stone"), "saved inventory");
|
|
AssertClose(42f, GameData.survival.hunger, 0.001f, "saved hunger");
|
|
AssertEqual(3, GameData.lowestLayer, "saved layer");
|
|
AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
|
|
}
|
|
|
|
private void TestResearchExecutionPaysResourcesAndFinishes()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);
|
|
ResearchData researchData = new ResearchData
|
|
{
|
|
Id = "stone_counterweight",
|
|
Inputs = new List<Ingredient>
|
|
{
|
|
new Ingredient { Item = "stone", Amount = 3 }
|
|
},
|
|
Research = "basics",
|
|
CraftTime = 1.0,
|
|
Texture = "",
|
|
Effects = new List<ResearchEffect>()
|
|
};
|
|
|
|
Research research = new Research(researchData);
|
|
ResearchResult result = research.Execute(1.0);
|
|
|
|
AssertEqual(ResearchResult.FINISHED, result, "research result");
|
|
AssertEqual(2, GameData.inventory.GetItemAmount("stone"), "research cost");
|
|
AssertEqual(ResearchState.RESEARCHED, research.state, "research state");
|
|
}
|
|
|
|
private void TestInventoryAddFailureKeepsInventoryUnchanged()
|
|
{
|
|
GameData.maxRobotCount = 1;
|
|
GameData.inventory.maxInventorySize = 1;
|
|
ItemData stone = GameData.availableItems["stone"];
|
|
|
|
bool result = GameData.inventory.AddItem(new Item { data = stone }, stone.StackSize + 1);
|
|
|
|
AssertFalse(result, "add should fail");
|
|
AssertEqual(0, GameData.inventory.GetItemAmount("stone"), "failed add should be atomic");
|
|
AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks");
|
|
}
|
|
|
|
private void TestSplitSaveFilesRoundtrip()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 7);
|
|
GameData.survival.energy = 77f;
|
|
|
|
SaveGameManager.SaveGame();
|
|
SaveGameData saveData = SaveGameManager.LoadSaveData();
|
|
|
|
AssertTrue(SaveGameManager.SaveExists(), "save folder should exist");
|
|
AssertTrue(FileAccess.FileExists("user://savegame/gamedata.json"), "gamedata file");
|
|
AssertTrue(FileAccess.FileExists("user://savegame/robots.json"), "robots file");
|
|
AssertTrue(FileAccess.FileExists("user://savegame/research.json"), "research file");
|
|
AssertEqual(7, saveData.Inventory[0].Amount, "saved file inventory");
|
|
AssertClose(77f, saveData.Survival.Energy, 0.001f, "saved file energy");
|
|
}
|
|
|
|
private void TestSplitSaveFilesIncludeLayerFiles()
|
|
{
|
|
GameData.ruinSize = 2;
|
|
GameData.map = new Layer[2];
|
|
GameData.map[0] = CreateTestLayer(0, "spawn");
|
|
GameData.map[1] = CreateTestLayer(1, "gate");
|
|
|
|
SaveGameManager.SaveGame();
|
|
SaveGameData saveData = SaveGameManager.LoadSaveData();
|
|
|
|
AssertTrue(FileAccess.FileExists("user://savegame/layer_0.json"), "layer 0 file");
|
|
AssertTrue(FileAccess.FileExists("user://savegame/layer_1.json"), "layer 1 file");
|
|
AssertEqual(2, saveData.Layers.Count, "loaded layer count");
|
|
AssertEqual("spawn", saveData.Layers[0].Tiles[0].CollapsedMesh, "layer 0 tile");
|
|
AssertEqual("gate", saveData.Layers[1].Tiles[0].CollapsedMesh, "layer 1 tile");
|
|
}
|
|
|
|
private void TestIfNodeEvaluatesInventoryComparisons()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);
|
|
IfNode node = new IfNode
|
|
{
|
|
selectedItem = new Item { data = GameData.availableItems["stone"] },
|
|
amount = 3,
|
|
comparator = "is bigger than"
|
|
};
|
|
|
|
AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "if condition true");
|
|
|
|
node.amount = 8;
|
|
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "if condition false");
|
|
}
|
|
|
|
private void TestWhileNodeReportsFalseConditions()
|
|
{
|
|
GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 2);
|
|
WhileNode node = new WhileNode
|
|
{
|
|
selectedItem = new Item { data = GameData.availableItems["water"] },
|
|
amount = 5,
|
|
comparator = "is bigger than or equal to"
|
|
};
|
|
|
|
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "while condition false");
|
|
}
|
|
|
|
private void TestForNodeStopsAfterConfiguredAmount()
|
|
{
|
|
ForNode node = new ForNode
|
|
{
|
|
amount = 2
|
|
};
|
|
|
|
AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "first iteration");
|
|
AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "second iteration");
|
|
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished");
|
|
}
|
|
|
|
private void TestItemDataReadableNames()
|
|
{
|
|
AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name");
|
|
AssertEqual("iron_gear", ItemData.GetIndex("Iron Gear"), "index name");
|
|
}
|
|
|
|
private void TestResourceFilesLoadCoreData()
|
|
{
|
|
AssertTrue(GameData.availableItems.ContainsKey("stone"), "stone item loaded");
|
|
AssertTrue(GameData.availableItems.ContainsKey("water"), "water item loaded");
|
|
AssertTrue(GameData.availableResearch.ContainsKey("basics"), "basics research loaded");
|
|
AssertTrue(GameData.availableResearch.ContainsKey("iron_robotics"), "iron robotics research loaded");
|
|
}
|
|
|
|
private Layer CreateTestLayer(int level, string collapsedMesh)
|
|
{
|
|
Layer layer = new Layer
|
|
{
|
|
level = level,
|
|
currentResources = new List<string> { "stone" },
|
|
gateIngredients = new List<Ingredient>(),
|
|
tiles = new Tile[1, 1]
|
|
};
|
|
|
|
Tile tile = new Tile
|
|
{
|
|
GridPosition = new Vector2I(0, 0),
|
|
Position = Vector3.Zero,
|
|
collapsedMesh = collapsedMesh,
|
|
containsResource = true,
|
|
resource = new GameResource("stone")
|
|
};
|
|
|
|
layer.tiles[0, 0] = tile;
|
|
return layer;
|
|
}
|
|
|
|
private void AssertTrue(bool value, string message)
|
|
{
|
|
if (!value)
|
|
{
|
|
throw new Exception(message);
|
|
}
|
|
}
|
|
|
|
private void AssertFalse(bool value, string message)
|
|
{
|
|
if (value)
|
|
{
|
|
throw new Exception(message);
|
|
}
|
|
}
|
|
|
|
private void AssertEqual<T>(T expected, T actual, string message)
|
|
{
|
|
if (!EqualityComparer<T>.Default.Equals(expected, actual))
|
|
{
|
|
throw new Exception($"{message}: expected {expected}, got {actual}");
|
|
}
|
|
}
|
|
|
|
private void AssertClose(float expected, float actual, float tolerance, string message)
|
|
{
|
|
if (Math.Abs(expected - actual) > tolerance)
|
|
{
|
|
throw new Exception($"{message}: expected {expected}, got {actual}");
|
|
}
|
|
}
|
|
}
|