diff --git a/Ficdown.Parser.Tests/IntegrationTests.cs b/Ficdown.Parser.Tests/IntegrationTests.cs index 6e570e7..7b4a57b 100644 --- a/Ficdown.Parser.Tests/IntegrationTests.cs +++ b/Ficdown.Parser.Tests/IntegrationTests.cs @@ -1,8 +1,10 @@ namespace Ficdown.Parser.Tests { + using System; using System.Linq; using System.Text; using Player; + using ServiceStack.Text; using TestStories; using Xunit; @@ -20,6 +22,7 @@ var traverser = new GameTraverser(story); var test = traverser.Enumerate(); + Console.WriteLine(test.Take(10).Dump()); } } } diff --git a/Ficdown.Parser/Ficdown.Parser.csproj b/Ficdown.Parser/Ficdown.Parser.csproj index cc85472..0b73b6e 100644 --- a/Ficdown.Parser/Ficdown.Parser.csproj +++ b/Ficdown.Parser/Ficdown.Parser.csproj @@ -43,6 +43,8 @@ + + diff --git a/Ficdown.Parser/Model/Traverser/PageState.cs b/Ficdown.Parser/Model/Traverser/PageState.cs index ed5343f..c5ac4e4 100644 --- a/Ficdown.Parser/Model/Traverser/PageState.cs +++ b/Ficdown.Parser/Model/Traverser/PageState.cs @@ -8,10 +8,9 @@ internal class PageState { public Guid Id { get; set; } - public BitArray PlayerState { get; set; } - public BitArray ScenesSeen { get; set; } - public BitArray ActionsToShow { get; set; } public Scene Scene { get; set; } + public State State { get; set; } + public State AffectedState { get; set; } public string Resolved { get; set; } public IDictionary Links { get; set; } @@ -23,10 +22,11 @@ { if (_uniqueHash == null) { - var combined = new bool[PlayerState.Count + ScenesSeen.Count + ActionsToShow.Count]; - PlayerState.CopyTo(combined, 0); - ScenesSeen.CopyTo(combined, PlayerState.Count); - ActionsToShow.CopyTo(combined, PlayerState.Count + ScenesSeen.Count); + var combined = + new bool[State.PlayerState.Count + State.ScenesSeen.Count + State.ActionsToShow.Count]; + State.PlayerState.CopyTo(combined, 0); + State.ScenesSeen.CopyTo(combined, State.PlayerState.Count); + State.ActionsToShow.CopyTo(combined, State.PlayerState.Count + State.ScenesSeen.Count); var ba = new BitArray(combined); var byteSize = (int) Math.Ceiling(combined.Length/8.0); var encoded = new byte[byteSize]; diff --git a/Ficdown.Parser/Model/Traverser/State.cs b/Ficdown.Parser/Model/Traverser/State.cs new file mode 100644 index 0000000..e2ef44b --- /dev/null +++ b/Ficdown.Parser/Model/Traverser/State.cs @@ -0,0 +1,11 @@ +namespace Ficdown.Parser.Model.Traverser +{ + using System.Collections; + + internal class State + { + public BitArray PlayerState { get; set; } + public BitArray ScenesSeen { get; set; } + public BitArray ActionsToShow { get; set; } + } +} diff --git a/Ficdown.Parser/Model/Traverser/StateQueueItem.cs b/Ficdown.Parser/Model/Traverser/StateQueueItem.cs new file mode 100644 index 0000000..71afcd4 --- /dev/null +++ b/Ficdown.Parser/Model/Traverser/StateQueueItem.cs @@ -0,0 +1,10 @@ +namespace Ficdown.Parser.Model.Traverser +{ + using System.Collections.Generic; + + internal class StateQueueItem + { + public PageState Page { get; set; } + public IList AffectedStates { get; set; } + } +} diff --git a/Ficdown.Parser/Player/GameTraverser.cs b/Ficdown.Parser/Player/GameTraverser.cs index 3f4cf5e..bfb2130 100644 --- a/Ficdown.Parser/Player/GameTraverser.cs +++ b/Ficdown.Parser/Player/GameTraverser.cs @@ -9,13 +9,13 @@ internal class GameTraverser { private readonly StateManager _manager; - private readonly Queue _processingQueue; + private readonly Queue _processingQueue; private readonly IDictionary _processed; public GameTraverser(Story story) { _manager = new StateManager(story); - _processingQueue = new Queue(); + _processingQueue = new Queue(); _processed = new Dictionary(); } @@ -23,13 +23,18 @@ { // generate comprehensive enumeration - _processingQueue.Enqueue(_manager.InitialState); + var initial = _manager.InitialState; + _processingQueue.Enqueue(new StateQueueItem + { + Page = initial, + AffectedStates = new List {initial.AffectedState} + }); while (_processingQueue.Count > 0) { var state = _processingQueue.Dequeue(); - if (!_processed.ContainsKey(state.UniqueHash)) + if (!_processed.ContainsKey(state.Page.UniqueHash)) { - _processed.Add(state.UniqueHash, state); + _processed.Add(state.Page.UniqueHash, state.Page); ProcessState(state); } } @@ -40,21 +45,39 @@ return _processed.Values; } - private void ProcessState(PageState currentState) + private void ProcessState(StateQueueItem currentState) { var states = new HashSet(); - foreach ( - var anchor in - Utilities.ParseAnchors(currentState.Scene.Description) - .Where(a => a.Href.Target != null || a.Href.Toggles != null)) + + var anchors = Utilities.ParseAnchors(currentState.Page.Scene.Description); + var conditionals = + anchors.SelectMany( + a => a.Href.Conditions != null ? a.Href.Conditions.Select(c => c.Key) : new string[] {}) + .Distinct() + .ToArray(); + var hasFirstSeen = RegexLib.BlockQuotes.IsMatch(currentState.Page.Scene.Description); + + foreach (var affected in currentState.AffectedStates) { - var newState = _manager.ResolveNewState(anchor, currentState); - if (!currentState.Links.ContainsKey(anchor.Original)) - currentState.Links.Add(anchor.Original, newState.UniqueHash); + // signal to previous scenes that this scene's used conditionals are important + foreach (var conditional in conditionals) _manager.ToggleStateOn(affected, conditional); + + // signal to previous scenes if this scene has first-seen text + if (hasFirstSeen) _manager.ToggleSeenSceneOn(affected, currentState.Page.Scene.Id); + } + + foreach (var anchor in anchors.Where(a => a.Href.Target != null || a.Href.Toggles != null)) + { + var newState = _manager.ResolveNewState(anchor, currentState.Page); + if (!currentState.Page.Links.ContainsKey(anchor.Original)) + currentState.Page.Links.Add(anchor.Original, newState.UniqueHash); + if (!states.Contains(newState.UniqueHash) && !_processed.ContainsKey(newState.UniqueHash)) { states.Add(newState.UniqueHash); - _processingQueue.Enqueue(newState); + var newAffected = new List(currentState.AffectedStates); + newAffected.Add(newState.AffectedState); + _processingQueue.Enqueue(new StateQueueItem {Page = newState, AffectedStates = newAffected}); } } } diff --git a/Ficdown.Parser/Player/StateManager.cs b/Ficdown.Parser/Player/StateManager.cs index c89893f..a6d612b 100644 --- a/Ficdown.Parser/Player/StateManager.cs +++ b/Ficdown.Parser/Player/StateManager.cs @@ -47,9 +47,18 @@ { Id = Guid.Empty, Links = new Dictionary(), - PlayerState = new BitArray(_stateMatrix.Keys.Count), - ScenesSeen = new BitArray(_sceneCount), - ActionsToShow = new BitArray(_actionCount), + State = new State + { + PlayerState = new BitArray(_stateMatrix.Keys.Count), + ScenesSeen = new BitArray(_sceneCount), + ActionsToShow = new BitArray(_actionCount) + }, + AffectedState = new State + { + PlayerState = new BitArray(_stateMatrix.Keys.Count), + ScenesSeen = new BitArray(_sceneCount), + ActionsToShow = new BitArray(_actionCount) + }, Scene = _story.Scenes[_story.FirstScene].Single(s => s.Conditions == null) }; } @@ -59,23 +68,33 @@ { var target = anchor.Href.Target ?? current.Scene.Key; - var newState = CloneState(current); - newState.ScenesSeen[current.Scene.Id - 1] = true; + var newState = ClonePage(current); + newState.State.ScenesSeen[current.Scene.Id - 1] = true; if (anchor.Href.Toggles != null) { foreach (var toggle in anchor.Href.Toggles) { if (_story.Actions.ContainsKey(toggle)) { - newState.ActionsToShow[_story.Actions[toggle].Id - 1] = true; + newState.State.ActionsToShow[_story.Actions[toggle].Id - 1] = true; } - newState.PlayerState[_stateMatrix[toggle]] = true; + newState.State.PlayerState[_stateMatrix[toggle]] = true; } } - newState.Scene = GetScene(target, newState.PlayerState); + newState.Scene = GetScene(target, newState.State.PlayerState); return newState; } + public void ToggleStateOn(State state, string toggle) + { + state.PlayerState[_stateMatrix[toggle]] = true; + } + + public void ToggleSeenSceneOn(State state, int sceneId) + { + state.ScenesSeen[sceneId - 1] = true; + } + private Scene GetScene(string target, BitArray playerState) { if (!_story.Scenes.ContainsKey(target)) @@ -102,15 +121,24 @@ return scene.Conditions.All(c => playerState[_stateMatrix[c.Key]] == c.Value); } - private PageState CloneState(PageState state) + private PageState ClonePage(PageState page) { return new PageState { Id = Guid.NewGuid(), Links = new Dictionary(), - PlayerState = state.PlayerState.Clone() as BitArray, - ScenesSeen = state.ScenesSeen.Clone() as BitArray, - ActionsToShow = new BitArray(_actionCount) + State = new State + { + PlayerState = page.State.PlayerState.Clone() as BitArray, + ScenesSeen = page.State.ScenesSeen.Clone() as BitArray, + ActionsToShow = new BitArray(_actionCount) + }, + AffectedState = new State + { + PlayerState = new BitArray(_stateMatrix.Keys.Count), + ScenesSeen = new BitArray(_sceneCount), + ActionsToShow = new BitArray(_actionCount) + } }; } }