diff --git a/.gitignore b/.gitignore index 50160e5..e8b2308 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ fonts/ maps/ bin/ dist/ +dev-assets/guidebook/venv +dev-assets/guidebook/compiled/pages wasm/assets/ *.wasm *.doodad diff --git a/dev-assets/doodads/doors/electric.gif b/dev-assets/doodads/doors/electric.gif new file mode 100644 index 0000000..c280b2d Binary files /dev/null and b/dev-assets/doodads/doors/electric.gif differ diff --git a/dev-assets/doodads/trapdoors/down.gif b/dev-assets/doodads/trapdoors/down.gif new file mode 100644 index 0000000..07a1fc9 Binary files /dev/null and b/dev-assets/doodads/trapdoors/down.gif differ diff --git a/dev-assets/guidebook/build.py b/dev-assets/guidebook/build.py new file mode 100644 index 0000000..d3789fd --- /dev/null +++ b/dev-assets/guidebook/build.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import codecs +import glob +import os +import markdown +import jinja2 +import re + +def main(): + if not os.path.isdir("./compiled"): + print("Make output directory: ./compiled") + os.mkdir("./compiled") + os.mkdir("./compiled/pages") + + # Render the main index.html template. + with codecs.open("./pages/index.html", "r", "utf-8") as fh: + html = fh.read() + html = render_template(html) + with open("./compiled/index.html", "w") as outfh: + outfh.write(html) + + # Load the Markdown wrapper HTML template. + html_wrapper = "$CONTENT" + with codecs.open("./pages/markdown.html", "r", "utf-8") as fh: + html_wrapper = fh.read() + + for md in glob.glob("./pages/*.md"): + filename = md.split(os.path.sep)[-1] + htmlname = filename.replace(".md", ".html") + print("Compile Markdown: {} -> {}".format(filename, htmlname)) + + with codecs.open(md, 'r', 'utf-8') as fh: + data = fh.read() + rendered = markdown.markdown(data, + extensions=["codehilite", "fenced_code"], + ) + html = html_wrapper.replace("$CONTENT", rendered) + html = render_template(html, + title=title_from_markdown(data), + ) + + with open(os.path.join("compiled", "pages", htmlname), "w") as outfh: + outfh.write(html) + +jinja_env = jinja2.Environment() + +def render_template(input, *args, **kwargs): + templ = jinja_env.from_string(input) + return templ.render( + app_name="Project: Doodle", + app_version=get_app_version(), + *args, **kwargs + ) + +def title_from_markdown(text): + """Retrieve the title from the first Markdown header.""" + for line in text.split("\n"): + if line.startswith("# "): + return line[2:] + +def get_app_version(): + """Get the app version from pkg/branding/branding.go in Doodle""" + ver = re.compile(r'Version\s*=\s*"(.+?)"') + with codecs.open("../../pkg/branding/branding.go", "r", "utf-8") as fh: + text = fh.read() + for line in text.split("\n"): + m = ver.search(line) + if m: + return m[1] + +if __name__ == "__main__": + main() diff --git a/dev-assets/guidebook/build.sh b/dev-assets/guidebook/build.sh new file mode 100755 index 0000000..11d620b --- /dev/null +++ b/dev-assets/guidebook/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [[ ! -d "./venv" ]]; then + echo Creating Python virtualenv... + python3 -m venv ./venv + source ./venv/bin/activate + pip install -r requirements.txt +else + source ./venv/bin/activate +fi + +python build.py + +# Copy static files in. +mkdir -p compiled/pages/res +cp -r pages/res/*.* compiled/pages/res/ diff --git a/dev-assets/guidebook/compiled/index.html b/dev-assets/guidebook/compiled/index.html new file mode 100644 index 0000000..cabb96c --- /dev/null +++ b/dev-assets/guidebook/compiled/index.html @@ -0,0 +1,28 @@ + + + + Project: Doodle Guidebook + + + + + + + + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/dev-assets/guidebook/pages/DoodadScripts.md b/dev-assets/guidebook/pages/DoodadScripts.md new file mode 100644 index 0000000..c4dece8 --- /dev/null +++ b/dev-assets/guidebook/pages/DoodadScripts.md @@ -0,0 +1,130 @@ +# Doodad Scripts + +Doodads are programmed using JavaScript which gives them their behavior +and ability to interact with the player and other doodads. + +An example Doodad script looks like the following: + +```javascript +// The main function is called when the doodad is initialized in Play Mode +// at the start of the level. +function main() { + // Important global variables: + // - Self: information about the current Doodad running this script. + // - Events: handle events raised during gameplay. + // - Message: publish or subscribe to named messages to interact with + // other doodads. + + // Logs go to the game's log file (standard output on Linux/Mac). + console.log("%s initialized!", Self.Doodad.Title); + + // If our doodad has 'solid' parts that should prohibit movement, + // define the hitbox here. Coordinates are relative so 0,0 is the + // top-left pixel of the doodad's sprite. + Self.SetHitbox(0, 0, 64, 12); + + // Handle a collision when another doodad (or player) has entered + // the space of our doodad. + Events.OnCollide(function(e) { + // The `e` object holds information about the event. + console.log("Actor %s has entered our hitbox!", e.Actor.ID()); + + // InHitbox is `true` if we defined a hitbox for ourselves, and + // the colliding actor is inside of the hitbox we defined. + if (e.InHitbox) { + // To prohibit movement, return false from the OnCollide handler. + // If you don't return false, the actor is allowed to keep on + // moving through. + return false; + } + }); + + // OnLeave is called when an actor, who was previously colliding with + // us, is no longer doing so. + Events.OnLeave(function(e) { + console.log("Actor %s has stopped colliding!", e.Actor.ID()); + }) +} +``` + +# JavaScript API + +## Global Variables + +The following global variables are available to all Doodad scripts. + +### Self + +Self holds information about the current doodad. The full surface area of +the Self object is subject to change, but some useful things you can access +from it include: + +* Self.Doodad: a pointer to the doodad's file data. + * Self.Doodad.Title: get the title of the doodad file. + * Self.Doodad.Author: the name of the author who wrote the doodad. + * Self.Doodad.Script: the doodad's JavaScript source code. Note that + modifying this won't have any effect in-game, as the script had already + been loaded into the interpreter. + * Self.Doodad.GameVersion: the version of {{ app_name }} that was used + when the doodad was created. + +### Events + +### Message + +## Global Functions + +The following useful functions are also available globally: + +### Timers and Intervals + +Doodad scripts implement setTimeout() and setInterval() functions similar +to those found in web browsers. + +```javascript +// Call a function after 5 seconds. +setTimeout(function() { + console.log("I've been called!"); +}, 5000); +``` + +setTimeout() and setInterval() return an ID number for the timer created. +If you wish to cancel a timer before it has finished, or to stop an interval +from running, you need to pass its ID number into `clearTimeout()` or +`clearInterval()`, respectively. + +```javascript +// Start a 1-second interval +var id = setInterval(function() { + console.log("Tick..."); +}, 1000); + +// Cancel it after 30 seconds. +setTimeout(function() { + clearInterval(id); +}, 30000); +``` + +### Console Logging + +Doodad scripts also implement the `console.log()` and similar functions as +found in web browser APIs. They support "printf" style variable placeholders. + +```javascript +console.log("Hello world!"); +console.error("The answer is %d!", 42); +console.warn("Actor '%s' has collided with us!", e.Actor.ID()); +console.debug("This only logs when the game is in debug mode!"); +``` + +### RGBA(red, green, blue, alpha uint8) + +RGBA initializes a Color variable using the game's native Color type. May +be useful for certain game APIs that take color values. + +Example: RGBA(255, 0, 255, 255) creates an opaque magenta color. + +### Point(x, y int) + +Returns a Point object which refers to a location in the game world. This +type is required for certain game APIs. diff --git a/dev-assets/guidebook/pages/Home.md b/dev-assets/guidebook/pages/Home.md new file mode 100644 index 0000000..c7be728 --- /dev/null +++ b/dev-assets/guidebook/pages/Home.md @@ -0,0 +1,9 @@ +# Guidebook to {{ app_name }} + +This is the users manual to {{ app_name }}, a drawing-based maze game. + +## Creating Custom Content + +* [Doodad Scripts](DoodadScripts.html) + +v{{ app_version }} diff --git a/dev-assets/guidebook/pages/index.html b/dev-assets/guidebook/pages/index.html new file mode 100644 index 0000000..bb332e4 --- /dev/null +++ b/dev-assets/guidebook/pages/index.html @@ -0,0 +1,28 @@ + + + + {{ app_name }} Guidebook + + + + + + + + +
+
+ +
+
+ + + diff --git a/dev-assets/guidebook/pages/markdown.html b/dev-assets/guidebook/pages/markdown.html new file mode 100644 index 0000000..0ab3a1c --- /dev/null +++ b/dev-assets/guidebook/pages/markdown.html @@ -0,0 +1,15 @@ + + + + {{ title }} + + + + + + + +$CONTENT + + + diff --git a/dev-assets/guidebook/pages/res/main.css b/dev-assets/guidebook/pages/res/main.css new file mode 100644 index 0000000..4aa5246 --- /dev/null +++ b/dev-assets/guidebook/pages/res/main.css @@ -0,0 +1,66 @@ +body,html { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +body { + background-color: #EEE; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 16px; + color: #000; + line-height: 1.4em; +} + +div#sidebar { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 220px; + background-color: #000066; + color: #EEE; +} + +nav { + padding: 1.5rem; +} + +nav ul { + margin: 0; + padding: 0; + list-style: none; +} +nav ul li { + display: block; +} +nav a:link, nav a:visited { + color: #EEE; + text-decoration: none; +} +nav a:hover, nav a:active { + color: #F9F; + text-decoration: underline; +} + +div#content { + position: absolute; + top: 0; + left: 220px; + right: 0; + bottom: 0; +} + +div#content div { + position: relative; + width: 100%; + height: 100%; +} + +div#content iframe { + width: 100%; + height: 100%; + border: 0; + overflow: auto; +} diff --git a/dev-assets/guidebook/pages/res/markdown.css b/dev-assets/guidebook/pages/res/markdown.css new file mode 100644 index 0000000..5a193aa --- /dev/null +++ b/dev-assets/guidebook/pages/res/markdown.css @@ -0,0 +1,25 @@ +body { + background-color: #EEE; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 16px; + color: #000; + line-height: 1.4em; + margin: 2rem; +} + +a:link, a:visited { + color: #006699; + text-decoration: underline; +} +a:hover, a:active { + color: #996600; +} + +.codehilite { + border: 1px solid #222; +} +pre { + font-family: "Lucida Console", "DejaVu LGC Sans Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Ubuntu Mono", monospace; + margin: 0; + padding: 8px; +} diff --git a/dev-assets/guidebook/pages/res/syntax.css b/dev-assets/guidebook/pages/res/syntax.css new file mode 100644 index 0000000..bf82aed --- /dev/null +++ b/dev-assets/guidebook/pages/res/syntax.css @@ -0,0 +1,69 @@ +.codehilite .hll { background-color: #ffffcc } +.codehilite { background: #f8f8f8; } +.codehilite .c { color: #408080; font-style: italic } /* Comment */ +.codehilite .err { border: 1px solid #FF0000 } /* Error */ +.codehilite .k { color: #008000; font-weight: bold } /* Keyword */ +.codehilite .o { color: #666666 } /* Operator */ +.codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ +.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */ +.codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ +.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */ +.codehilite .gd { color: #A00000 } /* Generic.Deleted */ +.codehilite .ge { font-style: italic } /* Generic.Emph */ +.codehilite .gr { color: #FF0000 } /* Generic.Error */ +.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.codehilite .gi { color: #00A000 } /* Generic.Inserted */ +.codehilite .go { color: #888888 } /* Generic.Output */ +.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.codehilite .gs { font-weight: bold } /* Generic.Strong */ +.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.codehilite .gt { color: #0044DD } /* Generic.Traceback */ +.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.codehilite .kp { color: #008000 } /* Keyword.Pseudo */ +.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.codehilite .kt { color: #B00040 } /* Keyword.Type */ +.codehilite .m { color: #666666 } /* Literal.Number */ +.codehilite .s { color: #BA2121 } /* Literal.String */ +.codehilite .na { color: #7D9029 } /* Name.Attribute */ +.codehilite .nb { color: #008000 } /* Name.Builtin */ +.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.codehilite .no { color: #880000 } /* Name.Constant */ +.codehilite .nd { color: #AA22FF } /* Name.Decorator */ +.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.codehilite .nf { color: #0000FF } /* Name.Function */ +.codehilite .nl { color: #A0A000 } /* Name.Label */ +.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.codehilite .nv { color: #19177C } /* Name.Variable */ +.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.codehilite .w { color: #bbbbbb } /* Text.Whitespace */ +.codehilite .mb { color: #666666 } /* Literal.Number.Bin */ +.codehilite .mf { color: #666666 } /* Literal.Number.Float */ +.codehilite .mh { color: #666666 } /* Literal.Number.Hex */ +.codehilite .mi { color: #666666 } /* Literal.Number.Integer */ +.codehilite .mo { color: #666666 } /* Literal.Number.Oct */ +.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */ +.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ +.codehilite .sc { color: #BA2121 } /* Literal.String.Char */ +.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ +.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.codehilite .sx { color: #008000 } /* Literal.String.Other */ +.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */ +.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ +.codehilite .ss { color: #19177C } /* Literal.String.Symbol */ +.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.codehilite .fm { color: #0000FF } /* Name.Function.Magic */ +.codehilite .vc { color: #19177C } /* Name.Variable.Class */ +.codehilite .vg { color: #19177C } /* Name.Variable.Global */ +.codehilite .vi { color: #19177C } /* Name.Variable.Instance */ +.codehilite .vm { color: #19177C } /* Name.Variable.Magic */ +.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/dev-assets/guidebook/requirements.txt b/dev-assets/guidebook/requirements.txt new file mode 100644 index 0000000..f27036f --- /dev/null +++ b/dev-assets/guidebook/requirements.txt @@ -0,0 +1,2 @@ +markdown +jinja2