mirror of
https://github.com/Oreolek/gamebookformat.git
synced 2024-06-16 23:20:44 +03:00
Initial basic version of gamebook formatter.
Reused old paragraphs.py and test_paragraphs.py. Implemented very basic output for Graphviz DOT and LaTeX files. Basic plain-text debug output.
This commit is contained in:
commit
4986b40cf0
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
*~
|
||||
*.pyc
|
||||
*.debug
|
||||
*.tex
|
||||
*.dot
|
||||
*.log
|
||||
*.aux
|
||||
*.toc
|
||||
*.pdf
|
||||
*.rtf
|
12
debug.py
Normal file
12
debug.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from output import OutputFormat
|
||||
|
||||
class DebugFormat (OutputFormat):
|
||||
def __init__(self):
|
||||
super(DebugFormat, self).__init__('debug', 'Gamebook Debug Output')
|
||||
|
||||
def write_begin(self, book, output):
|
||||
print >> output, "BEGIN DEBUG OUTPUT"
|
||||
print >> output, "Number of paragraphs: ", book.max
|
||||
|
||||
def write_end(self, book, output):
|
||||
print >> output, "END DEBUG OUTPUT"
|
33
dot.py
Normal file
33
dot.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from output import OutputFormat
|
||||
|
||||
class DotParagraphFormat (object):
|
||||
def __init__(self, fromparagraph):
|
||||
self.fromparagraph = fromparagraph
|
||||
self.toparagraphs = []
|
||||
|
||||
def __call__(self, toparagraph, shuffled_paragraphs):
|
||||
self.toparagraphs.append(toparagraph)
|
||||
return ''
|
||||
|
||||
def write(self, shuffled_paragraphs, output):
|
||||
for toparagraph in self.toparagraphs:
|
||||
print >> output, "%s->%s" % (
|
||||
shuffled_paragraphs.to_nr[self.fromparagraph],
|
||||
shuffled_paragraphs.to_nr[toparagraph])
|
||||
|
||||
class DotFormat (OutputFormat):
|
||||
def __init__(self):
|
||||
super(DotFormat, self).__init__('dot', 'Graphviz paragraph flowchart')
|
||||
|
||||
def write_begin(self, book, output):
|
||||
print >> output, "digraph gamebook {"
|
||||
|
||||
def write_paragraph(self, paragraph, shuffled_paragraphs, output):
|
||||
paragraphformat = DotParagraphFormat(paragraph)
|
||||
paragraph.format(shuffled_paragraphs, paragraphformat)
|
||||
paragraphformat.write(shuffled_paragraphs, output)
|
||||
|
||||
def write_end(self, book, output):
|
||||
print >> output, "}"
|
||||
|
||||
|
59
formatgamebook.py
Executable file
59
formatgamebook.py
Executable file
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
import paragraphs
|
||||
|
||||
from output import OutputFormat
|
||||
from latex import LatexFormat
|
||||
from rtf import RtfFormat
|
||||
from dot import DotFormat
|
||||
from debug import DebugFormat
|
||||
|
||||
USAGE = "usage: %prog [options] inputfile(s)... outputfile"
|
||||
|
||||
OUTPUT_FORMATS = [LatexFormat(),
|
||||
RtfFormat(),
|
||||
DotFormat(),
|
||||
DebugFormat()]
|
||||
|
||||
def make_supported_formats_list_string():
|
||||
return "Supported Output Formats:\n" + "\n".join(
|
||||
[str(f) for f in OUTPUT_FORMATS])
|
||||
|
||||
def format_gamebook(inputfilenames, outputfilename):
|
||||
output_format = find_output_format(outputfilename)
|
||||
book = paragraphs.Book()
|
||||
for inputfilename in inputfilenames:
|
||||
parse_file_to_book(open(inputfilename, 'r'), book)
|
||||
output_format.write(book, open(outputfilename, 'w'))
|
||||
|
||||
def parse_file_to_book(inputfile, book):
|
||||
for name,contents in json.load(inputfile).iteritems():
|
||||
paragraph = paragraphs.Paragraph(name)
|
||||
if 'text' in contents:
|
||||
paragraph.addtext(contents['text'])
|
||||
if 'number' in contents:
|
||||
book.add(paragraph, contents['number'])
|
||||
else:
|
||||
book.add(paragraph)
|
||||
|
||||
def find_output_format(outputfilename):
|
||||
for of in OUTPUT_FORMATS:
|
||||
if of.supports(outputfilename):
|
||||
return of
|
||||
raise Exception("Unsupported or unknown output format for %s."
|
||||
% outputfile)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser(epilog=make_supported_formats_list_string(),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('inputfiles', metavar='inputfile', nargs='+',
|
||||
help='input gamebook file (eg test.json)')
|
||||
ap.add_argument('outputfile', metavar='outputfile',
|
||||
help='output file (eg test.tex or test.rtf)')
|
||||
args = ap.parse_args()
|
||||
format_gamebook(args.inputfiles, args.outputfile)
|
||||
|
54
latex.py
Normal file
54
latex.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from output import OutputFormat
|
||||
|
||||
DEFAULT_DOCUMENT_START = """
|
||||
\\documentclass[a4,onecolumn]{article}
|
||||
\\usepackage[utf8]{inputenc}
|
||||
\\usepackage[T1]{fontenc}
|
||||
\\usepackage[hidelinks]{hyperref}
|
||||
|
||||
\\usepackage[top=3.3cm, bottom=3.3cm, left=2cm, right=2cm]{geometry}
|
||||
|
||||
\\newif\\ifpdf
|
||||
\\ifx\\pdfoutput\\undefined
|
||||
\\pdffalse
|
||||
\\else
|
||||
\\ifnum\\pdfoutput=1
|
||||
\\pdftrue
|
||||
\\else
|
||||
\\pdffalse
|
||||
\\fi
|
||||
\\fi
|
||||
|
||||
\\title{Gamebook}
|
||||
\\author{}
|
||||
\\date{}
|
||||
|
||||
\\begin{document}
|
||||
|
||||
\\thispagestyle{empty}
|
||||
\\clearpage
|
||||
|
||||
"""
|
||||
|
||||
def format_latex_paragraph_ref(paragraph, shuffled_paragraphs):
|
||||
return ("\\textbf{%d}" % shuffled_paragraphs.to_nr[paragraph])
|
||||
|
||||
class LatexFormat (OutputFormat):
|
||||
def __init__(self):
|
||||
super(LatexFormat, self).__init__('tex', 'LaTeX')
|
||||
|
||||
def write_begin(self, book, output):
|
||||
print >> output, DEFAULT_DOCUMENT_START
|
||||
|
||||
def write_paragraph(self, paragraph, shuffled_paragraphs, output):
|
||||
print >> output, " \\noindent"
|
||||
print >> output, format_latex_paragraph_ref(paragraph,
|
||||
shuffled_paragraphs)
|
||||
print >> output, " -- "
|
||||
print >> output, paragraph.format(shuffled_paragraphs,
|
||||
format_latex_paragraph_ref)
|
||||
print >> output, "\\newline"
|
||||
print >> output
|
||||
|
||||
def write_end(self, book, output):
|
||||
print >> output, "\end{document}"
|
35
output.py
Normal file
35
output.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from paragraphs import paragraph_refs_format
|
||||
|
||||
def default_paragraph_link_render(paragraph, shuffled_paragraphs):
|
||||
return str(shuffled_paragraphs.to_nr[paragraph])
|
||||
|
||||
class OutputFormat (object):
|
||||
def __init__(self, extension, name):
|
||||
self.extension = extension
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return ".%s: %s" % (self.extension, self.name)
|
||||
|
||||
def write(self, book, output):
|
||||
self.write_begin(book, output)
|
||||
self.write_shuffled_paragraphs(book.shuffle(), output)
|
||||
self.write_end(book, output)
|
||||
|
||||
def write_begin(self, book, output):
|
||||
pass
|
||||
|
||||
def write_shuffled_paragraphs(self, shuffled_paragraphs, output):
|
||||
for p in shuffled_paragraphs.as_list[1:]:
|
||||
self.write_paragraph(p, shuffled_paragraphs, output)
|
||||
|
||||
def write_paragraph(self, paragraph, shuffled_paragraphs, output):
|
||||
print >> output, shuffled_paragraphs.to_nr[paragraph]
|
||||
print >> output, paragraph.format(shuffled_paragraphs,
|
||||
default_paragraph_link_render)
|
||||
|
||||
def write_end(self, book, output):
|
||||
pass
|
||||
|
||||
def supports(self, filename):
|
||||
return filename.endswith('.' + self.extension)
|
161
paragraphs.py
Normal file
161
paragraphs.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python2.5
|
||||
|
||||
import random
|
||||
import sys
|
||||
|
||||
def paragraph_refs_format(s,
|
||||
refs,
|
||||
shuffled=None,
|
||||
render_callback=None,
|
||||
bad_callback=None):
|
||||
"""
|
||||
render_callback, if set, must be a function accepting a paragraph name
|
||||
and a ShuffledParagraphs object. It must return a string formatting
|
||||
of a link to that reference. Its use is for instance if you render
|
||||
the paragraphs to HTML you can return a HTML link. It will only be
|
||||
used if format %c is used.
|
||||
|
||||
bad_callback, if set, must be a function accepting a paragraph
|
||||
name and a ShuffledParagraphs object. It may attempt to construct
|
||||
a new Paragraph object standing in for a missing paragraph, if there
|
||||
is a way to automatically construct some paragraphs in the game.
|
||||
If it can not construct a paragraph it should return None or False
|
||||
which will cause the formatting to fail and raise an exception.
|
||||
"""
|
||||
oi = 0
|
||||
next_ref = 0
|
||||
res = ''
|
||||
i = s.find('%')
|
||||
while i >= 0:
|
||||
res = res + s[oi:i]
|
||||
skip = 0
|
||||
code = s[i+1]
|
||||
if code == '%':
|
||||
res += '%'
|
||||
else:
|
||||
if code == '(':
|
||||
ei = s.find(')', i+1)
|
||||
if ei < 0:
|
||||
raise "bad format, missing ) after %("
|
||||
ref = s[i+2:ei]
|
||||
skip = len(ref) + 2
|
||||
code = s[ei+1]
|
||||
else:
|
||||
ref = refs[next_ref]
|
||||
if type(ref) in set([str, unicode]):
|
||||
if ref in shuffled.from_name:
|
||||
ref = shuffled.from_name[ref]
|
||||
elif bad_callback:
|
||||
created_ref = bad_callback(ref, shuffled)
|
||||
if not created_ref:
|
||||
raise "bad reference " + ref
|
||||
ref = created_ref
|
||||
if code == 'n':
|
||||
res += str(shuffled.to_nr[ref])
|
||||
elif code == 'c':
|
||||
if render_callback:
|
||||
res += render_callback(ref, shuffled)
|
||||
else:
|
||||
res += str(ref)
|
||||
elif code == 's':
|
||||
res += str(ref.name)
|
||||
elif code == 'r':
|
||||
res += repr(ref.name)
|
||||
next_ref = next_ref + 1
|
||||
oi = i + 2 + skip
|
||||
i = s.find('%', oi)
|
||||
return res + s[oi:]
|
||||
|
||||
class ParagraphItem:
|
||||
def __init__(self, text, refs=None, title=None):
|
||||
self.text = text
|
||||
self.title = title
|
||||
if refs:
|
||||
self.refs = refs
|
||||
else:
|
||||
self.refs = []
|
||||
|
||||
def __repr__(self):
|
||||
return "ParagraphItem(%s, %s, %s)" % (self.text.__repr__(),
|
||||
self.refs.__repr__(),
|
||||
self.title.__repr__())
|
||||
|
||||
def format(self, shuffled=None, render_callback=None, bad_callback=None):
|
||||
return paragraph_refs_format(self.text, self.refs, shuffled, render_callback,
|
||||
bad_callback)
|
||||
|
||||
class Paragraph:
|
||||
def __init__(self, name, text=None, refs=None, items=None):
|
||||
self.name = name
|
||||
if text:
|
||||
self.text = text
|
||||
else:
|
||||
self.text = ''
|
||||
if items:
|
||||
self.items = items
|
||||
else:
|
||||
self.items = []
|
||||
if refs:
|
||||
self.refs = refs
|
||||
else:
|
||||
self.refs = []
|
||||
|
||||
def __repr__(self):
|
||||
return "Paragraph(%s, %s, %s, %s)" % (self.name.__repr__(),
|
||||
self.text.__repr__(),
|
||||
self.refs.__repr__(),
|
||||
self.items.__repr__())
|
||||
def addtext(self, moretext):
|
||||
self.text = self.text + moretext
|
||||
|
||||
def format(self, shuffled=None, render_callback=None, bad_callback=None):
|
||||
return paragraph_refs_format(self.text, self.refs, shuffled, render_callback,
|
||||
bad_callback)
|
||||
|
||||
class ShuffledParagraphs:
|
||||
def __init__(self, as_list, from_nr, to_nr, from_name):
|
||||
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]]
|
||||
|
||||
class Book:
|
||||
def __init__(self):
|
||||
self.paragraphs = []
|
||||
self.nr_paragraphs = {}
|
||||
self.max = 0
|
||||
|
||||
def add(self, paragraph, nr=None):
|
||||
self.paragraphs.append(paragraph)
|
||||
if len(self.paragraphs) > self.max:
|
||||
self.max = len(self.paragraphs)
|
||||
if nr:
|
||||
self.nr_paragraphs[nr] = paragraph
|
||||
if nr > self.max:
|
||||
self.max = nr
|
||||
|
||||
def shuffle(self):
|
||||
as_list = [None]
|
||||
from_nr = {}
|
||||
to_nr = {}
|
||||
from_name = {}
|
||||
shuffled = self.paragraphs[:]
|
||||
for p in self.nr_paragraphs.values():
|
||||
shuffled.remove(p)
|
||||
random.shuffle(shuffled)
|
||||
for nr in range(1, self.max + 1):
|
||||
if self.nr_paragraphs.has_key(nr):
|
||||
paragraph = self.nr_paragraphs[nr]
|
||||
elif len(shuffled):
|
||||
paragraph = shuffled.pop()
|
||||
else:
|
||||
paragraph = None
|
||||
as_list.append(paragraph)
|
||||
from_nr[nr] = paragraph
|
||||
if paragraph:
|
||||
to_nr[paragraph] = nr
|
||||
from_name[paragraph.name] = paragraph
|
||||
return ShuffledParagraphs(as_list, from_nr, to_nr, from_name)
|
6
rtf.py
Normal file
6
rtf.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from output import OutputFormat
|
||||
|
||||
class RtfFormat (OutputFormat):
|
||||
def __init__(self):
|
||||
super(RtfFormat, self).__init__('rtf', 'Rich Text Format')
|
||||
|
18
test.json
Normal file
18
test.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"start" : {
|
||||
"text" : "This is where the adventure begins. You can go on to the next paragraph, see %(next)c or try the other instread, see %(other)c.",
|
||||
"number" : 1
|
||||
},
|
||||
|
||||
"next" : {
|
||||
"text" : "This is the next paragraph. Go on to the end at %(end)c."
|
||||
},
|
||||
|
||||
"other" : {
|
||||
"text" : "This is another paragraph. You can try the next paragraph now, see %(next)c, or go on to the end, see %(end)c."
|
||||
},
|
||||
|
||||
"end" : {
|
||||
"text" : "The end."
|
||||
}
|
||||
}
|
13
test.sh
Normal file
13
test.sh
Normal file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
|
||||
./formatgamebook.py test.json test.debug
|
||||
cat test.debug
|
||||
|
||||
./formatgamebook.py test.json test.dot
|
||||
dot -Tpng test.dot > test.png && open test.png
|
||||
|
||||
./formatgamebook.py test.json test.tex
|
||||
|
||||
#./formatgamebook.py test.json test.rtf
|
||||
|
||||
|
76
test_paragraphs.py
Executable file
76
test_paragraphs.py
Executable file
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env python2.5
|
||||
|
||||
import unittest
|
||||
from unittest import TestCase
|
||||
|
||||
import paragraphs
|
||||
|
||||
class TestParagraph(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_create(self):
|
||||
p = paragraphs.Paragraph("foo")
|
||||
self.assertEqual(p.name, "foo")
|
||||
|
||||
class TestBook(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_create(self):
|
||||
g = paragraphs.Book()
|
||||
self.assertEqual(g.paragraphs, [])
|
||||
self.assertEqual(g.nr_paragraphs, {})
|
||||
self.assertEqual(g.max, 0)
|
||||
|
||||
def test_old_big_mess(self):
|
||||
"""This was tested using print statements in the old
|
||||
paragraphs __main__. Not pretty. Keeping it here anyway
|
||||
as long as it doesn't cause too many problems to update."""
|
||||
from paragraphs import paragraph_refs_format
|
||||
g = paragraphs.Book()
|
||||
c = paragraphs.Paragraph("c", "aaac")
|
||||
g.add(paragraphs.Paragraph("a", "aaa"))
|
||||
g.add(paragraphs.Paragraph("b", "aaab"))
|
||||
g.add(c)
|
||||
g.add(paragraphs.Paragraph("d", "aaad"), 22)
|
||||
g.add(paragraphs.Paragraph("e", "aaae"), 1)
|
||||
g.add(paragraphs.Paragraph("f", "aaaf",
|
||||
[paragraphs.ParagraphItem("fff")]))
|
||||
g.add(paragraphs.Paragraph("g", "aaag",
|
||||
[paragraphs.ParagraphItem("ggg", "G")]))
|
||||
m = paragraphs.Paragraph("m")
|
||||
m.addtext("m")
|
||||
m.addtext("t")
|
||||
g.add(m)
|
||||
shuffled = g.shuffle()
|
||||
|
||||
self.assertEqual(len(shuffled.as_list), 23)
|
||||
self.assertEqual(paragraph_refs_format('abc', []), 'abc')
|
||||
self.assertEqual(paragraph_refs_format('abc%%z', []), 'abc%z')
|
||||
self.assertEqual(paragraph_refs_format(
|
||||
'abc%sx',
|
||||
[paragraphs.Paragraph("p1", "111")]),
|
||||
'abcp1x')
|
||||
self.assertEqual(paragraph_refs_format(
|
||||
'abc%ry', [paragraphs.Paragraph("p2", "222")]),
|
||||
"abc'p2'y")
|
||||
self.assertEqual(paragraph_refs_format('%%a%nbc%su', ["f", c],
|
||||
shuffled),
|
||||
'%%a%dbccu' % shuffled.name_to_nr["f"])
|
||||
self.assertEqual(paragraph_refs_format('abc%nu', ["c"], shuffled),
|
||||
'abc%du' % shuffled.name_to_nr["c"])
|
||||
self.assertEqual(paragraph_refs_format('%s', [m]),
|
||||
'm')
|
||||
self.assertEqual(m.text, 'mt')
|
||||
|
||||
def test_name_replace(self):
|
||||
game = paragraphs.Book()
|
||||
game.add(paragraphs.Paragraph('foo'))
|
||||
shuffled = game.shuffle()
|
||||
self.assertEqual(
|
||||
'a1b',
|
||||
paragraphs.paragraph_refs_format('a%(foo)nb', [], shuffled))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
12
todo.org
Normal file
12
todo.org
Normal file
|
@ -0,0 +1,12 @@
|
|||
* Must Add [2/5]
|
||||
- [X] Debug output
|
||||
- [X] DOT output
|
||||
- [ ] LaTeX output
|
||||
- [ ] RTF output
|
||||
- [ ] Save paragraph-number mapping and reuse automatically
|
||||
|
||||
* Known Bugs [0/0]
|
||||
|
||||
* Nice Features To Have Later [0/0]
|
||||
- [ ] Command-line flag to set max paragraph number to use
|
||||
- [ ] Dummy paragraphs
|
Loading…
Reference in a new issue