diff --git a/.gitignore b/.gitignore index 1fcd153..3e5979f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ test.dot *.toc *.pdf test.rtf -*.png *.out test.html .lein* diff --git a/examples/.gitignore b/examples/.gitignore index c76759c..5e8b2fb 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -3,4 +3,6 @@ *.html *.dot *.debug -*.txt \ No newline at end of file +*.txt +*.png +!testimage.png \ No newline at end of file diff --git a/examples/format.gamebook b/examples/format.gamebook index cb6b042..f358487 100644 --- a/examples/format.gamebook +++ b/examples/format.gamebook @@ -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
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 diff --git a/examples/references.gamebook b/examples/references.gamebook index 746b33a..6159932 100644 --- a/examples/references.gamebook +++ b/examples/references.gamebook @@ -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]]. diff --git a/examples/testimage.png b/examples/testimage.png new file mode 100644 index 0000000..41e39ae Binary files /dev/null and b/examples/testimage.png differ diff --git a/formatgamebook.py b/formatgamebook.py index b177193..f474c03 100755 --- a/formatgamebook.py +++ b/formatgamebook.py @@ -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) diff --git a/output.py b/output.py index 15ef76e..46cfbad 100644 --- a/output.py +++ b/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) diff --git a/sections.py b/sections.py index c8a1b0c..6eeb7ef 100644 --- a/sections.py +++ b/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): diff --git a/templates/DEFAULT/img.txt b/templates/DEFAULT/img.txt new file mode 100644 index 0000000..e69de29 diff --git a/templates/DEFAULT/introsection.txt b/templates/DEFAULT/introsection.txt new file mode 100644 index 0000000..91bb99f --- /dev/null +++ b/templates/DEFAULT/introsection.txt @@ -0,0 +1,2 @@ +#include "introsectionheading" +#include "sectionbody" diff --git a/templates/DEFAULT/sections_begin.txt b/templates/DEFAULT/sections_begin.txt new file mode 100644 index 0000000..dd41cff --- /dev/null +++ b/templates/DEFAULT/sections_begin.txt @@ -0,0 +1,2 @@ + +%(starttext)s diff --git a/templates/debug/img.debug b/templates/debug/img.debug new file mode 100644 index 0000000..15974fd --- /dev/null +++ b/templates/debug/img.debug @@ -0,0 +1 @@ +[IMG]%(inner)s[/IMG] \ No newline at end of file diff --git a/templates/debug/introsection.debug b/templates/debug/introsection.debug new file mode 100644 index 0000000..530d132 --- /dev/null +++ b/templates/debug/introsection.debug @@ -0,0 +1,2 @@ +%(name)s +%(text)s diff --git a/templates/dot/introsection.dot b/templates/dot/introsection.dot new file mode 100644 index 0000000..e69de29 diff --git a/templates/dot/sections_begin.dot b/templates/dot/sections_begin.dot new file mode 100644 index 0000000..e69de29 diff --git a/templates/html/begin.html b/templates/html/begin.html index ee03fe2..ea6d929 100644 --- a/templates/html/begin.html +++ b/templates/html/begin.html @@ -12,5 +12,5 @@ -#include "intro" +#include "hideintro"
diff --git a/templates/html/end.html b/templates/html/end.html index c3cc145..e1ed820 100644 --- a/templates/html/end.html +++ b/templates/html/end.html @@ -3,5 +3,6 @@ +#include "showintro" diff --git a/templates/html/endscript.html b/templates/html/endscript.html index 926a878..2c82c28 100644 --- a/templates/html/endscript.html +++ b/templates/html/endscript.html @@ -1,3 +1,2 @@ if (this.gamebook) { - gamebook.turnTo(1); } diff --git a/templates/html/hideintro.html b/templates/html/hideintro.html new file mode 100644 index 0000000..fad4ecf --- /dev/null +++ b/templates/html/hideintro.html @@ -0,0 +1,2 @@ + diff --git a/templates/html/img.html b/templates/html/img.html new file mode 100644 index 0000000..c32e899 --- /dev/null +++ b/templates/html/img.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/html/introsection.html b/templates/html/introsection.html new file mode 100644 index 0000000..dc10186 --- /dev/null +++ b/templates/html/introsection.html @@ -0,0 +1,6 @@ +
+%(name)s +
+%(text)s +
+
diff --git a/templates/html/script.html b/templates/html/script.html index 9c7c7a0..ead7c65 100644 --- a/templates/html/script.html +++ b/templates/html/script.html @@ -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; diff --git a/templates/html/sections_begin.html b/templates/html/sections_begin.html new file mode 100644 index 0000000..db98d54 --- /dev/null +++ b/templates/html/sections_begin.html @@ -0,0 +1,2 @@ + diff --git a/templates/html/showintro.html b/templates/html/showintro.html new file mode 100644 index 0000000..44ada42 --- /dev/null +++ b/templates/html/showintro.html @@ -0,0 +1,2 @@ + diff --git a/templates/html/startlink.html b/templates/html/startlink.html new file mode 100644 index 0000000..5f8bd11 --- /dev/null +++ b/templates/html/startlink.html @@ -0,0 +1,2 @@ + diff --git a/templates/html/style.html b/templates/html/style.html index 559e702..660c7e1 100644 --- a/templates/html/style.html +++ b/templates/html/style.html @@ -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;} \ No newline at end of file diff --git a/templates/rtf/introsectionheading.rtf b/templates/rtf/introsectionheading.rtf new file mode 100644 index 0000000..761b4ff --- /dev/null +++ b/templates/rtf/introsectionheading.rtf @@ -0,0 +1,2 @@ +\b \qc %(name)s +\b0\ diff --git a/templates/tex/begin.tex b/templates/tex/begin.tex index 5d9a7c0..d7cc95b 100644 --- a/templates/tex/begin.tex +++ b/templates/tex/begin.tex @@ -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 diff --git a/templates/tex/documentclass.tex b/templates/tex/documentclass.tex index b7115ab..9d9dd99 100644 --- a/templates/tex/documentclass.tex +++ b/templates/tex/documentclass.tex @@ -1,2 +1,2 @@ -\documentclass[A5,twocolumn]{book} +\documentclass[a5,onecolumn]{book} diff --git a/templates/tex/img.tex b/templates/tex/img.tex new file mode 100644 index 0000000..f575c45 --- /dev/null +++ b/templates/tex/img.tex @@ -0,0 +1,4 @@ +\begin{center} +\includegraphics[width=.9\textwidth]{%(inner)s} +\end{center} + diff --git a/templates/tex/introsectionheading.tex b/templates/tex/introsectionheading.tex new file mode 100644 index 0000000..8525347 --- /dev/null +++ b/templates/tex/introsectionheading.tex @@ -0,0 +1,3 @@ +\subsection*{\begin{center} \textbf{%(name)s} \end{center}} + + diff --git a/templates/tex/sectionbody.tex b/templates/tex/sectionbody.tex index 14822c5..1e8e777 100644 --- a/templates/tex/sectionbody.tex +++ b/templates/tex/sectionbody.tex @@ -1,5 +1,3 @@ -\subsection*{\begin{center} \textbf{%(nr)d} \end{center}} - \noindent %(text)s -\newline +\vspace{1em} diff --git a/templates/tex/sectionheading.tex b/templates/tex/sectionheading.tex index a8bb1dd..b42bec0 100644 --- a/templates/tex/sectionheading.tex +++ b/templates/tex/sectionheading.tex @@ -1,3 +1,5 @@ \phantomsection \refstepcounter{sectionnr} \label{section%(nr)d} +\subsection*{\begin{center} \textbf{%(nr)d} \end{center}} + diff --git a/templates/txt/introsectionheading.txt b/templates/txt/introsectionheading.txt new file mode 100644 index 0000000..f444a60 --- /dev/null +++ b/templates/txt/introsectionheading.txt @@ -0,0 +1 @@ +%(name)s diff --git a/templates/txt/sectionheading.txt b/templates/txt/sectionheading.txt index 32a9d09..44e9ecc 100644 --- a/templates/txt/sectionheading.txt +++ b/templates/txt/sectionheading.txt @@ -1,2 +1 @@ %(nr)d - diff --git a/todo.org b/todo.org index 3e36085..143a35f 100644 --- a/todo.org +++ b/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 + + + + + + + + + + + + + + + + +