1
0
Fork 0
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:
Pelle Nilsson 2013-05-27 23:33:36 +02:00
commit 4986b40cf0
14 changed files with 489 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
*~
*.pyc
*.debug
*.tex
*.dot
*.log
*.aux
*.toc
*.pdf
*.rtf

12
debug.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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."
}
}

0
test.out Normal file
View file

BIN
test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

13
test.sh Normal file
View 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
View 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
View 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