mirror of
https://github.com/Oreolek/gamebookformat.git
synced 2024-05-14 15:08:19 +03:00
More formatting options.
This commit is contained in:
parent
5a468bdd65
commit
f60a650598
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,7 +8,6 @@ test.dot
|
|||
*.toc
|
||||
*.pdf
|
||||
test.rtf
|
||||
*.png
|
||||
*.out
|
||||
test.html
|
||||
.lein*
|
||||
|
|
4
examples/.gitignore
vendored
4
examples/.gitignore
vendored
|
@ -3,4 +3,6 @@
|
|||
*.html
|
||||
*.dot
|
||||
*.debug
|
||||
*.txt
|
||||
*.txt
|
||||
*.png
|
||||
!testimage.png
|
|
@ -1,12 +1,27 @@
|
|||
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
|
||||
This examples tests gamebook formatting, not so much game mechanics or
|
||||
references. Currently there is nothing here really.
|
||||
This section contains some tricky characters to quote,
|
||||
like } and { and " and ' and \.
|
||||
HTML will probably not like <div> or &boom;.
|
||||
There should be an image below as well.
|
||||
If something broke, turn to [[bad]],
|
||||
otherwise turn to [[good]].
|
||||
[img]testimage.png[/img]
|
||||
|
||||
* dum :dummy:
|
||||
Sections tagged as dummy will not be
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
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
|
||||
This is where the adventure begins. You can go on to the
|
||||
next section, see [[next]] or try the other instead, see [[other]].
|
||||
|
|
BIN
examples/testimage.png
Normal file
BIN
examples/testimage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 393 KiB |
|
@ -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.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
@ -41,6 +42,8 @@ from output import OutputFormat
|
|||
|
||||
USAGE = "usage: %prog [options] inputfile(s)... outputfile"
|
||||
|
||||
SECTION_NAME_RE = re.compile("^[a-z][a-z_0-9]*$")
|
||||
|
||||
def of(extension, name, quote):
|
||||
return {'extension' : extension,
|
||||
'name' : name,
|
||||
|
@ -81,17 +84,15 @@ def parse_file_to_book(inputfile, book):
|
|||
number = None
|
||||
text = ""
|
||||
tags = None
|
||||
intro_section = False
|
||||
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('*'):
|
||||
before_first_section = False
|
||||
if name:
|
||||
add_section_to_book(book, name, text, number)
|
||||
add_section_to_book(book, name, text, intro_section, number)
|
||||
number = None
|
||||
text = ""
|
||||
intro_section = False
|
||||
heading = [h.strip() for h in line[1:].strip().split()]
|
||||
if len(heading) > 1 and heading[-1].startswith(':'):
|
||||
if not heading[-1].endswith(':'):
|
||||
|
@ -106,20 +107,34 @@ def parse_file_to_book(inputfile, book):
|
|||
elif len(heading) == 2:
|
||||
number = int(heading[0])
|
||||
name = heading[1]
|
||||
else:
|
||||
raise Exception("bad section heading %s" % str(heading))
|
||||
else:
|
||||
if not name or not SECTION_NAME_RE.match(name):
|
||||
raise Exception("bad section heading: %s" % str(heading))
|
||||
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()
|
||||
elif len(line.strip()):
|
||||
raise Exception("unknown content before sections: %s" % line.strip())
|
||||
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)
|
||||
if tags:
|
||||
section.add_tags(tags)
|
||||
book.add(section)
|
||||
if number:
|
||||
book.force_section_nr(name, number)
|
||||
if intro_section:
|
||||
book.addintro(section)
|
||||
else:
|
||||
book.add(section)
|
||||
if number:
|
||||
book.force_section_nr(name, number)
|
||||
|
||||
def make_output(outputfilename, templatedirs):
|
||||
for of in OUTPUT_FORMATS:
|
||||
|
@ -134,6 +149,8 @@ def write_book(book, output_format, outputfilename):
|
|||
shuffled_sections = book.shuffle()
|
||||
output = open(outputfilename, 'w')
|
||||
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_end(book, output)
|
||||
save_section_mapping(shuffled_sections, outputfilename)
|
||||
|
|
31
output.py
31
output.py
|
@ -12,6 +12,27 @@ class OutputFormat (object):
|
|||
# FIXME make sure book config is properly quoted
|
||||
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):
|
||||
for i, p in enumerate(shuffled_sections.as_list):
|
||||
if p and not p.hastag('dummy'):
|
||||
|
@ -26,7 +47,7 @@ class OutputFormat (object):
|
|||
self.quote)
|
||||
formatted_text = self.format_section(section, refsdict)
|
||||
print >> output, self.format_with_template("section", {
|
||||
'nr' : shuffled_sections.to_nr[section],
|
||||
'nr' : section.nr,
|
||||
'name' : section.name,
|
||||
'text' : formatted_text,
|
||||
'refs' : '\n'.join(refsdict.getfound()) # hack for DOT output
|
||||
|
@ -92,7 +113,7 @@ class OutputFormat (object):
|
|||
}),
|
||||
|
||||
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):
|
||||
template = self.templates.get(name)
|
||||
|
@ -108,7 +129,7 @@ class ReferenceFormatter (object):
|
|||
self.shuffled_sections = shuffled_sections
|
||||
self.found = set()
|
||||
self.ref_template = ref_template
|
||||
self.items = {'nr' : shuffled_sections.to_nr[section]}
|
||||
self.items = {'nr' : section.nr}
|
||||
self.quote = quote
|
||||
|
||||
def __getitem__(self, key):
|
||||
|
@ -116,8 +137,8 @@ class ReferenceFormatter (object):
|
|||
return self.quote(self.items[key])
|
||||
to_section = self.shuffled_sections.from_name[key]
|
||||
res = self.ref_template % {
|
||||
'nr' : self.shuffled_sections.to_nr[to_section],
|
||||
'from_nr' : self.shuffled_sections.to_nr[self.section]
|
||||
'nr' : to_section.nr,
|
||||
'from_nr' : self.section.nr
|
||||
}
|
||||
if key in self.shuffled_sections.name_to_nr:
|
||||
self.found.add(res)
|
||||
|
|
50
sections.py
50
sections.py
|
@ -17,30 +17,57 @@ class Section:
|
|||
return "Section(%s, %s, %s)" % (repr(self.name), repr(self.text),
|
||||
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:
|
||||
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.from_nr = from_nr
|
||||
self.to_nr = to_nr
|
||||
self.from_name = from_name
|
||||
self.name_to_nr = {}
|
||||
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:
|
||||
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'])
|
||||
|
||||
class Book:
|
||||
def __init__(self):
|
||||
self.sections = []
|
||||
self.introsections = []
|
||||
self.from_name = {}
|
||||
self.nr_sections = {}
|
||||
self.codewords = set()
|
||||
self.config = {'max' : 0,
|
||||
'title' : 'Gamebook',
|
||||
'author' : ''}
|
||||
'author' : '',
|
||||
'starttext' : 'Turn to 1 to begin.',
|
||||
'hideintrotext' : '(hide instructions)',
|
||||
'showintrotext' : '(show instructions)'}
|
||||
|
||||
def configure(self, name, value):
|
||||
if name in INT_BOOK_CONFIG:
|
||||
|
@ -59,6 +86,9 @@ class Book:
|
|||
if len(self.sections) > self.config['max']:
|
||||
self.config['max'] = len(self.sections)
|
||||
|
||||
def addintro(self, section):
|
||||
self.introsections.append(IntroSection(section))
|
||||
|
||||
def add_codeword(self, word):
|
||||
self.codewords.add(word)
|
||||
|
||||
|
@ -70,8 +100,8 @@ class Book:
|
|||
def shuffle(self):
|
||||
as_list = [None]
|
||||
from_nr = {}
|
||||
to_nr = {}
|
||||
shuffled = self.sections[:]
|
||||
shuffled_from_name = {}
|
||||
while len(shuffled) < self.config['max']:
|
||||
dummy = Section('Dummy', '')
|
||||
dummy.add_tags(['dummy'])
|
||||
|
@ -83,16 +113,16 @@ class Book:
|
|||
for nr in range(1, self.config['max'] + 1):
|
||||
if (self.nr_sections.has_key(nr)
|
||||
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):
|
||||
section = shuffled.pop()
|
||||
section = ShuffledSection(nr, shuffled.pop())
|
||||
else:
|
||||
section = None
|
||||
as_list.append(section)
|
||||
from_nr[nr] = section
|
||||
if section:
|
||||
to_nr[section] = nr
|
||||
return ShuffledSections(as_list, from_nr, to_nr, self.from_name.copy(),
|
||||
shuffled_from_name[section.name] = section
|
||||
return ShuffledSections(as_list, from_nr, shuffled_from_name,
|
||||
self.nr_sections)
|
||||
|
||||
class Item (object):
|
||||
|
|
0
templates/DEFAULT/img.txt
Normal file
0
templates/DEFAULT/img.txt
Normal file
2
templates/DEFAULT/introsection.txt
Normal file
2
templates/DEFAULT/introsection.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
#include "introsectionheading"
|
||||
#include "sectionbody"
|
2
templates/DEFAULT/sections_begin.txt
Normal file
2
templates/DEFAULT/sections_begin.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
%(starttext)s
|
1
templates/debug/img.debug
Normal file
1
templates/debug/img.debug
Normal file
|
@ -0,0 +1 @@
|
|||
[IMG]%(inner)s[/IMG]
|
2
templates/debug/introsection.debug
Normal file
2
templates/debug/introsection.debug
Normal file
|
@ -0,0 +1,2 @@
|
|||
%(name)s
|
||||
%(text)s
|
0
templates/dot/introsection.dot
Normal file
0
templates/dot/introsection.dot
Normal file
0
templates/dot/sections_begin.dot
Normal file
0
templates/dot/sections_begin.dot
Normal file
|
@ -12,5 +12,5 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
#include "intro"
|
||||
#include "hideintro"
|
||||
<div class="gamebook">
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
<script>
|
||||
#include "endscript"
|
||||
</script>
|
||||
#include "showintro"
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
if (this.gamebook) {
|
||||
gamebook.turnTo(1);
|
||||
}
|
||||
|
|
2
templates/html/hideintro.html
Normal file
2
templates/html/hideintro.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div class="hideintrolink nodisplay"
|
||||
onclick="gamebook.hideIntroSections()">%(hideintrotext)s</div>
|
1
templates/html/img.html
Normal file
1
templates/html/img.html
Normal file
|
@ -0,0 +1 @@
|
|||
<img src="%(inner)s" class="sectionimage"></img>
|
6
templates/html/introsection.html
Normal file
6
templates/html/introsection.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class="introsection">
|
||||
<span class="introsectionheading">%(name)s</span>
|
||||
<div class="introsectionbody">
|
||||
%(text)s
|
||||
</div>
|
||||
</div>
|
|
@ -64,6 +64,12 @@
|
|||
this.displaySection(nr);
|
||||
},
|
||||
|
||||
'start' : function() {
|
||||
this.hideIntroSections();
|
||||
this.addClassToClass('startlink', 'nodisplay');
|
||||
this.turnTo(1);
|
||||
},
|
||||
|
||||
'displaySection' : function(nr) {
|
||||
if (this.player.currentSection) {
|
||||
this.player.currentSection.element.style.display = 'none';
|
||||
|
@ -74,6 +80,35 @@
|
|||
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) {
|
||||
var enableNextLink = true;
|
||||
var hasXorScope = false;
|
||||
|
|
2
templates/html/sections_begin.html
Normal file
2
templates/html/sections_begin.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div class="startlink"
|
||||
onclick="gamebook.start()">%(starttext)s</div>
|
2
templates/html/showintro.html
Normal file
2
templates/html/showintro.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div class="displayintrolink nodisplay"
|
||||
onclick="gamebook.showIntroSections()">%(showintrotext)s</div>
|
2
templates/html/startlink.html
Normal file
2
templates/html/startlink.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div class="startlink"
|
||||
onclick="gamebook.start()">%(starttext)s</div>
|
|
@ -18,3 +18,8 @@
|
|||
.has {font-style: italic;}
|
||||
.hasnot {font-style: italic;}
|
||||
.collectionTemplate {display: none;}
|
||||
.sectionimage {width: 100%%; padding: 1em;}
|
||||
.nodisplay {display: none;}
|
||||
.startlink {font-weight: bold; cursor: pointer;}
|
||||
.displayintrolink {cursor: pointer;}
|
||||
.hideintrolink {cursor: pointer;}
|
2
templates/rtf/introsectionheading.rtf
Normal file
2
templates/rtf/introsectionheading.rtf
Normal file
|
@ -0,0 +1,2 @@
|
|||
\b \qc %(name)s
|
||||
\b0\
|
|
@ -2,6 +2,7 @@
|
|||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[hidelinks]{hyperref}
|
||||
\usepackage{graphicx}
|
||||
|
||||
#include "geometry"
|
||||
\newif\ifpdf
|
||||
|
@ -24,5 +25,8 @@
|
|||
\begin{document}
|
||||
|
||||
#include "titlepage"
|
||||
|
||||
\pagestyle{empty}
|
||||
|
||||
\clearpage
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
\documentclass[A5,twocolumn]{book}
|
||||
\documentclass[a5,onecolumn]{book}
|
||||
|
||||
|
|
4
templates/tex/img.tex
Normal file
4
templates/tex/img.tex
Normal file
|
@ -0,0 +1,4 @@
|
|||
\begin{center}
|
||||
\includegraphics[width=.9\textwidth]{%(inner)s}
|
||||
\end{center}
|
||||
|
3
templates/tex/introsectionheading.tex
Normal file
3
templates/tex/introsectionheading.tex
Normal file
|
@ -0,0 +1,3 @@
|
|||
\subsection*{\begin{center} \textbf{%(name)s} \end{center}}
|
||||
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
\subsection*{\begin{center} \textbf{%(nr)d} \end{center}}
|
||||
|
||||
\noindent
|
||||
%(text)s
|
||||
\newline
|
||||
\vspace{1em}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
\phantomsection
|
||||
\refstepcounter{sectionnr}
|
||||
\label{section%(nr)d}
|
||||
\subsection*{\begin{center} \textbf{%(nr)d} \end{center}}
|
||||
|
||||
|
|
1
templates/txt/introsectionheading.txt
Normal file
1
templates/txt/introsectionheading.txt
Normal file
|
@ -0,0 +1 @@
|
|||
%(name)s
|
|
@ -1,2 +1 @@
|
|||
%(nr)d
|
||||
|
||||
|
|
32
todo.org
32
todo.org
|
@ -1,4 +1,4 @@
|
|||
* TODO [33/59] [55%]
|
||||
* TODO [37/65] [56%]
|
||||
- [X] Debug output
|
||||
- [X] DOT output
|
||||
- [X] LaTeX output
|
||||
|
@ -36,13 +36,12 @@
|
|||
- [X] Book option to set max section number to use
|
||||
- [X] Quote strings to not break formatting.
|
||||
- [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
|
||||
of introduction, otherwise formatted identical to other sections.
|
||||
- [ ] Inserting images
|
||||
- [ ] More formatting possibilities in sections
|
||||
Look at existing gamebooks to get ideas.
|
||||
- [ ] Only accept specific characters in identifiers (eg section names)
|
||||
- [X] Inserting images
|
||||
- [X] HTML hide intro sections with link to display again
|
||||
- [X] Only accept specific characters in section names
|
||||
eg [a-z][a-z_0-9]+
|
||||
- [ ] Random pick of link to follow from a section.
|
||||
- [ ] 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
|
||||
- [ ] Defensive removal of any weird unicode not handled by quoting.
|
||||
- [ ] Somewhat user-friendly error messages
|
||||
- [ ] More formatting possibilities in sections
|
||||
Look at existing gamebooks to get ideas.
|
||||
- [ ] Document Gamebook format
|
||||
- [ ] HTML CSS
|
||||
- [ ] Higher level text-language for Gamebooks
|
||||
- [ ] BGG forum output (.bgg)
|
||||
- [ ] Make sure HTML output works with javascript disabled and in inferior browsers
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue