|
|
|
@@ -0,0 +1,95 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using MagnetismHeroes.Core;
|
|
|
|
|
using MagnetismHeroes.UnityRuntime.Persistence;
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
|
|
|
|
|
namespace MagnetismHeroes.Tests
|
|
|
|
|
{
|
|
|
|
|
public sealed class PersistenceSessionTests
|
|
|
|
|
{
|
|
|
|
|
private string _directory, _path;
|
|
|
|
|
private static readonly StageDefinition[] Definitions = {
|
|
|
|
|
new StageDefinition(1, StageKind.Counting, new[] { 1 }, new[] { 1 }),
|
|
|
|
|
new StageDefinition(2, StageKind.Counting, new[] { 1, 1 }, new[] { 2 }),
|
|
|
|
|
new StageDefinition(6, StageKind.Composition, new[] { 1, 1 }, new[] { 2 }) };
|
|
|
|
|
private sealed class RecordedStore : IProgressStore
|
|
|
|
|
{
|
|
|
|
|
private readonly LocalProgressStore _store;
|
|
|
|
|
public int Writes;
|
|
|
|
|
public RecordedStore(string path) { _store = new LocalProgressStore(path, Definitions); }
|
|
|
|
|
public bool TryLoad(out ProgressSave save, out string error) => _store.TryLoad(out save, out error);
|
|
|
|
|
public bool TrySave(ProgressSave save, out string error) { Writes++; return _store.TrySave(save, out error); }
|
|
|
|
|
}
|
|
|
|
|
[SetUp] public void SetUp()
|
|
|
|
|
{ _directory = Path.Combine(Path.GetTempPath(), "MagnetismHeroes-persistence-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_directory); _path = Path.Combine(_directory, "progress.json"); }
|
|
|
|
|
[TearDown] public void TearDown() => Directory.Delete(_directory, true);
|
|
|
|
|
private ProgressSave Load()
|
|
|
|
|
{ Assert.That(new LocalProgressStore(_path, Definitions).TryLoad(out var save, out var error), Is.True, error); return save; }
|
|
|
|
|
private static CameraState Camera(float distance) => new CameraState {
|
|
|
|
|
target = new Position3(1, .8f, -2), rotation = new Rotation4(0, .6f, 0, .8f), distance = distance };
|
|
|
|
|
private static void CompleteOne(GameSession session)
|
|
|
|
|
{ session.StartStage(1); session.MoveGroup(session.Board.Groups.Single().id, new Position3(.5f, .8f, 0)); session.Count(session.Board.Balls.Single().id); }
|
|
|
|
|
|
|
|
|
|
[Test] public void ActualFileAlternatesTwoUnfinishedBoardsAndLatestFreeWithoutLosingAnyField()
|
|
|
|
|
{
|
|
|
|
|
var store = new RecordedStore(_path); var session = new GameSession(Definitions, store); CompleteOne(session);
|
|
|
|
|
session.Spawn(new Position3(-2, .8f, 0)); session.CommitCamera(Camera(9)); session.ShowHome();
|
|
|
|
|
session.StartStage(2); var groups = session.Board.Groups;
|
|
|
|
|
session.MoveAndMerge(groups[0].id, new Position3(.6f, 1.1f, -2), groups[1].id);
|
|
|
|
|
session.Count(session.Board.Balls[0].id); session.CommitCamera(Camera(11)); session.ShowHome();
|
|
|
|
|
var fixture = Load(); fixture.completedStageIds = new[] { 1, 2, 3, 4, 5 };
|
|
|
|
|
Assert.That(new LocalProgressStore(_path, Definitions).TrySave(fixture, out var error), Is.True, error);
|
|
|
|
|
session = new GameSession(Definitions, new RecordedStore(_path)); session.StartStage(6);
|
|
|
|
|
session.MoveGroup(session.Board.Groups[0].id, new Position3(-1, .8f, 2)); session.CommitCamera(Camera(7)); session.ShowHome();
|
|
|
|
|
var before = File.ReadAllBytes(_path); var saved = Load();
|
|
|
|
|
Assert.That(saved.puzzleStates.Length, Is.EqualTo(2));
|
|
|
|
|
Assert.That(saved.puzzleStates.Single(state => state.stageId == 2).countedBallIds.Length, Is.EqualTo(1));
|
|
|
|
|
foreach (int id in new[] { 2, 6, 2, 6 })
|
|
|
|
|
{
|
|
|
|
|
session = new GameSession(Definitions, new RecordedStore(_path)); Assert.That(session.Screen, Is.EqualTo(SessionScreen.Home));
|
|
|
|
|
Assert.That(session.StartStage(id), Is.True); var expected = saved.puzzleStates.Single(state => state.stageId == id);
|
|
|
|
|
AssertBoard(session.Board, expected.balls, expected.groups); AssertCamera(session.Camera, expected.camera);
|
|
|
|
|
Assert.That(session.Board.CollectedBallIds, Is.EqualTo(expected.collectedBallIds));
|
|
|
|
|
Assert.That(session.Board.CountedBallIds, Is.EqualTo(expected.countedBallIds)); session.ShowHome();
|
|
|
|
|
Assert.That(session.ContinueFreePlay(), Is.True); AssertBoard(session.Board, saved.latestFreePlay.balls, saved.latestFreePlay.groups);
|
|
|
|
|
AssertCamera(session.Camera, saved.latestFreePlay.camera); session.ShowHome();
|
|
|
|
|
Assert.That(File.ReadAllBytes(_path), Is.EqualTo(before), "Exact deterministic JSON bytes retain both unfinished plus global Free.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[Test] public void CompletionIsOneWriteReplacesOnlyLatestFreeAndReplayKeepsProgress()
|
|
|
|
|
{
|
|
|
|
|
var store = new RecordedStore(_path); var session = new GameSession(Definitions, store); CompleteOne(session);
|
|
|
|
|
session.StartStage(2); session.MoveAndMerge(session.Board.Groups[0].id, new Position3(.5f, 0, 0), session.Board.Groups[1].id);
|
|
|
|
|
session.Count(session.Board.Balls[0].id); int before = store.Writes;
|
|
|
|
|
session.Count(session.Board.Balls[1].id); Assert.That(store.Writes, Is.EqualTo(before + 1));
|
|
|
|
|
var completed = Load(); Assert.That(completed.completedStageIds, Is.EqualTo(new[] { 1, 2 }));
|
|
|
|
|
Assert.That(completed.puzzleStates, Is.Empty); Assert.That(completed.latestFreePlay.sourceStageId, Is.EqualTo(2));
|
|
|
|
|
session.StartStage(1); session.MoveGroup(session.Board.Groups[0].id, new Position3(.2f, 0, 0));
|
|
|
|
|
var replay = Load(); Assert.That(replay.completedStageIds, Is.EqualTo(new[] { 1, 2 }));
|
|
|
|
|
Assert.That(replay.puzzleStates.Single().stageId, Is.EqualTo(1)); Assert.That(replay.latestFreePlay.sourceStageId, Is.EqualTo(2));
|
|
|
|
|
session.Count(session.Board.Balls.Single().id); Assert.That(Load().latestFreePlay.sourceStageId, Is.EqualTo(1));
|
|
|
|
|
Assert.That(Load().completedStageIds, Is.EqualTo(new[] { 1, 2 }));
|
|
|
|
|
}
|
|
|
|
|
[Test] public void TutorialRecordsAreUniqueValidatedAndPersistAcrossFreeAndPuzzleTransitions()
|
|
|
|
|
{
|
|
|
|
|
var store = new RecordedStore(_path); var session = new GameSession(Definitions, store);
|
|
|
|
|
Assert.That(session.MarkTutorialSeen("merge"), Is.True); int before = store.Writes;
|
|
|
|
|
Assert.That(session.MarkTutorialSeen("merge"), Is.False); Assert.That(session.MarkTutorialSeen("unknown"), Is.False);
|
|
|
|
|
Assert.That(store.Writes, Is.EqualTo(before)); CompleteOne(session); Assert.That(session.MarkTutorialSeen("split"), Is.True);
|
|
|
|
|
session.StartStage(1); session.ShowHome(); session = new GameSession(Definitions, new RecordedStore(_path));
|
|
|
|
|
Assert.That(session.HasSeenTutorial("merge"), Is.True); Assert.That(session.HasSeenTutorial("split"), Is.True);
|
|
|
|
|
Assert.That(Load().seenTutorialIds, Is.EqualTo(new[] { "merge", "split" }));
|
|
|
|
|
}
|
|
|
|
|
private static void AssertBoard(BoardRules board, BallState[] balls, GroupState[] groups)
|
|
|
|
|
{
|
|
|
|
|
Assert.That(board.Balls.Select(ball => ball.id), Is.EqualTo(balls.Select(ball => ball.id)));
|
|
|
|
|
Assert.That(board.Balls.Select(ball => ball.position), Is.EqualTo(balls.Select(ball => ball.position)));
|
|
|
|
|
Assert.That(board.Groups.Select(group => group.id), Is.EqualTo(groups.Select(group => group.id)));
|
|
|
|
|
for (int index = 0; index < groups.Length; index++) Assert.That(board.Groups[index].ballIds, Is.EqualTo(groups[index].ballIds));
|
|
|
|
|
}
|
|
|
|
|
private static void AssertCamera(CameraState actual, CameraState expected)
|
|
|
|
|
{ Assert.That(actual.target, Is.EqualTo(expected.target)); Assert.That(actual.rotation, Is.EqualTo(expected.rotation)); Assert.That(actual.distance, Is.EqualTo(expected.distance)); }
|
|
|
|
|
}
|
|
|
|
|
}
|