UNPKG

imbroglio

Version:

a tool for making browser-based interactive fiction

146 lines (138 loc) 4.56 kB
choiceChars = 'abcdefghijklmnopqrstuvwxyz0123456789' error = (msg) -> throw new Error msg assert = require 'assert' $ = require 'jquery' {quote, stdlib, prepare} = require './compiler' mkText = (s) -> window.document.createTextNode s mkElem = (tag, children = [], attrs = {}) -> result = window.document.createElement tag result.setAttribute k, v for k, v of attrs result.appendChild child for child in children when child result exports.compile = compile = (src) -> passages = {} firstPassage = null do -> re = /(?:^\s*\n?|\n\n)#\s*([^\n]*\S)\s*\n/g lastPassage = null while m = re.exec src if lastPassage then lastPassage.endIndex = m.index lastPassage = name: m[1], startIndex: re.lastIndex assert lastPassage.name not of passages, lastPassage.name passages[lastPassage.name] = lastPassage firstPassage or= lastPassage if not lastPassage then error 'no passages found' lastPassage.endIndex = src.length return do -> for k, v of passages then do -> v.src = src.substring v.startIndex, v.endIndex v.mungedSrc = v.src.replace /\[\[([^\]]*)\]\]/g, (outer, inner, index) -> offset = index + v.startIndex text = target = inner if m = /^(.*)->\s*([^<>]*[^\s<>])\s*$/.exec inner text = m[1] target = m[2] else if m = /^\s*([^<>]*[^\s<>])\s*<-(.*)$/.exec inner target = m[1] text = m[2] if target not of passages error "bad link target '#{target}' at #{outer}, passage #{k}, offset #{offset}" "#\{imbroglio.mkLink #{quote target}, #{quote text}}" v.prepared = prepare v.mungedSrc, { argNames: ['imbroglio'] thisVar: 'imbroglio.state' handleError: (e) -> console.log e if e.error instanceof Error then throw e.error else if e.error then error e.error else error e } return return render = (passage, result = {}) -> moves = result.moves or= '' links = {} linkCount = 0 mkLink = (target, text) -> if linkCount >= choiceChars.length error "too many links, passage #{passage.name}, target [#{target}], text [#{text}]" choiceChar = choiceChars[linkCount++] el = mkElem 'a', [mkText text], class: 'choice' href: "#!#{moves}#{choiceChar}" links[choiceChar] = {el, target} return el state = JSON.parse result.stateJSON or '{}' result.passageElem = passage.prepared stdlib {mkLink, state} stateJSON = result.stateJSON = JSON.stringify state result.choose = (ch) -> if not link = links[ch] console.log "invalid move #{ch} from passage #{passage.name}" return null return render passages[link.target], { moves: "#{moves}#{ch}" chosenElem: link.el stateJSON } return result return -> render firstPassage newGame = turn = null restore = (moves) -> if turn?.moves is moves then return $('#loading').show() $('.pane').hide() $('#game').hide() $output = $('#output') if not turn turn = newGame() $output.empty() $output.append turn.passageElem else last = -> children = $output.children() if children.length then $ children.get children.length-1 else children while turn.moves != moves[...turn.moves.length] last().remove() turn = turn.prevTurn last().find('.chosen').removeClass 'chosen' for ch in moves[turn.moves.length...] prevTurn = turn if not turn = turn.choose ch $('#404-pane').show() $('#loading').hide() return turn.prevTurn = prevTurn $(turn.chosenElem).addClass 'chosen' $output.append turn.passageElem $('#game').show() $('#loading').hide() $p = $ turn.passageElem window.scrollTo 0, $p.offset().top return hashchange = -> hash = window.location.hash.replace /^#/, '' if m = /^!(.*)$/.exec hash then return restore m[1] turn = target = null if m = /^\/([a-z][a-z-]*)$/.exec hash then target = $ "##{m[1]}-pane" if not target?.length then target = $('#home') $('#game').hide() $('#output').empty() $('.pane').hide() target.show() $('#loading').hide() window.scrollTo 0, 0 return exports.start = (src) -> newGame = compile src $ -> do -> m = /([^\/]+)$/.exec window.location?.pathname or '' if m then $('a[href="#"], a[href="#/"]').attr 'href', m[1] return $(window).on 'hashchange', (e) -> e.preventDefault() hashchange() true hashchange() return