diff --git a/README.md b/README.md new file mode 100644 index 0000000..37c294e --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +Dirtbags Monarch Of The Hill Server +===================== + +This is a set of thingies to run our Monarch-Of-The-Hill contest, +which in the past has been called +"Tracer FIRE", +"Project 2", +"HACK", +"Queen Of The Hill", +and "Cyber FIRE". + +Information about these events is at +http://dirtbags.net/contest/ + +This software serves up puzzles in a manner similar to Jeopardy. +It also track scores, +and comes with a JavaScript-based scoreboard to display team rankings. + + +How everything works +--------------------------- + +This section wound up being pretty long. +Please check out [the overview](doc/overview.md) +for details. + + +How to set it up +-------------------- + +It's made to be virtualized, +so you can run multiple contests at once if you want. +If you were to want to run it out of `/opt/koth`, +do the following: + + $ mkdir -p /opt/koth/mycontest + $ ./install /opt/koth/mycontest + $ cp kothd /opt/koth + +Yay, you've got it set up. + + +Installing Puzzle Categories +------------------------------------ + +Puzzle categories are distributed in a different way than the server. +After setting up (see above), just run + + $ /opt/koth/mycontest/bin/install-category /path/to/my/category + + +Running It +------------- + +Get your web server to serve up files from +`/opt/koth/mycontest/www`. + +Then run `/opt/koth/kothd`. + + +Permissions +---------------- + +It's up to you not to be a bonehead about permissions. + +Install sets it so the web user on your system can write to the files it needs to, +but if you're using Apache, +it plays games with user IDs when running CGI. +You're going to have to figure out how to configure your preferred web server. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..4228e72 --- /dev/null +++ b/TODO.md @@ -0,0 +1,9 @@ +* Scoreboard refresh + +Test: + +* awarding points +* points already awarded +* bad team hash +* category doesn't exist +* puzzle doesn't exist diff --git a/bin/mktokens b/bin/mktokens index 2e938ab..3730b46 100755 --- a/bin/mktokens +++ b/bin/mktokens @@ -1,4 +1,5 @@ #!/usr/bin/perl +### GAH PERL NOOOOOOO use strict; use warnings; @@ -54,7 +55,7 @@ sub readch { sub usage { my ($msg) = @_; print <<'EOB'; -Usage: token-hash [options] count +Usage: mktokens [options] count -c category name -s size of token hash [default: 8] EOB diff --git a/doc/LICENSE.md b/doc/LICENSE.md new file mode 100644 index 0000000..5470082 --- /dev/null +++ b/doc/LICENSE.md @@ -0,0 +1,44 @@ +Portions of this code (anything committed by an email address not +ending with `@lanl.gov`, or by Neale and after June 2015) are +Copyright © 2015-2016 Neale Pickett , and come with +the following license (the so-called "MIT License"). + +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation files +> (the "Software"), to deal in the Software without restriction, +> including without limitation the rights to use, copy, modify, merge, +> publish, distribute, sublicense, and/or sell copies of the Software, +> and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: + +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. + +> The software is provided "as is", without warranty of any kind, +> express or implied, including but not limited to the warranties of +> merchantability, fitness for a particular purpose and +> noninfringement. In no event shall the authors or copyright holders +> be liable for any claim, damages or other liability, whether in an +> action of contract, tort or otherwise, arising from, out of or in +> connection with the software or the use or other dealings in the +> software. + +----- + +Portions of this software (everything in git committed before June +2015, or from a @lanl.gov email address) come with the following +notice: + +> This software has been authored by an employee or employees of Los +> Alamos National Security, LLC, operator of the Los Alamos National +> Laboratory (LANL) under Contract No. DE-AC52-06NA25396 with the U.S. +> Department of Energy. The U.S. Government has rights to use, +> reproduce, and distribute this software. The public may copy, +> distribute, prepare derivative works and publicly display this +> software without charge, provided that this Notice and any statement +> of authorship are reproduced on all copies. Neither the Government +> nor LANS makes any warranty, express or implied, or assumes any +> liability or responsibility for the use of this software. If +> software is modified to produce derivative works, such modified +> software should be clearly marked, so as not to confuse it with the +> version available from LANL. diff --git a/doc/ideas.txt b/doc/ideas.txt deleted file mode 100644 index 5c411f0..0000000 --- a/doc/ideas.txt +++ /dev/null @@ -1,34 +0,0 @@ -Ideas for puzzles -================= -* Bootable image with FreeDOS, Linux, Inferno? HURD? - * Bury puzzles in various weird locations within each OS - * Maybe put some in the boot loader, too - * Perhaps have some sort of network puzzle as well -* Network treasure hunt - * DHCP option - * Single TCP RST with token in payload - * Multiple TCP RST with different payloads - * http://10.0.0.2/token -* PXE boot some sort of points-gathering client - * Init asks for a team hash, and starts awarding points - * Broken startup scripts, when fixed award more points - * Lots of remote exploits -* "qemu -net socket" vpn thingy and then... -* sfxrar packed with upx. Change an instruction so it won't actually - execute. -* pwnables: have scp log passwords somewhere - -Capture the Packet ------------------- - -* Jim Meilander could teach a class about Bro -* Use qemu -net socket,connect=10.0.0.2:5399 for capture the packet - - -From Jed Crandell ------------------ - -* Have password easily read, must determine username with stack - examination (like in printf category) -* Use %600000u%n to write an arbitrary value to a location in - stack, then jump to that location somehow. diff --git a/doc/overview.md b/doc/overview.md new file mode 100644 index 0000000..4403eaf --- /dev/null +++ b/doc/overview.md @@ -0,0 +1,175 @@ +Overview of MOTH +================ + +Monarch Of The Hill (MOTH) is a framework for running puzzle-based events for teams. +Each team is assigned a token which they use to identify themselves. + +Teams are presented with a number of *categories*, +each containing a sequence of *puzzles* of increasing point value. + +When the event starts, only the lowest-point puzzle in each category is available. +As soon as any team enters the correct solution to the puzzle, +the next puzzle is opened up for all teams. + +A scoreboard tracks team rankings, +indicating score within each category, +and overall ranking. + + +How Scores are Calculated +------------------------- + +The per-category score for team `t` is computed as: + +* Let `m` be the sum of all points in currently-visible puzzles in this category +* Let `s` be the sum of all points team `t` has won in this category +* Team `t`'s score is `s`/`m` + +Therefore, the maximum number of points a team can have in a category is 1.0. +Put another way, a team's per-category score is the percentage of points they've made so far in that category. + +The total score for team `t` is the sum of all per-category points. + +Therefore, the maximum number of points a team can have in a 7-category event is 7.0. + +This point system has proven both easy to explain (if not immediately obvious), +and acceptable by participants. +Because we don't award extra points for quick responses, +teams always feel like they have the possibility to catch up if they are skilled enough. + + +Requirements +------------- + +MOTH was written to run on a wide range of Linux systems. +We are very careful not to require exotic extensions: +you can run MOTH equally well on OpenWRT and Ubuntu Server. +It might even run on BSD: if you've tried this, please email us! + +Its architecture also limits permissions, +to make it easier to lock things down very tight. +Since it writes to the filesystem slowly and atomically, +it can be run from a USB flash drive formatted with VFAT. + + +On the server, it requires: + +* Bourne shell (POSIX 1003.2: BASH is okay but not required) +* Awk (POSIX 1003.2: gawk is okay but not required) +* Lua 5.1 + + +On the client, it requires: + +* A modern web browser with JavaScript +* Categories might add other requirements (like domain-specific tools to solve the puzzles) + + +Filesystem Layout +================= + +The system is set up to make it simple to run one or more contests on a single machine. + +I like to use `/srv/moth` as the base directory for all instances. +So if I were running an instance called "hack", +the instance directory would be `/srv/moth/hack`. + +There are five entries in each instance directory, described in detail below: + + /srv/moth/hack # (r-x) Instance directory + /srv/moth/hack/assigned.txt # (r--) List of assigned team tokens + /srv/moth/hack/bin/ # (r-x) Per-instance binaries + /srv/moth/hack/categories/ # (r-x) Installed categories + /srv/moth/hack/state/ # (rwx) Contest state + /srv/moth/hack/www/ # (r-x) Web server documentroot + + + +`assigned.txt` +---------------- + +This is just a list of tokens that have been assigned. +One token per line, and tokens can be anything you want. + +For my middle school events, I make tokens all possible 4-digit numbers, +and tell kids to use any number they want: it makes it quicker to start. +For more advanced events, +this doesn't work as well because people start guessing other teams' numbers to confuse each other. +So I use hex representations of random 32-bit ints. +But you could use anything you want in here (for specifics on allowed characters, read the registration CGI). + +The registration CGI checks this list to see if a token has already assigned to a team name. +Teams enter points by token, +which lets them use any text they want for a team name. +Since we don't read their team name anywhere else than the registration and scoreboard generator, +it allows some assumptions about what kind of strings tokens can be, +resulting in simpler code. + + +`categories/` +-------------- + +`categories/` contains read-only category packages. +Within each subdirectory there is: + +* `map.txt` mapping point values to directory names +* `answers.txt` a list of answers for each point value +* `salt` used to generate directory names (so people can't guess them to skip ahead) +* `summary.txt` a compliation of `00summary.txt` files for puzzles, to give you a quick reference point when someone says "I need help on js 40". +* `puzzles` is all the HTML that needs to be served up for the category + + +`bin/` +------ + +Contains all the binaries you'll need to run an event. +These are probably just copies from the `base` package (where this README lives). +They're copied over in case you need to hack on them during an event. + +`bin/once` is of particular interest: +it gets run periodically to do everything, including: + +* Gather points from `points.new` and append them to the points log. +* Generate a new `puzzles.html` listing all open puzzles. +* Generate a new `points.json` for the scoreboard + +### Pausing `once` + +You can pause everything `bin/once` does by touching a file in the root directory +called `disabled`. +This doesn't stop the game: +it just stops points collection and generation of the files listed above. + +This is extremely helpful when, inevitably, +you need to hack the points log, +or do other maintenance tasks. +Most times you don't even need to announce that you're doing anything: +people can keep playing the game and their points keep collecting, +ready to be appended to the log when you're done and you re-enable `once`. + + +`www/` +----------- + +HTML root for an event. +It is possible to make this read-only, +after you've set up your packages. +You will need to symlink a few things into the `state` directory, though. + + +`state/` +--------- + +Where all game state is stored. +This is the only part of the contest directory setup that needs to be writable, +and tarring it up preserves exactly the entire contest. + +Notable, it contains the mapping from team hash to name, +and the points log. + +`points.log` is replayed by the scoreboard generator to calculate the current score for each team. + +New points are written to `points.new`, and picked up by `bin/once` to append to `points.log`. +When `once` is disabled (by touching a file called `disabled` at the top level for a game), +the various points-awarding things can keep writing files into `points.new`, +with no need for locking or "bringing down the game for maintenance". diff --git a/doc/summary.txt b/doc/summary.txt deleted file mode 100644 index bfed796..0000000 --- a/doc/summary.txt +++ /dev/null @@ -1,76 +0,0 @@ -LANL Capture The Flag -===================== - -The LANL CTF training and exercise is designed to train novice to expert -analysts in new techniques and tools. Course material is in a tutorial -format, which is bundled into the exercise. - -The class portion proceeds as a lecture style, although participants are -encouraged to work at their own pace, soliciting assistance from -instructors during the lab sections of the lecture. A Capture-The-Flag -style exercise follows the training as a mechanism to reinforce concepts -the participants have just learned, as well as introduce new concepts, -and to help participants learn how to deal with an actual security -incident. In the exercise portion, participants form into teams which -compete against each other to gain points in a broad spectrum of -categories. - -Event categories and training topics are easily customized to better -meet each site's requirements for training. - - -Key Features ------------- - -Portable: Hardware for up to 80 participants fits into a single -suitcase, and the exercise portion can be conducted by a single -organizer for up to 100 participants. - -Flexible: Exercise or Training can be run standalone, and can last -anywhere from 2 hours to 5 days. - -Lasting: Exercise portion reinforces concepts learned during training. - -Modular: Categories can be cherry-picked from an ever-growing list, -creating a custom-tailored training and exercise. - -Extensible: New modules can be added quickly. - - -Categories currently available: (September 2010) ------------------------------------------------- - -* Base arithmetic -* Introductory computer programming / logical thinking -* Host forensics -* Malware reverse-engineering -* Network reverse-engineering - * Packet capture and analysis tools - * Reconstruction of session data - * Protocol reverse-engineering - * Custom tool development skills -* Linux systems programming - * Using strace, ltrace, gdb - * Understanding race conditions - * Programming securely -* Web application development - * Cross-site scripting attacks - * Input validation - * SQL Injection - * Security vs. obscurity -* Cryptography and codebreaking -* Steganography detection and extraction -* Social engineering -* Binary file formats -* General puzzle-solving skills - - -Categories in development -------------------------- - -* Securing SCADA devices -* Network traffic monitoring -* Log file analysis -* HTML / Javascript reverse-engineering -* Your request goes here! - diff --git a/doc/tokens.txt b/doc/tokens.md similarity index 100% rename from doc/tokens.txt rename to doc/tokens.md diff --git a/doc/writing-puzzles.txt b/doc/writing-puzzles.md similarity index 100% rename from doc/writing-puzzles.txt rename to doc/writing-puzzles.md diff --git a/kothd b/kothd new file mode 100755 index 0000000..ffad247 --- /dev/null +++ b/kothd @@ -0,0 +1,14 @@ +#! /bin/sh + +cd ${1:-$(dirname $0)} +KOTH_BASE=$(pwd) + +echo "Running koth instances in $KOTH_BASE" + +while true; do + for i in $KOTH_BASE/*/assigned.txt; do + dir=${i%/*} + $dir/bin/once + done + sleep 5 +done diff --git a/www/cgi-bin/puzzles.cgi b/www/cgi-bin/puzzles.cgi new file mode 100755 index 0000000..8a3882c --- /dev/null +++ b/www/cgi-bin/puzzles.cgi @@ -0,0 +1,57 @@ +#! /usr/bin/env lua + +package.path = "?.lua;cgi-bin/?.lua;www/cgi-bin/?.lua" + +local koth = require "koth" + +local max_by_cat = {} + +local f = io.popen("ls " .. koth.path("packages")) +for cat in f:lines() do + max_by_cat[cat] = 0 +end +f:close() + + +for line in io.lines(koth.path("state/points.log")) do + local ts, team, cat, points, comment = line:match("^(%d+) (%w+) ([%w-]+) (%d+) ?(.*)") + points = tonumber(points) or 0 + + -- Skip scores for removed categories + if (max_by_cat[cat] ~= nil) then + max_by_cat[cat] = math.max(max_by_cat[cat], points) + end +end + +local body = "
\n" +for cat, biggest in pairs(max_by_cat) do + local points, dirname + + body = body .. "
" .. cat .. "
" + body = body .. "
" + for line in io.lines(koth.path("packages/" .. cat .. "/map.txt")) do + points, dirname = line:match("^(%d+) (.*)") + points = tonumber(points) + + body = body .. "" .. points .. " " + if (points > biggest) then + break + end + end + if (points == biggest) then + body = body .. "" + end + body = body .. "
\n" +end +body = body .. "
\n" +body = body .. "
Sandia Token:" +body = body .. "

