html renderer generates index page; fixed more traversal/state resolution bugs
This commit is contained in:
parent
611cde2e29
commit
eeb7f1badb
|
@ -61,6 +61,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
<None Include="TestStories\CloakOfDarkness.md" />
|
||||||
<None Include="TestStories\TheRobotKing.md" />
|
<None Include="TestStories\TheRobotKing.md" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
public void CanParseValidStoryFile()
|
public void CanParseValidStoryFile()
|
||||||
{
|
{
|
||||||
var parser = new FicdownParser();
|
var parser = new FicdownParser();
|
||||||
var storyText = Encoding.UTF8.GetString(Resources.TheRobotKing);
|
var storyText = Encoding.UTF8.GetString(Resources.CloakOfDarkness);
|
||||||
var story = parser.ParseStory(storyText);
|
var story = parser.ParseStory(storyText);
|
||||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "itest_output");
|
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "itest_output");
|
||||||
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
# [Cloak of Darkness](/foyer)
|
||||||
|
|
||||||
|
A basic IF demonstration.
|
||||||
|
|
||||||
|
Hurrying through the rainswept November night, you're glad to see the bright lights of the Opera House. It's surprising that there aren't more people about but, hey, what do you expect in a cheap demo game...?
|
||||||
|
|
||||||
|
## [Foyer]("Foyer of the Opera House")
|
||||||
|
|
||||||
|
[You](#examined-self) are standing in a spacious hall, splendidly decorated in red and gold, with glittering chandeliers overhead. The entrance from the street is to the [north](#tried-to-leave), and there are doorways [south](/bar) and [west](/cloakroom).
|
||||||
|
|
||||||
|
### Tried to Leave
|
||||||
|
|
||||||
|
[You've](#examined-self) only just arrived, and besides, the weather outside seems to be getting worse.
|
||||||
|
|
||||||
|
### Examined Self
|
||||||
|
|
||||||
|
[You aren't carrying anything.|You are wearing a handsome cloak, of velvet trimmed with satin, and slightly splattered with raindrops. Its blackness is so deep that it almost seems to suck light from the room.](?lost-cloak)
|
||||||
|
|
||||||
|
## Cloakroom
|
||||||
|
|
||||||
|
The walls of this small room were clearly once lined with hooks, though now only one remains. The exit is a door to the [east](/foyer).
|
||||||
|
|
||||||
|
[Your cloak is on the floor here.](?dropped-cloak)
|
||||||
|
[Your cloak is hanging on the hook.](?hung-cloak)
|
||||||
|
|
||||||
|
- [Examine the hook.](#examined-hook)
|
||||||
|
- [Hang your cloak on the hook.](?examined-self&!lost-cloak#lost-cloak+hung-cloak)
|
||||||
|
- [Drop your cloak on the floor.](?examined-self&!lost-cloak#lost-cloak+dropped-cloak)
|
||||||
|
|
||||||
|
### Examined Hook
|
||||||
|
|
||||||
|
It's just a small brass hook, [with your cloak hanging on it|screwed to the wall](?hung-cloak).
|
||||||
|
|
||||||
|
## [Bar]("Foyer Bar")
|
||||||
|
|
||||||
|
You walk to the bar, but it's so dark here you can't really make anything out. The foyer is back to the [north](/foyer).
|
||||||
|
|
||||||
|
- [Feel around for a light switch.](?!scuffled1#scuffled1+not-in-the-dark)
|
||||||
|
- [Sit on a bar stool.](?!scuffled2#scuffled2+not-in-the-dark)
|
||||||
|
|
||||||
|
### Not in the Dark
|
||||||
|
|
||||||
|
In the dark? You could easily disturb something.
|
||||||
|
|
||||||
|
## [Bar](?lost-cloak "Foyer Bar")
|
||||||
|
|
||||||
|
The bar, much rougher than you'd have guessed after the opulence of the foyer to the north, is completely empty. There seems to be some sort of message scrawled in the sawdust on the floor. The foyer is back to the [north](/foyer).
|
||||||
|
|
||||||
|
[Examine the message.](/message)
|
||||||
|
|
||||||
|
## [Message]("Foyer Bar")
|
||||||
|
|
||||||
|
The message, neatly marked in the sawdust, reads...
|
||||||
|
|
||||||
|
**You have won!**
|
||||||
|
|
||||||
|
## [Message](?scuffled1&scuffled2 "Foyer Bar")
|
||||||
|
|
||||||
|
The message has been carelessly trampled, making it difficult to read. You can just distinguish the words...
|
||||||
|
|
||||||
|
**You have lost.**
|
|
@ -60,6 +60,16 @@ namespace Ficdown.Parser.Tests.TestStories {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized resource of type System.Byte[].
|
||||||
|
/// </summary>
|
||||||
|
internal static byte[] CloakOfDarkness {
|
||||||
|
get {
|
||||||
|
object obj = ResourceManager.GetObject("CloakOfDarkness", resourceCulture);
|
||||||
|
return ((byte[])(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized resource of type System.Byte[].
|
/// Looks up a localized resource of type System.Byte[].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -118,6 +118,9 @@
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||||
|
<data name="CloakOfDarkness" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>cloakofdarkness.md;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</data>
|
||||||
<data name="TheRobotKing" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="TheRobotKing" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>therobotking.md;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>therobotking.md;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace Ficdown.Parser
|
||||||
set { _stateResolver = value; }
|
set { _stateResolver = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ResolvedPage> ParseStory(string storyText)
|
public ResolvedStory ParseStory(string storyText)
|
||||||
{
|
{
|
||||||
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
||||||
var blocks = BlockHandler.ExtractBlocks(lines);
|
var blocks = BlockHandler.ExtractBlocks(lines);
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Model\Parser\ResolvedPage.cs" />
|
<Compile Include="Model\Parser\ResolvedPage.cs" />
|
||||||
|
<Compile Include="Model\Parser\ResolvedStory.cs" />
|
||||||
<Compile Include="Model\Player\PageState.cs" />
|
<Compile Include="Model\Player\PageState.cs" />
|
||||||
<Compile Include="Model\Player\State.cs" />
|
<Compile Include="Model\Player\State.cs" />
|
||||||
<Compile Include="Model\Player\StateQueueItem.cs" />
|
<Compile Include="Model\Player\StateQueueItem.cs" />
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ficdown.Parser.Model.Parser
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class ResolvedStory
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string FirstPage { get; set; }
|
||||||
|
public IEnumerable<ResolvedPage> Pages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,9 @@
|
||||||
public Story ParseBlocks(IEnumerable<Block> blocks)
|
public Story ParseBlocks(IEnumerable<Block> blocks)
|
||||||
{
|
{
|
||||||
// get the story
|
// get the story
|
||||||
var storyBlock = blocks.Single(b => b.Type == BlockType.Story);
|
var storyBlock = blocks.SingleOrDefault(b => b.Type == BlockType.Story);
|
||||||
|
if(storyBlock == null) throw new FormatException("No story block found");
|
||||||
|
|
||||||
var storyAnchor = Utilities.ParseAnchor(storyBlock.Name);
|
var storyAnchor = Utilities.ParseAnchor(storyBlock.Name);
|
||||||
|
|
||||||
if (storyAnchor.Href.Target == null || storyAnchor.Href.Conditions != null ||
|
if (storyAnchor.Href.Target == null || storyAnchor.Href.Conditions != null ||
|
||||||
|
|
|
@ -7,6 +7,6 @@
|
||||||
|
|
||||||
internal interface IStateResolver
|
internal interface IStateResolver
|
||||||
{
|
{
|
||||||
IEnumerable<ResolvedPage> Resolve(IEnumerable<PageState> pages, Story story);
|
ResolvedStory Resolve(IEnumerable<PageState> pages, Story story);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,18 +21,23 @@
|
||||||
_usedNames = new HashSet<string>();
|
_usedNames = new HashSet<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ResolvedPage> Resolve(IEnumerable<PageState> pages, Story story)
|
public ResolvedStory Resolve(IEnumerable<PageState> pages, Story story)
|
||||||
{
|
{
|
||||||
_story = story;
|
_story = story;
|
||||||
return
|
return new ResolvedStory
|
||||||
pages.Select(
|
{
|
||||||
|
Name = story.Name,
|
||||||
|
Description = story.Description,
|
||||||
|
FirstPage = GetPageNameForHash(pages.Single(p => p.Id.Equals(Guid.Empty)).CompressedHash),
|
||||||
|
Pages = pages.Select(
|
||||||
page =>
|
page =>
|
||||||
new ResolvedPage
|
new ResolvedPage
|
||||||
{
|
{
|
||||||
Name = GetPageNameForHash(page.CompressedHash),
|
Name = GetPageNameForHash(page.CompressedHash),
|
||||||
Content = ResolveDescription(page),
|
Content = ResolveDescription(page),
|
||||||
ActiveToggles = GetStateDictionary(page).Where(t => t.Value).Select(t => t.Key)
|
ActiveToggles = GetStateDictionary(page).Where(t => t.Value).Select(t => t.Key)
|
||||||
}).ToList();
|
}).ToList()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveAnchor(Anchor anchor, IDictionary<string, bool> playerState, string targetHash)
|
private string ResolveAnchor(Anchor anchor, IDictionary<string, bool> playerState, string targetHash)
|
||||||
|
@ -63,7 +68,9 @@
|
||||||
var actionTuple = _story.Actions.Single(a => a.Value.Id == i + 1);
|
var actionTuple = _story.Actions.Single(a => a.Value.Id == i + 1);
|
||||||
var actionAnchors = Utilities.ParseAnchors(actionTuple.Value.Description);
|
var actionAnchors = Utilities.ParseAnchors(actionTuple.Value.Description);
|
||||||
var anchorDict = GetStateDictionary(page);
|
var anchorDict = GetStateDictionary(page);
|
||||||
if (actionAnchors.Any(a => a.Href.Conditions.ContainsKey(actionTuple.Key)))
|
if (
|
||||||
|
actionAnchors.Any(
|
||||||
|
a => a.Href.Conditions != null && a.Href.Conditions.ContainsKey(actionTuple.Key)))
|
||||||
{
|
{
|
||||||
if (page.State.ActionFirstToggles[firstToggleCounter++])
|
if (page.State.ActionFirstToggles[firstToggleCounter++])
|
||||||
{
|
{
|
||||||
|
@ -95,7 +102,7 @@
|
||||||
return resolved.ToString();
|
return resolved.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IDictionary<string, bool> GetStateDictionary(PageState page)
|
public static IDictionary<string, bool> GetStateDictionary(PageState page)
|
||||||
{
|
{
|
||||||
return page.StateMatrix.Where(matrix => page.State.PlayerState[matrix.Value])
|
return page.StateMatrix.Where(matrix => page.State.PlayerState[matrix.Value])
|
||||||
.ToDictionary(m => m.Key, m => true);
|
.ToDictionary(m => m.Key, m => true);
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace Ficdown.Parser.Parser
|
||||||
conditions.All(
|
conditions.All(
|
||||||
c =>
|
c =>
|
||||||
(!c.Value && (!playerState.ContainsKey(c.Key) || !playerState[c.Key])) ||
|
(!c.Value && (!playerState.ContainsKey(c.Key) || !playerState[c.Key])) ||
|
||||||
(playerState.ContainsKey(c.Key) && playerState[c.Key]));
|
(c.Value && (playerState.ContainsKey(c.Key) && playerState[c.Key])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,13 @@
|
||||||
|
|
||||||
foreach (var anchor in anchors.Where(a => a.Href.Target != null || a.Href.Toggles != null))
|
foreach (var anchor in anchors.Where(a => a.Href.Target != null || a.Href.Toggles != null))
|
||||||
{
|
{
|
||||||
|
// don't follow links that would be hidden
|
||||||
|
if (anchor.Href.Conditions != null &&
|
||||||
|
string.IsNullOrEmpty(
|
||||||
|
Utilities.ParseConditionalText(anchor.Text)[
|
||||||
|
Utilities.ConditionsMet(StateResolver.GetStateDictionary(currentState.Page),
|
||||||
|
anchor.Href.Conditions)])) continue;
|
||||||
|
|
||||||
var newState = _manager.ResolveNewState(anchor, currentState.Page);
|
var newState = _manager.ResolveNewState(anchor, currentState.Page);
|
||||||
if (!currentState.Page.Links.ContainsKey(anchor.Original))
|
if (!currentState.Page.Links.ContainsKey(anchor.Original))
|
||||||
currentState.Page.Links.Add(anchor.Original, newState.UniqueHash);
|
currentState.Page.Links.Add(anchor.Original, newState.UniqueHash);
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
newState.State.ActionsToShow[_story.Actions[toggle].Id - 1] = true;
|
newState.State.ActionsToShow[_story.Actions[toggle].Id - 1] = true;
|
||||||
if (
|
if (
|
||||||
Utilities.ParseAnchors(_story.Actions[toggle].Description)
|
Utilities.ParseAnchors(_story.Actions[toggle].Description)
|
||||||
.Any(a => a.Href.Conditions.ContainsKey(toggle)))
|
.Any(a => a.Href.Conditions != null && a.Href.Conditions.ContainsKey(toggle)))
|
||||||
actionFirstToggles.Add(!current.State.PlayerState[_stateMatrix[toggle]]);
|
actionFirstToggles.Add(!current.State.PlayerState[_stateMatrix[toggle]]);
|
||||||
}
|
}
|
||||||
newState.State.PlayerState[_stateMatrix[toggle]] = true;
|
newState.State.PlayerState[_stateMatrix[toggle]] = true;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
namespace Ficdown.Parser.Render
|
namespace Ficdown.Parser.Render
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MarkdownSharp;
|
using MarkdownSharp;
|
||||||
|
@ -16,9 +15,14 @@
|
||||||
_md = new Markdown();
|
_md = new Markdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(IEnumerable<ResolvedPage> pages, string outPath, bool debug = false)
|
public void Render(ResolvedStory story, string outPath, bool debug = false)
|
||||||
{
|
{
|
||||||
foreach (var page in pages)
|
var index = string.Format("# {0}\n\n{1}\n\n[Click here]({2}.html) to start!", story.Name, story.Description,
|
||||||
|
story.FirstPage);
|
||||||
|
|
||||||
|
File.WriteAllText(Path.Combine(outPath, "index.html"), _md.Transform(index));
|
||||||
|
|
||||||
|
foreach (var page in story.Pages)
|
||||||
{
|
{
|
||||||
var content = page.Content;
|
var content = page.Content;
|
||||||
foreach (var anchor in Utilities.ParseAnchors(page.Content))
|
foreach (var anchor in Utilities.ParseAnchors(page.Content))
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
namespace Ficdown.Parser.Render
|
namespace Ficdown.Parser.Render
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using Model.Parser;
|
using Model.Parser;
|
||||||
|
|
||||||
internal interface IRenderer
|
internal interface IRenderer
|
||||||
{
|
{
|
||||||
void Render(IEnumerable<ResolvedPage> pages, string outPath, bool debug);
|
void Render(ResolvedStory story, string outPath, bool debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue