2014-07-26 23:54:50 -05:00
namespace Ficdown.Parser.Player
{
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
using Model.Parser ;
2014-08-10 11:10:21 -05:00
using Model.Player ;
2014-07-26 23:54:50 -05:00
using Model.Story ;
using Parser ;
internal class StateManager
{
2019-09-21 18:52:00 -05:00
private static Logger _logger = Logger . GetLogger < StateManager > ( ) ;
2014-07-26 23:54:50 -05:00
private readonly Story _story ;
private readonly Dictionary < string , int > _stateMatrix ;
private readonly int _sceneCount ;
private readonly int _actionCount ;
2019-09-21 18:52:00 -05:00
private BitArray _scenesSeenMask ;
2014-07-26 23:54:50 -05:00
2018-09-27 12:32:32 -05:00
private List < FicdownException > _warnings { get ; set ; }
public StateManager ( Story story , List < FicdownException > warnings )
2014-07-26 23:54:50 -05:00
{
2018-09-27 12:32:32 -05:00
_warnings = warnings ;
2014-07-26 23:54:50 -05:00
_story = story ;
var allScenes = _story . Scenes . SelectMany ( s = > s . Value ) ;
_sceneCount = allScenes . Max ( s = > s . Id ) ;
2019-09-21 18:52:00 -05:00
_scenesSeenMask = new BitArray ( _sceneCount ) ;
// figure out which scenes can affect state
var masked = 0 ;
foreach ( var scene in allScenes )
{
if ( Utilities . GetInstance ( ) . ParseAnchors ( scene . RawDescription ) . Any ( a = > a . Href . Toggles ! = null ) | | RegexLib . BlockQuotes . IsMatch ( scene . RawDescription ) )
{
_scenesSeenMask [ scene . Id - 1 ] = true ;
masked + + ;
}
}
2015-07-19 15:51:10 -05:00
_actionCount = _story . Actions . Count > 0 ? _story . Actions . Max ( a = > a . Value . Id ) : 0 ;
2014-07-26 23:54:50 -05:00
_stateMatrix = new Dictionary < string , int > ( ) ;
var state = 0 ;
foreach (
var toggle in
allScenes . SelectMany (
sc = >
2018-09-27 12:32:32 -05:00
Utilities . GetInstance ( _warnings , sc . Name , sc . LineNumber ) . ParseAnchors ( sc . RawDescription )
2014-07-26 23:54:50 -05:00
. SelectMany (
a = >
2018-09-27 12:32:32 -05:00
a . Href ! = null & & a . Href . Toggles ! = null
2014-07-26 23:54:50 -05:00
? a . Href . Toggles . Where ( t = > ! _stateMatrix . ContainsKey ( t ) )
: new string [ ] { } ) ) )
{
_stateMatrix . Add ( toggle , state + + ) ;
}
2019-09-21 18:52:00 -05:00
_logger . Debug ( $"{_sceneCount} scenes ({masked} can change state)." ) ;
_logger . Debug ( $"{_actionCount} actions." ) ;
_logger . Debug ( $"{_stateMatrix.Count()} states." ) ;
2014-07-26 23:54:50 -05:00
}
public PageState InitialState
{
get
{
2018-09-25 16:29:53 -05:00
var scene = _story . Scenes [ _story . FirstScene ] . Where ( s = > s . Conditions = = null ) ;
2018-09-25 17:18:11 -05:00
if ( scene . Count ( ) = = 0 )
2018-09-25 16:29:53 -05:00
throw new FicdownException ( _story . Name , string . Format ( "Story links to undefined scene: {0}" , _story . FirstScene ) ) ;
if ( scene . Count ( ) > 1 )
2018-09-27 12:32:32 -05:00
_warnings . Add ( new FicdownException ( _story . Name , string . Format ( "Story links to scene that is defined more than once: {0}" , _story . FirstScene ) ) ) ;
2018-09-25 16:29:53 -05:00
2014-07-26 23:54:50 -05:00
return new PageState
{
2019-09-21 18:52:00 -05:00
Manager = this ,
2014-07-26 23:54:50 -05:00
Id = Guid . Empty ,
Links = new Dictionary < string , string > ( ) ,
2014-07-27 00:43:32 -05:00
State = new State
{
PlayerState = new BitArray ( _stateMatrix . Keys . Count ) ,
ScenesSeen = new BitArray ( _sceneCount ) ,
2014-08-10 15:25:20 -05:00
ActionsToShow = new BitArray ( _actionCount ) ,
ActionFirstToggles = null
2014-07-27 00:43:32 -05:00
} ,
AffectedState = new State
{
PlayerState = new BitArray ( _stateMatrix . Keys . Count ) ,
ScenesSeen = new BitArray ( _sceneCount ) ,
2014-08-10 15:25:20 -05:00
ActionsToShow = new BitArray ( _actionCount ) ,
ActionFirstToggles = null
2014-07-27 00:43:32 -05:00
} ,
2018-09-27 12:32:32 -05:00
Scene = scene . First ( ) ,
2014-08-09 15:18:31 -05:00
StateMatrix = _stateMatrix
2014-07-26 23:54:50 -05:00
} ;
}
}
public PageState ResolveNewState ( Anchor anchor , PageState current )
{
var target = anchor . Href . Target ? ? current . Scene . Key ;
2014-07-27 00:43:32 -05:00
var newState = ClonePage ( current ) ;
newState . State . ScenesSeen [ current . Scene . Id - 1 ] = true ;
2014-08-10 15:25:20 -05:00
List < bool > actionFirstToggles = null ;
2014-07-26 23:54:50 -05:00
if ( anchor . Href . Toggles ! = null )
{
foreach ( var toggle in anchor . Href . Toggles )
{
if ( _story . Actions . ContainsKey ( toggle ) )
{
2014-08-10 15:25:20 -05:00
if ( actionFirstToggles = = null ) actionFirstToggles = new List < bool > ( ) ;
2014-07-27 00:43:32 -05:00
newState . State . ActionsToShow [ _story . Actions [ toggle ] . Id - 1 ] = true ;
2014-08-10 15:25:20 -05:00
if (
2018-09-27 12:32:32 -05:00
Utilities . GetInstance ( _warnings , _story . Actions [ toggle ] . Toggle , _story . Actions [ toggle ] . LineNumber ) . ParseAnchors ( _story . Actions [ toggle ] . RawDescription )
2014-08-10 17:32:13 -05:00
. Any ( a = > a . Href . Conditions ! = null & & a . Href . Conditions . ContainsKey ( toggle ) ) )
2014-08-10 15:25:20 -05:00
actionFirstToggles . Add ( ! current . State . PlayerState [ _stateMatrix [ toggle ] ] ) ;
2014-07-26 23:54:50 -05:00
}
2014-07-27 00:43:32 -05:00
newState . State . PlayerState [ _stateMatrix [ toggle ] ] = true ;
2014-07-26 23:54:50 -05:00
}
}
2014-08-10 15:25:20 -05:00
newState . State . ActionFirstToggles = actionFirstToggles ! = null
? new BitArray ( actionFirstToggles . ToArray ( ) )
: null ;
2018-09-25 16:29:53 -05:00
newState . Scene = GetScene ( current . Scene . Name , anchor , target , newState . State . PlayerState ) ;
2014-07-26 23:54:50 -05:00
return newState ;
}
2018-09-27 12:32:32 -05:00
public void ToggleStateOn ( State state , string toggle , string blockName , int lineNumber , int colNumber )
2014-07-27 00:43:32 -05:00
{
2018-09-27 12:32:32 -05:00
if ( _stateMatrix . ContainsKey ( toggle ) )
state . PlayerState [ _stateMatrix [ toggle ] ] = true ;
else
_warnings . Add ( new FicdownException ( blockName , string . Format ( "Conditional for undefined state: {0}" , toggle ) , lineNumber , colNumber ) ) ;
2014-07-27 00:43:32 -05:00
}
public void ToggleSeenSceneOn ( State state , int sceneId )
{
state . ScenesSeen [ sceneId - 1 ] = true ;
}
2019-09-21 18:52:00 -05:00
public string GetUniqueHash ( State state , string sceneKey )
2014-08-09 15:18:31 -05:00
{
2014-08-10 15:25:20 -05:00
var combined =
new bool [
state . PlayerState . Count + state . ScenesSeen . Count + state . ActionsToShow . Count +
( state . ActionFirstToggles ! = null ? state . ActionFirstToggles . Count : 0 ) ] ;
2014-08-09 15:18:31 -05:00
state . PlayerState . CopyTo ( combined , 0 ) ;
2019-09-21 18:52:00 -05:00
state . ScenesSeen . And ( _scenesSeenMask ) . CopyTo ( combined , state . PlayerState . Count ) ;
2014-08-09 15:18:31 -05:00
state . ActionsToShow . CopyTo ( combined , state . PlayerState . Count + state . ScenesSeen . Count ) ;
2014-08-10 15:25:20 -05:00
if ( state . ActionFirstToggles ! = null )
state . ActionFirstToggles . CopyTo ( combined ,
state . PlayerState . Count + state . ScenesSeen . Count + state . ActionsToShow . Count ) ;
2014-08-09 15:18:31 -05:00
var ba = new BitArray ( combined ) ;
var byteSize = ( int ) Math . Ceiling ( combined . Length / 8.0 ) ;
var encoded = new byte [ byteSize ] ;
2019-09-21 18:52:00 -05:00
for ( var i = 0 ; i < byteSize ; i + + )
{
encoded [ i ] = 0 ;
}
2014-08-09 15:18:31 -05:00
ba . CopyTo ( encoded , 0 ) ;
return string . Format ( "{0}=={1}" , sceneKey , Convert . ToBase64String ( encoded ) ) ;
}
2019-09-21 18:52:00 -05:00
public string GetCompressedHash ( PageState page )
2014-08-09 15:18:31 -05:00
{
var compressed = new State
{
PlayerState = page . State . PlayerState . And ( page . AffectedState . PlayerState ) ,
ScenesSeen = page . State . ScenesSeen . And ( page . AffectedState . ScenesSeen ) ,
2014-08-10 15:25:20 -05:00
ActionsToShow = page . State . ActionsToShow ,
ActionFirstToggles = page . State . ActionFirstToggles
2014-08-09 15:18:31 -05:00
} ;
return GetUniqueHash ( compressed , page . Scene . Key ) ;
}
2018-09-25 16:29:53 -05:00
private Scene GetScene ( string blockName , Anchor anchor , string target , BitArray playerState )
2014-07-26 23:54:50 -05:00
{
2018-09-27 12:32:32 -05:00
if ( _story . Scenes . ContainsKey ( target ) )
2014-07-26 23:54:50 -05:00
{
2018-09-27 12:32:32 -05:00
Scene newScene = null ;
foreach ( var scene in _story . Scenes [ target ] )
2014-07-26 23:54:50 -05:00
{
2018-09-27 12:32:32 -05:00
if ( ConditionsMatch ( scene , playerState ) & &
( newScene = = null | | newScene . Conditions = = null | |
2020-05-10 12:00:32 -05:00
( scene . Conditions ! = null & & scene . Conditions . Count > newScene . Conditions . Count ) ) )
2018-09-27 12:32:32 -05:00
{
newScene = scene ;
}
2014-07-26 23:54:50 -05:00
}
2018-09-27 12:32:32 -05:00
if ( newScene = = null )
_warnings . Add ( new FicdownException ( blockName , string . Format ( "Link to scene that is undefined for conditionals: {0}" , target ) , anchor . LineNumber , anchor . ColNumber ) ) ;
return newScene ;
2014-07-26 23:54:50 -05:00
}
2018-09-27 12:32:32 -05:00
_warnings . Add ( new FicdownException ( blockName , string . Format ( "Link to undefined scene: {0}" , target ) , anchor . LineNumber , anchor . ColNumber ) ) ;
return null ;
2014-07-26 23:54:50 -05:00
}
private bool ConditionsMatch ( Scene scene , BitArray playerState )
{
if ( scene . Conditions = = null ) return true ;
2015-07-19 15:51:10 -05:00
scene . Conditions . ToList ( ) . ForEach ( c = >
{
2018-09-27 12:32:32 -05:00
if ( ! _stateMatrix . ContainsKey ( c . Key ) )
_warnings . Add ( new FicdownException ( scene . Name , string . Format ( "Conditional for undefined state: {0}" , c . Key ) , scene . LineNumber ) ) ;
2015-07-19 15:51:10 -05:00
} ) ;
2018-09-27 12:32:32 -05:00
return scene . Conditions . Where ( c = > _stateMatrix . ContainsKey ( c . Key ) ) . All ( c = > playerState [ _stateMatrix [ c . Key ] ] = = c . Value ) ;
2014-07-26 23:54:50 -05:00
}
2014-07-27 00:43:32 -05:00
private PageState ClonePage ( PageState page )
2014-07-26 23:54:50 -05:00
{
return new PageState
{
2019-09-21 18:52:00 -05:00
Manager = this ,
2014-07-26 23:54:50 -05:00
Id = Guid . NewGuid ( ) ,
Links = new Dictionary < string , string > ( ) ,
2014-07-27 00:43:32 -05:00
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 )
2014-08-09 15:18:31 -05:00
} ,
StateMatrix = _stateMatrix
2014-07-26 23:54:50 -05:00
} ;
}
}
}