Example: sandia:5:xylep-radar-nanox

" +body = body .. "
" +body = body .. "Team Hash:
" +body = body .. "Token: " +body = body .. "" +body = body .. "
" +body = body .. "
" +body = body .. "

Reloading this page periodically may yield updated puzzle lists.

" + +koth.page("Open Puzzles", body) diff --git a/www/index.html b/www/index.html index 962de79..7a083b3 100644 --- a/www/index.html +++ b/www/index.html @@ -1,75 +1,32 @@ - - - - Welcome - - - -

Tracer FIRE

+ + + + + + - - -
-

Getting Started

- -

- Here is what you need to do: -

+ + + + + -
    -
  1. - Register your team. - This only needs to happen once per team, - so if somebody else on your team has already done it, - you don't need to. -
  2. - -
  3. - Get an overview of puzzles, - and start working on something. - The list of open puzzles changes over time, - you need to reload the page to get the current version! -
  4. - -
  5. - Check the scoreboard - in another tab, - to see how your team is doing. -
  6. -
-
- -
-

Reading Material

- -

- Stuck? Taking a break? - Here are some things to read. -

- - -
- -
- Los Alamos National Laboratory - US Department Of Energy - Sandia National Laboratories -
- + MOTH Dashboard + + +
+

Monarch Of The Hill

