1
0
Fork 0
mirror of https://github.com/Oreolek/gamebookformat.git synced 2024-06-26 03:41:04 +03:00

More formatting options.

This commit is contained in:
Pelle Nilsson 2013-06-11 21:59:39 +02:00
parent 5a468bdd65
commit f60a650598
36 changed files with 229 additions and 43 deletions

1
.gitignore vendored
View file

@ -8,7 +8,6 @@ test.dot
*.toc *.toc
*.pdf *.pdf
test.rtf test.rtf
*.png
*.out *.out
test.html test.html
.lein* .lein*

4
examples/.gitignore vendored
View file

@ -3,4 +3,6 @@
*.html *.html
*.dot *.dot
*.debug *.debug
*.txt *.txt
*.png
!testimage.png

View file

@ -1,12 +1,27 @@
title = Format title = Format
starttext = Adventure begins in section 1.
hideintrotext = HIDE THE INTRO
showintrotext = SHOW THE INTRO
= Introduction
Adding an introduction
to the gamebook here. This will create
a section, but it will not be shuffled nor numbered
with the gamebook sections below.
= Another Heading
This starts another non-shuffled section.
* 1 start * 1 start
This examples tests gamebook formatting, not so much game mechanics or This examples tests gamebook formatting, not so much game mechanics or
references. Currently there is nothing here really. references. Currently there is nothing here really.
This section contains some tricky characters to quote, This section contains some tricky characters to quote,
like } and { and " and ' and \. like } and { and " and ' and \.
HTML will probably not like <div> or &boom;. HTML will probably not like <div> or &boom;.
There should be an image below as well.
If something broke, turn to [[bad]], If something broke, turn to [[bad]],
otherwise turn to [[good]]. otherwise turn to [[good]].
[img]testimage.png[/img]
* dum :dummy: * dum :dummy:
Sections tagged as dummy will not be Sections tagged as dummy will not be

View file

@ -1,4 +1,11 @@
max = 400 max = 400
= Introduction
This gamebook demonstrates simple references between sections.
Also notice that an intro section like this one can
reference sections, like the start at [[start]] or the next
section at [[next]].
* 1 start * 1 start
This is where the adventure begins. You can go on to the This is where the adventure begins. You can go on to the
next section, see [[next]] or try the other instead, see [[other]]. next section, see [[next]] or try the other instead, see [[other]].

BIN
examples/testimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

View file

@ -28,6 +28,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" """
import re
import os import os
import os.path import os.path
import sys import sys
@ -41,6 +42,8 @@ from output import OutputFormat
USAGE = "usage: %prog [options] inputfile(s)... outputfile" USAGE = "usage: %prog [options] inputfile(s)... outputfile"
SECTION_NAME_RE = re.compile("^[a-z][a-z_0-9]*$")
def of(extension, name, quote): def of(extension, name, quote):
return {'extension' : extension, return {'extension' : extension,
'name' : name, 'name' : name,
@ -81,17 +84,15 @@ def parse_file_to_book(inputfile, book):
number = None number = None
text = "" text = ""
tags = None tags = None
intro_section = False
for line in inputfile.readlines(): for line in inputfile.readlines():
if before_first_section:
if '=' in line:
config = line.split('=')
book.configure(config[0].strip(), config[1].strip())
if line.startswith('*'): if line.startswith('*'):
before_first_section = False before_first_section = False
if name: if name:
add_section_to_book(book, name, text, number) add_section_to_book(book, name, text, intro_section, number)
number = None number = None
text = "" text = ""
intro_section = False
heading = [h.strip() for h in line[1:].strip().split()] heading = [h.strip() for h in line[1:].strip().split()]
if len(heading) > 1 and heading[-1].startswith(':'): if len(heading) > 1 and heading[-1].startswith(':'):
if not heading[-1].endswith(':'): if not heading[-1].endswith(':'):
@ -106,20 +107,34 @@ def parse_file_to_book(inputfile, book):
elif len(heading) == 2: elif len(heading) == 2:
number = int(heading[0]) number = int(heading[0])
name = heading[1] name = heading[1]
else: if not name or not SECTION_NAME_RE.match(name):
raise Exception("bad section heading %s" % str(heading)) raise Exception("bad section heading: %s" % str(heading))
else: elif line.startswith('='):
if name:
add_section_to_book(book, name, text, intro_section, number)
name = line[1:].strip()
intro_section = True
text = ""
elif before_first_section and '=' in line:
config = line.split('=')
book.configure(config[0].strip(), config[1].strip())
elif name:
text = text + " " + line.strip() text = text + " " + line.strip()
elif len(line.strip()):
raise Exception("unknown content before sections: %s" % line.strip())
if name: if name:
add_section_to_book(book, name, text, number, tags) add_section_to_book(book, name, text, intro_section, number, tags)
def add_section_to_book(book, name, text, number=None, tags=None): def add_section_to_book(book, name, text, intro_section=False, number=None, tags=None):
section = sections.Section(name, text) section = sections.Section(name, text)
if tags: if tags:
section.add_tags(tags) section.add_tags(tags)
book.add(section) if intro_section:
if number: book.addintro(section)
book.force_section_nr(name, number) else:
book.add(section)
if number:
book.force_section_nr(name, number)
def make_output(outputfilename, templatedirs): def make_output(outputfilename, templatedirs):
for of in OUTPUT_FORMATS: for of in OUTPUT_FORMATS:
@ -134,6 +149,8 @@ def write_book(book, output_format, outputfilename):
shuffled_sections = book.shuffle() shuffled_sections = book.shuffle()
output = open(outputfilename, 'w') output = open(outputfilename, 'w')
output_format.write_begin(book, output) output_format.write_begin(book, output)
output_format.write_intro_sections(book, shuffled_sections, output)
output_format.write_sections_begin(book, output)
output_format.write_shuffled_sections(shuffled_sections, output) output_format.write_shuffled_sections(shuffled_sections, output)
output_format.write_end(book, output) output_format.write_end(book, output)
save_section_mapping(shuffled_sections, outputfilename) save_section_mapping(shuffled_sections, outputfilename)

View file

@ -12,6 +12,27 @@ class OutputFormat (object):
# FIXME make sure book config is properly quoted # FIXME make sure book config is properly quoted
print >> output, self.format_with_template("begin", book.config) print >> output, self.format_with_template("begin", book.config)
def write_intro_sections(self, book, shuffled_sections, output):
for s in book.introsections:
if not s.hastag('dummy'):
self.write_intro_section(s, shuffled_sections, output)
def write_intro_section(self, section, shuffled_sections, output):
# FIXME some serious code-duplication here
refs = []
refsdict = ReferenceFormatter(section, shuffled_sections,
self.format_with_template("section_ref"),
self.quote)
formatted_text = self.format_section(section, refsdict)
print >> output, self.format_with_template("introsection", {
'name' : section.name,
'text' : formatted_text,
'refs' : '\n'.join(refsdict.getfound()) # hack for DOT output
}),
def write_sections_begin(self, book, output):
print >> output, self.format_with_template("sections_begin", book.config);
def write_shuffled_sections(self, shuffled_sections, output): def write_shuffled_sections(self, shuffled_sections, output):
for i, p in enumerate(shuffled_sections.as_list): for i, p in enumerate(shuffled_sections.as_list):
if p and not p.hastag('dummy'): if p and not p.hastag('dummy'):
@ -26,7 +47,7 @@ class OutputFormat (object):
self.quote) self.quote)
formatted_text = self.format_section(section, refsdict) formatted_text = self.format_section(section, refsdict)
print >> output, self.format_with_template("section", { print >> output, self.format_with_template("section", {
'nr' : shuffled_sections.to_nr[section], 'nr' : section.nr,
'name' : section.name, 'name' : section.name,
'text' : formatted_text, 'text' : formatted_text,
'refs' : '\n'.join(refsdict.getfound()) # hack for DOT output 'refs' : '\n'.join(refsdict.getfound()) # hack for DOT output
@ -92,7 +113,7 @@ class OutputFormat (object):
}), }),
def write_end(self, book, output): def write_end(self, book, output):
print >> output, self.format_with_template("end"), print >> output, self.format_with_template("end", book.config),
def format_with_template(self, name, values=None): def format_with_template(self, name, values=None):
template = self.templates.get(name) template = self.templates.get(name)
@ -108,7 +129,7 @@ class ReferenceFormatter (object):
self.shuffled_sections = shuffled_sections self.shuffled_sections = shuffled_sections
self.found = set() self.found = set()
self.ref_template = ref_template self.ref_template = ref_template
self.items = {'nr' : shuffled_sections.to_nr[section]} self.items = {'nr' : section.nr}
self.quote = quote self.quote = quote
def __getitem__(self, key): def __getitem__(self, key):
@ -116,8 +137,8 @@ class ReferenceFormatter (object):
return self.quote(self.items[key]) return self.quote(self.items[key])
to_section = self.shuffled_sections.from_name[key] to_section = self.shuffled_sections.from_name[key]
res = self.ref_template % { res = self.ref_template % {
'nr' : self.shuffled_sections.to_nr[to_section], 'nr' : to_section.nr,
'from_nr' : self.shuffled_sections.to_nr[self.section] 'from_nr' : self.section.nr
} }
if key in self.shuffled_sections.name_to_nr: if key in self.shuffled_sections.name_to_nr:
self.found.add(res) self.found.add(res)

View file

@ -17,30 +17,57 @@ class Section:
return "Section(%s, %s, %s)" % (repr(self.name), repr(self.text), return "Section(%s, %s, %s)" % (repr(self.name), repr(self.text),
repr(self.tags)) repr(self.tags))
class ShuffledSection (Section):
def __init__(self, nr, section):
self.nr = nr
self.name = section.name
self.text = section.text
self.tags = section.tags.copy()
def __repr__(self):
return "ShuffledSection(%d, %s, %s, %s)" % (self.nr,
repr(self.name), repr(self.text),
repr(self.tags))
class IntroSection (Section):
def __init__(self, section):
self.nr = -1
self.name = section.name
self.text = section.text
self.tags = section.tags.copy()
def __repr__(self):
return "IntroSection(%d, %s, %s, %s)" % (repr(self.name), repr(self.text),
repr(self.tags))
class ShuffledSections: class ShuffledSections:
def __init__(self, as_list, from_nr, to_nr, from_name, nr_sections): def __init__(self, as_list, from_nr, from_name, nr_sections):
self.as_list = as_list self.as_list = as_list
self.from_nr = from_nr self.from_nr = from_nr
self.to_nr = to_nr
self.from_name = from_name self.from_name = from_name
self.name_to_nr = {} self.name_to_nr = {}
for n in from_name: for n in from_name:
self.name_to_nr[n] = to_nr[from_name[n]] self.name_to_nr[n] = from_name[n].nr
for nr in nr_sections: for nr in nr_sections:
self.name_to_nr[nr_sections[nr]] = nr self.name_to_nr[nr_sections[nr]] = nr
STR_BOOK_CONFIG = set(['title', 'author']) STR_BOOK_CONFIG = set(['title', 'author', 'starttext', 'hideintrotext',
'showintrotext'])
INT_BOOK_CONFIG = set(['max']) INT_BOOK_CONFIG = set(['max'])
class Book: class Book:
def __init__(self): def __init__(self):
self.sections = [] self.sections = []
self.introsections = []
self.from_name = {} self.from_name = {}
self.nr_sections = {} self.nr_sections = {}
self.codewords = set() self.codewords = set()
self.config = {'max' : 0, self.config = {'max' : 0,
'title' : 'Gamebook', 'title' : 'Gamebook',
'author' : ''} 'author' : '',
'starttext' : 'Turn to 1 to begin.',
'hideintrotext' : '(hide instructions)',
'showintrotext' : '(show instructions)'}
def configure(self, name, value): def configure(self, name, value):
if name in INT_BOOK_CONFIG: if name in INT_BOOK_CONFIG:
@ -59,6 +86,9 @@ class Book:
if len(self.sections) > self.config['max']: if len(self.sections) > self.config['max']:
self.config['max'] = len(self.sections) self.config['max'] = len(self.sections)
def addintro(self, section):
self.introsections.append(IntroSection(section))
def add_codeword(self, word): def add_codeword(self, word):
self.codewords.add(word) self.codewords.add(word)
@ -70,8 +100,8 @@ class Book:
def shuffle(self): def shuffle(self):
as_list = [None] as_list = [None]
from_nr = {} from_nr = {}
to_nr = {}
shuffled = self.sections[:] shuffled = self.sections[:]
shuffled_from_name = {}
while len(shuffled) < self.config['max']: while len(shuffled) < self.config['max']:
dummy = Section('Dummy', '') dummy = Section('Dummy', '')
dummy.add_tags(['dummy']) dummy.add_tags(['dummy'])
@ -83,16 +113,16 @@ class Book:
for nr in range(1, self.config['max'] + 1): for nr in range(1, self.config['max'] + 1):
if (self.nr_sections.has_key(nr) if (self.nr_sections.has_key(nr)
and self.nr_sections[nr] in self.from_name): and self.nr_sections[nr] in self.from_name):
section = self.from_name[self.nr_sections[nr]] section = ShuffledSection(nr, self.from_name[self.nr_sections[nr]])
elif len(shuffled): elif len(shuffled):
section = shuffled.pop() section = ShuffledSection(nr, shuffled.pop())
else: else:
section = None section = None
as_list.append(section) as_list.append(section)
from_nr[nr] = section from_nr[nr] = section
if section: if section:
to_nr[section] = nr shuffled_from_name[section.name] = section
return ShuffledSections(as_list, from_nr, to_nr, self.from_name.copy(), return ShuffledSections(as_list, from_nr, shuffled_from_name,
self.nr_sections) self.nr_sections)
class Item (object): class Item (object):

View file

View file

@ -0,0 +1,2 @@
#include "introsectionheading"
#include "sectionbody"

View file

@ -0,0 +1,2 @@
%(starttext)s

View file

@ -0,0 +1 @@
[IMG]%(inner)s[/IMG]

View file

@ -0,0 +1,2 @@
%(name)s
%(text)s

View file

View file

View file

@ -12,5 +12,5 @@
</style> </style>
</head> </head>
<body> <body>
#include "intro" #include "hideintro"
<div class="gamebook"> <div class="gamebook">

View file

@ -3,5 +3,6 @@
<script> <script>
#include "endscript" #include "endscript"
</script> </script>
#include "showintro"
</body> </body>
</html> </html>

View file

@ -1,3 +1,2 @@
if (this.gamebook) { if (this.gamebook) {
gamebook.turnTo(1);
} }

View file

@ -0,0 +1,2 @@
<div class="hideintrolink nodisplay"
onclick="gamebook.hideIntroSections()">%(hideintrotext)s</div>

1
templates/html/img.html Normal file
View file

@ -0,0 +1 @@
<img src="%(inner)s" class="sectionimage"></img>

View file

@ -0,0 +1,6 @@
<div class="introsection">
<span class="introsectionheading">%(name)s</span>
<div class="introsectionbody">
%(text)s
</div>
</div>

View file

@ -64,6 +64,12 @@
this.displaySection(nr); this.displaySection(nr);
}, },
'start' : function() {
this.hideIntroSections();
this.addClassToClass('startlink', 'nodisplay');
this.turnTo(1);
},
'displaySection' : function(nr) { 'displaySection' : function(nr) {
if (this.player.currentSection) { if (this.player.currentSection) {
this.player.currentSection.element.style.display = 'none'; this.player.currentSection.element.style.display = 'none';
@ -74,6 +80,35 @@
this.player.currentSection = gamebook.sections[nr]; this.player.currentSection = gamebook.sections[nr];
}, },
'hideIntroSections' : function() {
this.addClassToClass('introsection', 'nodisplay');
this.removeClassFromClass('displayintrolink', 'nodisplay');
this.addClassToClass('hideintrolink', 'nodisplay');
},
'showIntroSections' : function() {
this.removeClassFromClass('introsection', 'nodisplay');
this.addClassToClass('displayintrolink', 'nodisplay');
this.removeClassFromClass('hideintrolink', 'nodisplay');
document.body.scrollIntoView();
},
'addClassToClass' : function(className, addClass) {
Array.prototype.forEach.call(
document.getElementsByClassName(className),
function(e) {
e.classList.add(addClass);
});
},
'removeClassFromClass' : function(className, removeClass) {
Array.prototype.forEach.call(
document.getElementsByClassName(className),
function(e) {
e.classList.remove(removeClass);
});
},
'runActions' : function(e) { 'runActions' : function(e) {
var enableNextLink = true; var enableNextLink = true;
var hasXorScope = false; var hasXorScope = false;

View file

@ -0,0 +1,2 @@
<div class="startlink"
onclick="gamebook.start()">%(starttext)s</div>

View file

@ -0,0 +1,2 @@
<div class="displayintrolink nodisplay"
onclick="gamebook.showIntroSections()">%(showintrotext)s</div>

View file

@ -0,0 +1,2 @@
<div class="startlink"
onclick="gamebook.start()">%(starttext)s</div>

View file

@ -18,3 +18,8 @@
.has {font-style: italic;} .has {font-style: italic;}
.hasnot {font-style: italic;} .hasnot {font-style: italic;}
.collectionTemplate {display: none;} .collectionTemplate {display: none;}
.sectionimage {width: 100%%; padding: 1em;}
.nodisplay {display: none;}
.startlink {font-weight: bold; cursor: pointer;}
.displayintrolink {cursor: pointer;}
.hideintrolink {cursor: pointer;}

View file

@ -0,0 +1,2 @@
\b \qc %(name)s
\b0\

View file

@ -2,6 +2,7 @@
\usepackage[utf8]{inputenc} \usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc} \usepackage[T1]{fontenc}
\usepackage[hidelinks]{hyperref} \usepackage[hidelinks]{hyperref}
\usepackage{graphicx}
#include "geometry" #include "geometry"
\newif\ifpdf \newif\ifpdf
@ -24,5 +25,8 @@
\begin{document} \begin{document}
#include "titlepage" #include "titlepage"
\pagestyle{empty}
\clearpage \clearpage

View file

@ -1,2 +1,2 @@
\documentclass[A5,twocolumn]{book} \documentclass[a5,onecolumn]{book}

4
templates/tex/img.tex Normal file
View file

@ -0,0 +1,4 @@
\begin{center}
\includegraphics[width=.9\textwidth]{%(inner)s}
\end{center}

View file

@ -0,0 +1,3 @@
\subsection*{\begin{center} \textbf{%(name)s} \end{center}}

View file

@ -1,5 +1,3 @@
\subsection*{\begin{center} \textbf{%(nr)d} \end{center}}
\noindent \noindent
%(text)s %(text)s
\newline \vspace{1em}

View file

@ -1,3 +1,5 @@
\phantomsection \phantomsection
\refstepcounter{sectionnr} \refstepcounter{sectionnr}
\label{section%(nr)d} \label{section%(nr)d}
\subsection*{\begin{center} \textbf{%(nr)d} \end{center}}

View file

@ -0,0 +1 @@
%(name)s

View file

@ -1,2 +1 @@
%(nr)d %(nr)d

View file

@ -1,4 +1,4 @@
* TODO [33/59] [55%] * TODO [37/65] [56%]
- [X] Debug output - [X] Debug output
- [X] DOT output - [X] DOT output
- [X] LaTeX output - [X] LaTeX output
@ -36,13 +36,12 @@
- [X] Book option to set max section number to use - [X] Book option to set max section number to use
- [X] Quote strings to not break formatting. - [X] Quote strings to not break formatting.
- [X] Include other templates from a template. - [X] Include other templates from a template.
- [ ] Template for book introduction (including rules etc) - [X] Template for book introduction (including rules etc)
Sections with some markup (has number 0?) are added as chapters Sections with some markup (has number 0?) are added as chapters
of introduction, otherwise formatted identical to other sections. of introduction, otherwise formatted identical to other sections.
- [ ] Inserting images - [X] Inserting images
- [ ] More formatting possibilities in sections - [X] HTML hide intro sections with link to display again
Look at existing gamebooks to get ideas. - [X] Only accept specific characters in section names
- [ ] Only accept specific characters in identifiers (eg section names)
eg [a-z][a-z_0-9]+ eg [a-z][a-z_0-9]+
- [ ] Random pick of link to follow from a section. - [ ] Random pick of link to follow from a section.
- [ ] Possibility to make predictable random numbers and shuffling for testing - [ ] Possibility to make predictable random numbers and shuffling for testing
@ -73,6 +72,27 @@
- [ ] Some way to insert optional random numbers table at end of book - [ ] Some way to insert optional random numbers table at end of book
- [ ] Defensive removal of any weird unicode not handled by quoting. - [ ] Defensive removal of any weird unicode not handled by quoting.
- [ ] Somewhat user-friendly error messages - [ ] Somewhat user-friendly error messages
- [ ] More formatting possibilities in sections
Look at existing gamebooks to get ideas.
- [ ] Document Gamebook format - [ ] Document Gamebook format
- [ ] HTML CSS - [ ] HTML CSS
- [ ] Higher level text-language for Gamebooks - [ ] Higher level text-language for Gamebooks
- [ ] BGG forum output (.bgg)
- [ ] Make sure HTML output works with javascript disabled and in inferior browsers