+

Brought to you by dirtbags.net

+
+
+ If you turn on JavaScript this will look a lot cooler. +
+
+ 🌮 🌮 🌮 🌮 🌮 🌮 🌮 🌮 +
+
+
+ diff --git a/www/res/MicroFLF-Bold.ttf b/www/res/MicroFLF-Bold.ttf new file mode 100644 index 0000000..6702790 Binary files /dev/null and b/www/res/MicroFLF-Bold.ttf differ diff --git a/www/res/MicroFLF-BoldItalic.ttf b/www/res/MicroFLF-BoldItalic.ttf new file mode 100644 index 0000000..0929959 Binary files /dev/null and b/www/res/MicroFLF-BoldItalic.ttf differ diff --git a/www/res/MicroFLF-Italic.ttf b/www/res/MicroFLF-Italic.ttf new file mode 100644 index 0000000..ca219b9 Binary files /dev/null and b/www/res/MicroFLF-Italic.ttf differ diff --git a/www/res/MicroFLF.css b/www/res/MicroFLF.css new file mode 100644 index 0000000..103f9b8 --- /dev/null +++ b/www/res/MicroFLF.css @@ -0,0 +1,14 @@ +@font-face { + font-family: 'MicroFLF'; + src: url('MicroFLF.ttf'); + font-weight: 500; + font-style: normal; + +} + +@font-face { + font-family: 'MicroFLF'; + src: url('MicroFLF-Bold.ttf'); + font-weight: 700; + font-style: normal; +} diff --git a/www/res/MicroFLF.ttf b/www/res/MicroFLF.ttf new file mode 100644 index 0000000..c6e3f92 Binary files /dev/null and b/www/res/MicroFLF.ttf differ diff --git a/www/res/brown-lines.jpg b/www/res/brown-lines.jpg new file mode 100644 index 0000000..63d30ac Binary files /dev/null and b/www/res/brown-lines.jpg differ diff --git a/www/res/luna-moth.png b/www/res/luna-moth.png new file mode 100644 index 0000000..0e34114 Binary files /dev/null and b/www/res/luna-moth.png differ diff --git a/www/res/luna-moth.svg b/www/res/luna-moth.svg new file mode 100644 index 0000000..337ce04 --- /dev/null +++ b/www/res/luna-moth.svg @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + 2012-05-15T03:53:14 + luna moth, moon, night, butterfly + https://openclipart.org/detail/170025/papillon-lune-by-presquesage + + + presquesage + + + + + butterfly + luna moth + lune + moon + night + nuit + papillon + + + + + + + + + + + diff --git a/www/res/main.js b/www/res/main.js new file mode 100644 index 0000000..8299d94 --- /dev/null +++ b/www/res/main.js @@ -0,0 +1,26 @@ +var main_terminal; + +function Main(element) { + var term = new Terminal(element); + + function start() { + term.clear(); + term.par("Main terminal.") + term.par("This is the main terminal. In this terminal you will get your puzzle content and someplace to enter in possible answers. It's probably just going to pull the old URL, steal the body element, and submit it to a new Terminal method for slow-despooling of the content of text nodes.") + term.par("One side-effect of the method I'm considering to slow-despool pre-written HTML is that inline images will load before the text. While not exactly what I had in mind for the feel of the thing, it may still be an interesting effect. I mean, if anything, text should render the *quickest*, so if we're going to turn everything on its head, why not make images pull in quicker than text."); + term.par("Anyway."); + term.par("Hopefully this demo illustrates how things are going to look."); + } + + term.clear(); + term.par("Loading…"); + + setTimeout(start, 2500); +} + + +function main_start() { + main_terminal = new Main(document.getElementById("main")); +} + +window.addEventListener("load", main_start); diff --git a/www/res/maven_pro.css b/www/res/maven_pro.css new file mode 100755 index 0000000..07fa78a --- /dev/null +++ b/www/res/maven_pro.css @@ -0,0 +1,50 @@ +/* +@font-face { + font-family: 'Maven Pro'; + src: url('maven_pro_black-webfont.eot'); + src: url('maven_pro_black-webfont.eot?#iefix') format('eot'), + url('maven_pro_black-webfont.woff') format('woff'), + url('maven_pro_black-webfont.ttf') format('truetype'), + url('maven_pro_black-webfont.svg#webfontXhB2DgBK') format('svg'); + font-weight: 900; + font-style: normal; + +} +*/ + +@font-face { + font-family: 'Maven Pro'; + src: url('maven_pro_medium-webfont.eot'); + src: url('maven_pro_medium-webfont.eot?#iefix') format('eot'), + url('maven_pro_medium-webfont.woff') format('woff'), + url('maven_pro_medium-webfont.ttf') format('truetype'), + url('maven_pro_medium-webfont.svg#webfontNj5iy4Dl') format('svg'); + font-weight: 500; + font-style: normal; + +} + +@font-face { + font-family: 'Maven Pro'; + src: url('maven_pro_bold-webfont.eot'); + src: url('maven_pro_bold-webfont.eot?#iefix') format('eot'), + url('maven_pro_bold-webfont.woff') format('woff'), + url('maven_pro_bold-webfont.ttf') format('truetype'), + url('maven_pro_bold-webfont.svg#webfontNOU7iUTL') format('svg'); + font-weight: 700; + font-style: normal; + +} + +@font-face { + font-family: 'Maven Pro'; + src: url('maven_pro_regular-webfont.eot'); + src: url('maven_pro_regular-webfont.eot?#iefix') format('eot'), + url('maven_pro_regular-webfont.woff') format('woff'), + url('maven_pro_regular-webfont.ttf') format('truetype'), + url('maven_pro_regular-webfont.svg#webfontOM8fITNz') format('svg'); + font-weight: 400; + font-style: normal; + +} + diff --git a/www/res/maven_pro_black-webfont.eot b/www/res/maven_pro_black-webfont.eot new file mode 100755 index 0000000..c7091c1 Binary files /dev/null and b/www/res/maven_pro_black-webfont.eot differ diff --git a/www/res/maven_pro_black-webfont.svg b/www/res/maven_pro_black-webfont.svg new file mode 100755 index 0000000..456f336 --- /dev/null +++ b/www/res/maven_pro_black-webfont.svg @@ -0,0 +1,245 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Copyright c 2011 by Vissol Ltd All rights reserved +Designer : Joe Prince +Foundry : Joe Prince + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/res/maven_pro_black-webfont.ttf b/www/res/maven_pro_black-webfont.ttf new file mode 100755 index 0000000..22cebdf Binary files /dev/null and b/www/res/maven_pro_black-webfont.ttf differ diff --git a/www/res/maven_pro_black-webfont.woff b/www/res/maven_pro_black-webfont.woff new file mode 100755 index 0000000..ae8fba0 Binary files /dev/null and b/www/res/maven_pro_black-webfont.woff differ diff --git a/www/res/maven_pro_bold-webfont.eot b/www/res/maven_pro_bold-webfont.eot new file mode 100755 index 0000000..6f4a488 Binary files /dev/null and b/www/res/maven_pro_bold-webfont.eot differ diff --git a/www/res/maven_pro_bold-webfont.svg b/www/res/maven_pro_bold-webfont.svg new file mode 100755 index 0000000..7f24bdb --- /dev/null +++ b/www/res/maven_pro_bold-webfont.svg @@ -0,0 +1,245 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Copyright c 2011 by Vissol Ltd All rights reserved +Designer : Joe Prince +Foundry : Joe Prince + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/res/maven_pro_bold-webfont.ttf b/www/res/maven_pro_bold-webfont.ttf new file mode 100755 index 0000000..7682e5b Binary files /dev/null and b/www/res/maven_pro_bold-webfont.ttf differ diff --git a/www/res/maven_pro_bold-webfont.woff b/www/res/maven_pro_bold-webfont.woff new file mode 100755 index 0000000..3eea8ae Binary files /dev/null and b/www/res/maven_pro_bold-webfont.woff differ diff --git a/www/res/maven_pro_medium-webfont.eot b/www/res/maven_pro_medium-webfont.eot new file mode 100755 index 0000000..0eb177c Binary files /dev/null and b/www/res/maven_pro_medium-webfont.eot differ diff --git a/www/res/maven_pro_medium-webfont.svg b/www/res/maven_pro_medium-webfont.svg new file mode 100755 index 0000000..532bf53 --- /dev/null +++ b/www/res/maven_pro_medium-webfont.svg @@ -0,0 +1,245 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Copyright c 2011 by Vissol Ltd All rights reserved +Designer : Joe Prince +Foundry : Joe Prince + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/res/maven_pro_medium-webfont.ttf b/www/res/maven_pro_medium-webfont.ttf new file mode 100755 index 0000000..a930700 Binary files /dev/null and b/www/res/maven_pro_medium-webfont.ttf differ diff --git a/www/res/maven_pro_medium-webfont.woff b/www/res/maven_pro_medium-webfont.woff new file mode 100755 index 0000000..a2737c3 Binary files /dev/null and b/www/res/maven_pro_medium-webfont.woff differ diff --git a/www/res/maven_pro_regular-webfont.eot b/www/res/maven_pro_regular-webfont.eot new file mode 100755 index 0000000..a9b38de Binary files /dev/null and b/www/res/maven_pro_regular-webfont.eot differ diff --git a/www/res/maven_pro_regular-webfont.svg b/www/res/maven_pro_regular-webfont.svg new file mode 100755 index 0000000..6f5deb3 --- /dev/null +++ b/www/res/maven_pro_regular-webfont.svg @@ -0,0 +1,245 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Copyright c 2011 by Vissol Ltd All rights reserved +Designer : Joe Prince +Foundry : Joe Prince + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/res/maven_pro_regular-webfont.ttf b/www/res/maven_pro_regular-webfont.ttf new file mode 100755 index 0000000..fae9c6a Binary files /dev/null and b/www/res/maven_pro_regular-webfont.ttf differ diff --git a/www/res/maven_pro_regular-webfont.woff b/www/res/maven_pro_regular-webfont.woff new file mode 100755 index 0000000..3c27f73 Binary files /dev/null and b/www/res/maven_pro_regular-webfont.woff differ diff --git a/www/res/messages.js b/www/res/messages.js new file mode 100644 index 0000000..172599b --- /dev/null +++ b/www/res/messages.js @@ -0,0 +1,22 @@ +var messages_terminal; + +function Messages(element) { + var term = new Terminal(element); + + function start() { + term.clear(); + term.par("Messages terminal"); + term.par("I've long wanted a way to communicate things to participants, like «yes, we're aware that JS 12 is broken, we are working on it», or «tanks category is now open!». This might have updates about people scoring points, or provide a chat service (although that has not historically been well-utilized)."); + } + + term.clear(); + term.par("Loading…"); + setTimeout(start, 500); +} + + +function messages_start() { + messages_terminal = new Messages(document.getElementById("messages")); +} + +window.addEventListener("load", messages_start); diff --git a/www/res/overview.js b/www/res/overview.js new file mode 100644 index 0000000..ce6f817 --- /dev/null +++ b/www/res/overview.js @@ -0,0 +1,19 @@ +var overview_proc; + +function Overview(element) { + var term = new Terminal(element); + + this.start = function() { + term.clear(); + term.par("Overview terminal"); + term.par("Here you will find something resembling a scoreboard, maybe your team name.") + } +} + + +function overview_start() { + overview_proc = new Overview(document.getElementById("overview")); + setTimeout(overview_proc.start, 4000); +} + +window.addEventListener("load", overview_start); diff --git a/www/res/puzzles.js b/www/res/puzzles.js new file mode 100644 index 0000000..e72974c --- /dev/null +++ b/www/res/puzzles.js @@ -0,0 +1,45 @@ +var puzzles_terminal; + +var puzzles_url = "hack/puzzles.html"; + +function Puzzles(element) { + var term = new Terminal(element); + var refreshInterval; + + function loaded() { + var doc = this.response; + var puzzles = doc.getElementById("puzzles"); + var h1 = document.createElement("h1"); + + h1.textContent = "Puzzles"; + + term.clear(); + term.append(h1); + term.appendShallow(puzzles); + } + + function refresh() { + var myRequest = new XMLHttpRequest(); + myRequest.responseType = "document"; + myRequest.addEventListener("load", loaded); + myRequest.open("GET", puzzles_url); + myRequest.send(); + } + + function start() { + refreshInterval = setInterval(refresh, 20 * 1000); + refresh(); + } + + term.clear(); + term.par("Loading…"); + + setTimeout(start, 3000); +} + + +function puzzles_start() { + puzzles_terminal = new Puzzles(document.getElementById("puzzles")); +} + +window.addEventListener("load", puzzles_start); diff --git a/www/res/scoreboard.js b/www/res/scoreboard.js new file mode 100644 index 0000000..76486f0 --- /dev/null +++ b/www/res/scoreboard.js @@ -0,0 +1,116 @@ +function loadJSON(url, callback) { + function loaded(e) { + callback(e.target.response); + } + var xhr = new XMLHttpRequest() + xhr.onload = loaded; + xhr.open("GET", url, true); + xhr.responseType = "json"; + xhr.send(); +} + +function scoreboard(element, continuous) { + function update(state) { + var teamnames = state["teams"]; + var pointslog = state["points"]; + var pointshistory = JSON.parse(localStorage.getItem("pointshistory")) || []; + if (pointshistory.length >= 20){ + pointshistory.shift(); + } + pointshistory.push(pointslog); + localStorage.setItem("pointshistory", JSON.stringify(pointshistory)); + var highscore = {}; + var teams = {}; + + // Dole out points + for (var i in pointslog) { + var entry = pointslog[i]; + var timestamp = entry[0]; + var teamhash = entry[1]; + var category = entry[2]; + var points = entry[3]; + + var team = teams[teamhash] || {__hash__: teamhash}; + + // Add points to team's points for that category + team[category] = (team[category] || 0) + points; + + // Record highest score in a category + highscore[category] = Math.max(highscore[category] || 0, team[category]); + + teams[teamhash] = team; + } + + // Sort by team score + function teamScore(t) { + var score = 0; + + for (var category in highscore) { + score += (t[category] || 0) / highscore[category]; + } + // XXX: This function really shouldn't have side effects. + t.__score__ = score; + return score; + } + function teamCompare(a, b) { + return teamScore(a) - teamScore(b); + } + + var winners = []; + for (var i in teams) { + winners.push(teams[i]); + } + if (winners.length == 0) { + // No teams! + return; + } + winners.sort(teamCompare); + winners.reverse(); + + // Clear out the element we're about to populate + while (element.lastChild) { + element.removeChild(element.lastChild); + } + + // Populate! + var topActualScore = winners[0].__score__; + + // (100 / ncats) * (ncats / topActualScore); + var maxWidth = 100 / topActualScore; + for (var i in winners) { + var team = winners[i]; + var row = document.createElement("div"); + var ncat = 0; + for (var category in highscore) { + var catHigh = highscore[category]; + var catTeam = team[category] || 0; + var catPct = catTeam / catHigh; + var width = maxWidth * catPct; + + var bar = document.createElement("span"); + bar.classList.add("cat" + ncat); + bar.style.width = width + "%"; + bar.textContent = category + ": " + catTeam; + bar.title = bar.textContent; + + row.appendChild(bar); + ncat += 1; + } + + var te = document.createElement("span"); + te.classList.add("teamname"); + te.textContent = teamnames[team.__hash__]; + row.appendChild(te); + + element.appendChild(row); + } + } + + function once() { + loadJSON("points.json", update); + } + if (continuous) { + setInterval(once, 60000); + } + once(); +} diff --git a/www/res/style.css b/www/res/style.css new file mode 100644 index 0000000..ea8ff6c --- /dev/null +++ b/www/res/style.css @@ -0,0 +1,81 @@ +/* @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic); */ +/* @import "maven_pro.css"; */ +@import "lato.css"; + +html { + background: rgba(61, 50, 44, 0) url(brown-lines.jpg) no-repeat center center fixed; + background-size: cover; + color: #ccb; + height: 100%; + font-family: Lato; +} + +body { + margin: 0; + height: 100% +} + +.terminal { + background: rgba(80, 70, 60, 0.96); + display: inline-block; + margin: 1%; + border: solid black 0.2em; + border-radius: 1em 1em 0.5em 1em; + overflow: auto; +} + +.terminal p { + padding: 0.25em 0.5em; +} + +#overview, #messages { + width: 47%; + height: 20%; +} + +#puzzles { + width: 24%; + height: 70%; +} + +#main { + width: 70%; + height: 70%; +} + +h1 { + text-align: center; + font-size: 120%; +} + +a:link { + color: #13a5de; +} + +#puzzles dl { + display: inline; +} +#puzzles dd { + margin: 0; + margin-left: 1em; +} + +@media (max-width: 52em) { + #overview, #messages, #puzzles, #main { + width: 96%; + } +} + +::-webkit-scrollbar { + width: 0.7em; +} + +::-webkit-scrollbar-track { + /* -webkit-box-shadow: inset 0 0 0.5em rgba(200, 200, 200, 0.3); */ + background: rgba(0, 0, 0, 0.2); +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 1em; +} diff --git a/www/res/terminal.js b/www/res/terminal.js new file mode 100644 index 0000000..d742699 --- /dev/null +++ b/www/res/terminal.js @@ -0,0 +1,160 @@ +// A class to turn an element into a cybersteampunk terminal. +// Runs at 1200 baud by default, but unlike an actual modem, +// will despool in parallel. This looks pretty cool. + +// XXX: Hack for chrome not supporting an iterator method on HTMLCollection +HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; +NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; + +function Terminal(target, bps) { + bps = bps || 1200; + + var outq = []; + var outTimer; + + // Heavy lifting happens here. + // At first I had it auto-scrolling to the bottom, like xterm (1987). + // But that was actually kind of annoying, since this is meant to be read. + // So now it leaves the scrollbar in place, and the user has to scroll. + // This is how the Plan 9 terminal (1991) works. + function tx(pairs, bps, scroll) { + var drawTimer; + + // Looks like EMCAScript 6 has a yield statement. + // That would make this mess a lot easier to understand. + + var pairIndex = 0; + var pair = pairs[0]; + + var textIndex = 0; + var text = ""; + + function draw() { + var node = pair[0]; + var src = pair[1]; + var c = src[textIndex]; + + text += c; + node.textContent = text; + + textIndex += 1; + if (textIndex == src.length) { + textIndex = 0; + text = ""; + + pairIndex += 1; + if (pairIndex == pairs.length) { + clearInterval(drawTimer); + return; + } + pair = pairs[pairIndex]; + } + + if (scroll) { + node.scrollIntoView(); + } + } + + // N81 uses 1 stop bit, and 1 parity bit. + // That works out to exactly 10 bits per byte. + msec = 10000 / bps; + + drawTimer = setInterval(draw, msec); + draw(); + } + + + function start() { + if (! outTimer) { + outTimer = setInterval(drawElement, 75); + } + } + + + function stop() { + if (outTimer) { + clearInterval(outTimer); + outTimer = null; + } + } + + + function drawElement() { + var pairs = outq.shift(); + + if (! pairs) { + stop(); + return; + } + + tx(pairs, bps); + } + + + function prepare(element) { + var pairs = []; + + walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); + while (walker.nextNode()) { + var node = walker.currentNode; + var text = node.textContent; + + node.textContent = ""; + pairs.push([node, text]); + } + + return pairs; + } + + + // The main entry point: works like appendChild + this.append = function(element) { + pairs = prepare(element); + target.appendChild(element); + outq.push(pairs); + start(); + } + + + // A cool effect where it despools children in parallel + this.appendShallow = function(element) { + for (var child of element.childNodes) { + pairs = prepare(child); + outq.push(pairs); + } + target.appendChild(element); + start(); + } + + + this.clear = function() { + stop(); + outq = []; + while (target.firstChild) { + target.removeChild(target.firstChild); + } + } + + + this.par = function(txt) { + var e = document.createElement("p"); + e.textContent = txt; + this.append(e); + } + + + this.pre = function(txt) { + var e = document.createElement("pre"); + e.textContent = txt; + this.append(e); + } +} + +// +// Usage: +// +// var e = Terminal(document.getElementById("output")); +// e.output("This is a paragraph. It has sentences."); +// e.output("This is a second paragraph."); +// +