it works now
This commit is contained in:
parent
bbfa2addc9
commit
8be8459d9f
|
@ -28,3 +28,6 @@ MULTIPLE ACTORS:
|
|||
- other actors can generate nevs according to other criteria.
|
||||
- so we could model a TimeLord actor which generates a nev in response to the player's future action? No, because the only way to tell if future actions are valid is to simulate the world!
|
||||
- could provide a world-simulating primitive that action validity calls could run
|
||||
|
||||
GAME IDEAS:
|
||||
"I'm thinking of a number"
|
450
iffy.coffee
Normal file
450
iffy.coffee
Normal file
|
@ -0,0 +1,450 @@
|
|||
###
|
||||
Iffy - parserless interactive fiction for the web
|
||||
(c)2010 Jeremy Penner
|
||||
###
|
||||
|
||||
#lazy enumeration
|
||||
class Eni
|
||||
constructor: (@val, @fStarted) ->
|
||||
Value: () -> @val
|
||||
EniNext: () -> if not @fStarted then new Eni(@val, true)
|
||||
|
||||
EniEmpty = { EniNext: (-> null) }
|
||||
|
||||
class EniArray
|
||||
constructor: (@rg, @i) -> if not @i? then @i = -1
|
||||
Value: -> @rg[@i]
|
||||
EniNext: -> if @i + 1 < @rg.length then new EniArray(@rg, @i + 1)
|
||||
|
||||
class EniConcat
|
||||
constructor: (@eni1, @eni2) ->
|
||||
Value: -> @eni1.Value()
|
||||
EniNext: (rgarg...) ->
|
||||
eni1New = @eni1.EniNext(rgarg...)
|
||||
if eni1New?
|
||||
new EniConcat(eni1New, @eni2)
|
||||
else
|
||||
@eni2.EniNext(rgarg...)
|
||||
|
||||
class EniThunk
|
||||
constructor: (@thunk) ->
|
||||
# it is always illegal to call Value on this eni, as eninext will never return an eniThunk
|
||||
EniNext: (rgarg...) ->
|
||||
# memoize generated eni
|
||||
if not @eni?
|
||||
@eni = @thunk(rgarg...)
|
||||
@eni.EniNext()
|
||||
|
||||
class EniCons
|
||||
constructor: (@value, @thunk, @fStarted) ->
|
||||
Value: -> @value
|
||||
EniNext: (rgarg...) ->
|
||||
if not @fStarted
|
||||
if not @eniNext?
|
||||
@eniNext = new EniCons(@value, @thunk, true)
|
||||
@eniNext
|
||||
else
|
||||
if not @eni?
|
||||
@eni = @thunk(this, rgarg...)
|
||||
@eni.EniNext()
|
||||
|
||||
class EniMap
|
||||
constructor: (@eni, @dg) ->
|
||||
Value: -> @dg(@eni.Value())
|
||||
EniNext: (rgarg...) ->
|
||||
eniNext = @eni.EniNext()
|
||||
if eniNext? then new EniMap(eniNext, @dg, rgarg...)
|
||||
|
||||
class EniFilter
|
||||
constructor: (@eni, @dgFilter) ->
|
||||
Value: -> @eni.Value()
|
||||
EniNext: (rgarg...) ->
|
||||
eni = @eni.EniNext(rgarg...)
|
||||
while eni? and not @dgFilter(eni.Value(), eni, rgarg...)
|
||||
eni = eni.EniNext()
|
||||
eni
|
||||
|
||||
ArrayFromEni = (eni) ->
|
||||
rg = []
|
||||
while (eni = eni.EniNext())?
|
||||
rg.push(eni.Value())
|
||||
rg
|
||||
|
||||
# world state -- holds the state of the world after every
|
||||
NewWst = (obj, gst) ->
|
||||
Ctor = (@gst) ->
|
||||
@WstNext = (nev) ->
|
||||
CtorNext = (wstOld) ->
|
||||
@nev = nev
|
||||
@wstPrev = wstOld
|
||||
this
|
||||
CtorNext.prototype = this
|
||||
new CtorNext(this)
|
||||
@WstPrev = () -> @wstPrev
|
||||
this
|
||||
Ctor.prototype = obj
|
||||
new Ctor(gst)
|
||||
|
||||
class Respo
|
||||
constructor: (@enieniNev, @eniNev) ->
|
||||
if @enieniNev.eni.i < 0 and @eniNev?
|
||||
@eniNev = @eniNev
|
||||
@FromEnieniNev: (enieniNev) -> new Respo(enieniNev)
|
||||
Nev: () -> @eniNev.Value()
|
||||
Next: (nevgen) ->
|
||||
enieniNev = @enieniNev
|
||||
eniNev = @eniNev
|
||||
loop
|
||||
if not eniNev?
|
||||
enieniNev = enieniNev.EniNext()
|
||||
if enieniNev?
|
||||
eniNev = enieniNev.Value()
|
||||
else
|
||||
return null
|
||||
eniNev = eniNev.EniNext(nevgen, new Respo(enieniNev, eniNev))
|
||||
if eniNev? then return new Respo(enieniNev, eniNev)
|
||||
null
|
||||
ReplaceEniNev: (eniNevNew) -> new Respo(@enieniNev, eniNevNew)
|
||||
|
||||
class Nevgen
|
||||
constructor: (@gst, @wst, @eniWstResponding, @respo) ->
|
||||
@EniWst: (gst, wstInit) ->
|
||||
nevgen = new Nevgen(gst, wstInit, null, null)
|
||||
new EniCons(wstInit, nevgen.Thunk())
|
||||
Thunk: () ->
|
||||
(eniPrev) =>
|
||||
nevgen = this
|
||||
if not @eniWstResponding?
|
||||
nevgen = @RespondTo(@gst, @wst, eniPrev)
|
||||
nevgen = nevgen.Next()
|
||||
if nevgen?
|
||||
new EniCons(nevgen.wst, nevgen.Thunk())
|
||||
else
|
||||
EniEmpty
|
||||
Next: () ->
|
||||
nevgen = this
|
||||
while nevgen? and nevgen.wst == @wst
|
||||
nevgen = nevgen.NextI()
|
||||
nevgen
|
||||
NextI: () ->
|
||||
if @respo?
|
||||
respoNext = @respo.Next(this)
|
||||
wst = @wst
|
||||
if respoNext? and respoNext.Nev().FCanRun(@gst, wst)
|
||||
wst = @gst.RunNev(wst, respoNext.Nev())
|
||||
new Nevgen(@gst, wst, @eniWstResponding, respoNext)
|
||||
else if @wst != @eniWstResponding.Value()
|
||||
@RespondTo(@gst, @wst, @eniWstResponding.EniNext())
|
||||
RespondTo: (gst, wst, eniWstResponding) ->
|
||||
new Nevgen(gst, wst, eniWstResponding, gst.story.RespoResponse(eniWstResponding.Value().nev, wst))
|
||||
|
||||
# game state -- holds all the information needed to display the current state of the game in our browser
|
||||
class Gst
|
||||
constructor: (@story, @jDiv) ->
|
||||
@wstInit = @story.WstInit(this)
|
||||
@igenID = 0
|
||||
|
||||
Display: () ->
|
||||
@mpstId_dgOnClick = []
|
||||
@ClearMenu(true)
|
||||
@jDiv.empty()
|
||||
@jDiv.click(() => @ClearMenu())
|
||||
@jDiv.append("<div class='title'>
|
||||
<h1>#{@story.jStory.attr('title')}</h1>
|
||||
<h2>#{@story.jStory.attr('author')}</h2>
|
||||
</div>")
|
||||
for wst in @RgwstRun()
|
||||
@jDiv.append($("<div class='nev'/>").append(wst.nev.StHtmlDisplay(this, wst)))
|
||||
|
||||
for stId_dgOnClick in @mpstId_dgOnClick
|
||||
@jDiv.find("##{stId_dgOnClick[0]}").click(stId_dgOnClick[1])
|
||||
|
||||
ShowMenu: (ev, dLink, rgverb) ->
|
||||
@ClearMenu(true)
|
||||
jMenu = $("<div class='iffy-menu'/>").hide()
|
||||
for verb in rgverb
|
||||
jMenuItem = $("<a href='javascript:void(0)'>#{verb.stDisplay}</a><br/>").click(() => verb.dgActivate(); @Display())
|
||||
jMenu.append(jMenuItem)
|
||||
|
||||
jMenu.css({
|
||||
position: "absolute"
|
||||
top: ev.pageY
|
||||
left: ev.pageX
|
||||
})
|
||||
@jDiv.append(jMenu)
|
||||
|
||||
@jMenu = jMenu
|
||||
@fMenuVisible = false
|
||||
jMenu.show('blind', {}, 300, () => @fMenuVisible = true)
|
||||
|
||||
ClearMenu: (fForce) ->
|
||||
if @jMenu? and (fForce or @fMenuVisible)
|
||||
@jMenu.hide('blind', {}, 300, () -> $(this).remove())
|
||||
@jMenu = null
|
||||
@fMenuVisible = false
|
||||
|
||||
RegOnClick: (dgOnClick) ->
|
||||
stId = "__gen#{@igenID++}"
|
||||
@mpstId_dgOnClick.push([stId, dgOnClick])
|
||||
stId
|
||||
|
||||
RgwstRun: () ->
|
||||
wst = @wstInit.WstNext(@story.NevByName("start"))
|
||||
eniWst = Nevgen.EniWst(this, wst)
|
||||
ArrayFromEni(eniWst)
|
||||
|
||||
RunNev: (wst, nevResponse) ->
|
||||
wst = wst.WstNext(nevResponse)
|
||||
nevResponse.RunAction(this, wst)
|
||||
wst
|
||||
|
||||
FWillNevRun: (wst, eniNevResponseNext, nevgen, respo, nevPretend, nevLater) ->
|
||||
wstNext = @RunNev(wst, nevPretend)
|
||||
|
||||
ThunkWstContinue = (eniWstRespondingOld) ->
|
||||
() ->
|
||||
if eniWstRespondingOld?
|
||||
if eniWstRespondingOld.Value() == wst
|
||||
return new EniCons(wst, ThunkWstContinue(null))
|
||||
else
|
||||
return new EniCons(eniWstRespondingOld.Value(), ThunkWstContinue(eniWstRespondingOld.EniNext()))
|
||||
else
|
||||
return new EniConcat(new Eni(wstNext), eniWstNextPre)
|
||||
eniWstRespondingNew = ThunkWstContinue(nevgen.eniWstResponding)().EniNext()
|
||||
eniWstNextPre = new Nevgen(this, wstNext, eniWstRespondingNew, respo.ReplaceEniNev(eniNevResponseNext)).Thunk()()
|
||||
eniWstNext = eniWstNextPre.EniNext()
|
||||
while eniWstNext?
|
||||
if eniWstNext.Value().nev.ID() == nevLater.ID()
|
||||
return true
|
||||
eniWstNext = eniWstNext.EniNext()
|
||||
return false
|
||||
|
||||
FilterStHtml: (stHtml, wst) ->
|
||||
for actor in @story.rgactor
|
||||
try
|
||||
stHtml = actor.FilterStHtml(stHtml, wst)
|
||||
catch error
|
||||
stHtml = stHtml + "<p class='error'>[[error interpreting markup: #{error}]]</p>"
|
||||
stHtml
|
||||
|
||||
# narrative event -- the story is told as a sequence of these. Contains the HTML to display when the
|
||||
# narrative event happens, the preconditions to make sure the event should be allowed to occur, and
|
||||
# the resultant effect on the state of the model of the world
|
||||
class Nev
|
||||
constructor: (@dNev) ->
|
||||
ID: () ->
|
||||
if not @id?
|
||||
rgstName = $(@dNev).parents("[name]").map(() -> $(this).attr("name")).get()
|
||||
rgstName.reverse()
|
||||
rgstName.push($(@dNev).attr("name"))
|
||||
@id = rgstName.join(".")
|
||||
@id
|
||||
FHasRun: (wst) ->
|
||||
while wst?.nev
|
||||
if wst.nev.ID() == @ID()
|
||||
return true
|
||||
wst = wst.WstPrev()
|
||||
return false
|
||||
FCanRun: (gst, wst) -> not @FHasRun(wst)
|
||||
RunAction: (gst, wst) ->
|
||||
StHtmlDisplay: (gst, wst) ->
|
||||
jdivTmp = $("<div/>")
|
||||
for dHTML in $(@dNev).contents()
|
||||
jdivTmp.append(document.importNode(dHTML, true))
|
||||
stHtml = jdivTmp[0].innerHTML
|
||||
gst.FilterStHtml(stHtml, wst)
|
||||
|
||||
#[word text to display] -- links to a word, usually a noun, that the player can interact with in some way.
|
||||
#[word] == [word word]
|
||||
# $[if (expr)]some text$[else]some other text$[endif] (else is optional)
|
||||
# $[(expr)] inserts as a string the result of expr
|
||||
#[[ outputs [, ]] outputs ]
|
||||
|
||||
TemplateFromStNev = (st, wst) ->
|
||||
class Xpd
|
||||
constructor: (@st, @fExpr) ->
|
||||
|
||||
JsStringLit = (st) ->
|
||||
"'" + st.replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/, "\\t") + "'"
|
||||
|
||||
rgxpd = []
|
||||
ich = 0
|
||||
ichLim = 0
|
||||
ichLimTok = 0
|
||||
|
||||
PushText = (fExpr, dgSt) ->
|
||||
stTok = st.substring(ich, ichLim)
|
||||
if dgSt? then stTok = dgSt(stTok)
|
||||
if stTok then rgxpd.push(new Xpd(stTok, fExpr))
|
||||
ich = ichLimTok
|
||||
ichLim = ichLimTok
|
||||
|
||||
Match = (dgPushNoTok, rgre_dg...) ->
|
||||
ire = 0
|
||||
ireMatched = -1
|
||||
ichMatchFirst = null
|
||||
for ire in [0...rgre_dg.length] by 2
|
||||
re = rgre_dg[ire]
|
||||
re.lastIndex = ichLimTok
|
||||
rgstMatched = re.exec(st)
|
||||
if rgstMatched?
|
||||
ichMatchT = re.lastIndex - rgstMatched[0].length
|
||||
if not ichMatchFirst? or ichMatchT < ichMatchFirst
|
||||
ichMatchFirst = ichMatchT
|
||||
stMatched = rgstMatched[0]
|
||||
ireMatched = ire
|
||||
reMatched = rgre_dg[re]
|
||||
if ireMatched >= 0
|
||||
ichLim = ichMatchFirst
|
||||
ichLimTok = ichMatchFirst + stMatched.length
|
||||
rgre_dg[ireMatched + 1]()
|
||||
else
|
||||
ichLim = ichLimTok = st.length
|
||||
if dgPushNoTok?
|
||||
dgPushNoTok()
|
||||
null
|
||||
|
||||
MatchInText = () ->
|
||||
EndText = () ->
|
||||
PushText(true, (st) -> JsStringLit(st.replace(/\[\[/g, "[").replace(/\]\]/g, "]")))
|
||||
Match(EndText,
|
||||
/\[\[/g, () -> MatchInText,
|
||||
/\]\]/g, () -> MatchInText,
|
||||
/\[/g, () -> EndText(); MatchInWord,
|
||||
/\$\[/g, () -> EndText(); MatchInExpr)
|
||||
MatchBracket = (cbracket, dgPush) ->
|
||||
() -> Match(dgPush, # error?
|
||||
/"/g, () -> MatchString(MatchBracket(cbracket, dgPush)),
|
||||
/\[/g, () -> MatchBracket(cbracket + 1, dgPush),
|
||||
/\]/g, () -> if cbracket == 0 then dgPush(); MatchInText else MatchBracket(cbracket - 1, dgPush))
|
||||
|
||||
MatchString = (matchAfter) ->
|
||||
() ->
|
||||
Match(null,
|
||||
/\\\\/g, () -> MatchString(matchAfter),
|
||||
/\\"/g, () -> MatchString(matchAfter),
|
||||
/"/g, () -> matchAfter)
|
||||
StWord = (st) ->
|
||||
rgstDisplay = /[ ]*([^ ]+)(.*)/.exec(st)
|
||||
stWord = rgstDisplay[1]
|
||||
if rgstDisplay[2]
|
||||
stDisplay = rgstDisplay[2]
|
||||
else
|
||||
stDisplay = rgstDisplay[1].replace(/_/g, " ")
|
||||
|
||||
word = wst.gst.story.WordByName(wst.gst, stWord)
|
||||
if word? and (rgverb = word.Rgverb(wst)).length > 0
|
||||
dgOnClick = (ev) -> wst.gst.ShowMenu(ev, this, rgverb)
|
||||
|
||||
stId = wst.gst.RegOnClick(dgOnClick)
|
||||
JsStringLit("<a href='javascript:void(0)' id='#{stId}' class='iffy-word'>#{stDisplay}</a>")
|
||||
else
|
||||
JsStringLit(stDisplay)
|
||||
MatchInWord = MatchBracket(0, () -> PushText(true, StWord))
|
||||
MatchInExpr = MatchBracket(0, () -> PushText(true))
|
||||
|
||||
BuildRgxpd = (dgMatch) ->
|
||||
while dgMatch?
|
||||
dgMatch = dgMatch()
|
||||
|
||||
BuildRgxpd(MatchInText)
|
||||
|
||||
rgstJs = ["var __p=[];with(obj){"]
|
||||
fExpr = false
|
||||
for xpd in rgxpd
|
||||
if xpd.fExpr != fExpr
|
||||
rgstJs.push(if fExpr then ");" else "__p.push(")
|
||||
else if fExpr
|
||||
rgstJs.push(",")
|
||||
fExpr = xpd.fExpr
|
||||
rgstJs.push(xpd.st)
|
||||
if fExpr then rgstJs.push(");")
|
||||
rgstJs.push("}return __p.join('');")
|
||||
new Function("obj",rgstJs.join(''))
|
||||
|
||||
class Word
|
||||
constructor: (@gst, @jWord) ->
|
||||
Rgverb: (wst) ->
|
||||
rgverb = []
|
||||
for dVerb in @jWord.find("verb")
|
||||
nev = new Nev(dVerb)
|
||||
if not nev.FHasRun(wst) and not @gst.story.actorPlayer.FWillAttempt(wst, nev)
|
||||
stDisplay = $(dVerb).attr("display") or $(dVerb).attr("name")
|
||||
dgActivate = () => @gst.story.actorPlayer.RespondTo(wst.nev, new Nev(dVerb))
|
||||
rgverb.push(new Verb(stDisplay, dgActivate))
|
||||
rgverb
|
||||
|
||||
class Verb
|
||||
constructor: (@stDisplay, @dgActivate) ->
|
||||
|
||||
# actors drive the story by responding to nevs with more nevs
|
||||
# ActorPlayer does its best to perform the actions that the player has told the game to perform.
|
||||
class ActorPlayer
|
||||
constructor: (@story) ->
|
||||
@mpnevID_rgnev = {}
|
||||
RespondTo: (nev, nevResponse) ->
|
||||
rgnev = @mpnevID_rgnev[nev.ID()]
|
||||
if rgnev?
|
||||
rgnev.push(nevResponse)
|
||||
else
|
||||
@mpnevID_rgnev[nev.ID()] = [nevResponse]
|
||||
FWillAttempt: (wst, nev) ->
|
||||
rgnev = @mpnevID_rgnev[wst.nev.ID()]
|
||||
if rgnev?
|
||||
for nevResponse in rgnev
|
||||
if nevResponse.ID() == nev.ID()
|
||||
return true
|
||||
return false
|
||||
EninevResponse: (nev, wst) ->
|
||||
rgnev = @mpnevID_rgnev[nev.ID()]
|
||||
if rgnev? then new EniArray(rgnev) else EniEmpty
|
||||
|
||||
FilterStHtml: (stHtml, wst) ->
|
||||
TemplateFromStNev(stHtml, wst)(wst)
|
||||
|
||||
class Story
|
||||
constructor: (@jStory) ->
|
||||
@actorPlayer = new ActorPlayer(this)
|
||||
@rgactor = [@actorPlayer]
|
||||
NevByName: (stName) ->
|
||||
if stName?
|
||||
rgstName = stName.split('.')
|
||||
for dNev in @jStory.find("[name=#{rgstName[rgstName.length - 1]}]")
|
||||
istName = rgstName.length - 2
|
||||
dParent = dNev
|
||||
while dParent? and istName >= 0
|
||||
dParent = $(dParent).parent("[name=#{rgstName[istName]}]").get(0)
|
||||
istName--
|
||||
if dParent?
|
||||
return new Nev(dNev)
|
||||
null
|
||||
WordByName: (gst, stName) ->
|
||||
jWord = @jStory.find("word[name=#{stName}]")
|
||||
if jWord.length > 0 then new Word(gst, jWord)
|
||||
RespoResponse: (nev, wst) ->
|
||||
Respo.FromEnieniNev(new EniMap(new EniArray(@rgactor), (actor) -> actor.EninevResponse(nev, wst)))
|
||||
WstInit: (gst) ->
|
||||
NewWst({}, gst)
|
||||
|
||||
Rgget = (rgurl, dgAfter, iurl, rgresult) ->
|
||||
if not iurl? then iurl = 0
|
||||
if not rgresult then rgresult = []
|
||||
if iurl == rgurl.length
|
||||
dgAfter(rgresult)
|
||||
else
|
||||
# $.get, called with javascript, results in a "syntax error" in FireFox if the result is not xml. This is OK.
|
||||
$.get(rgurl[iurl], ((result) -> rgresult.push(result); Rgget(rgurl, dgAfter, iurl + 1, rgresult)), "text")
|
||||
|
||||
window.PlayStory = (url, jDiv) ->
|
||||
$.get(url, (domXml) ->
|
||||
story = new Story($("story", domXml))
|
||||
gst = new Gst(story, jDiv)
|
||||
rgurlExt = []
|
||||
for domLoad in $("loadmodule", domXml)
|
||||
rgurlExt.push($(domLoad).attr("url"))
|
||||
Rgget(rgurlExt, (rgscript) ->
|
||||
for script in rgscript
|
||||
eval(script)(gst)
|
||||
gst.Display()
|
||||
)
|
||||
, "xml")
|
35
retrochronal.coffee
Normal file
35
retrochronal.coffee
Normal file
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
RetroChronal -- an iffy module for violating causality
|
||||
|
||||
Usage:
|
||||
<pastresponse after="nev" future_cause="nev" [name="name"]>
|
||||
nev text
|
||||
</pastresponse>
|
||||
|
||||
where the nev specified in "after" is the one that this nev should appear after, and the nev specified in future_cause is the one
|
||||
that causes this nev to happen in the future. If the nev specified in future_cause doesn't appear, this nev will not be displayed.
|
||||
###
|
||||
|
||||
class ActorRetro
|
||||
constructor: (@story) ->
|
||||
@mpnevID_rgnevResponse = {}
|
||||
for dResponse in @story.jStory.find("pastresponse")
|
||||
@add(@story.NevByName($(dResponse).attr("after")), dResponse)
|
||||
add: (nev, dResponse) ->
|
||||
if not @mpnevID_rgnevResponse[nev.ID()]?
|
||||
@mpnevID_rgnevResponse[nev.ID()] = []
|
||||
@mpnevID_rgnevResponse[nev.ID()].push(new Nev($(dResponse)))
|
||||
EninevResponse: (nev, wst) ->
|
||||
rgnevTest = @mpnevID_rgnevResponse[nev.ID()]
|
||||
if rgnevTest?
|
||||
FKeepNevTest = (nevTest, eniNev, nevgen, respo) ->
|
||||
nevFuture = nevgen.wst.gst.story.NevByName($(nevTest.dNev).attr("future_cause"))
|
||||
return nevFuture? and nevgen.wst.gst.FWillNevRun(nevgen.wst, new EniFilter(eniNev, FKeepNevTest), nevgen, respo, nevTest, nevFuture)
|
||||
|
||||
new EniFilter(new EniArray(rgnevTest), FKeepNevTest)
|
||||
else
|
||||
EniEmpty
|
||||
FilterStHtml: (stHtml, wst) -> stHtml
|
||||
|
||||
return (gst) ->
|
||||
gst.story.rgactor.splice(0, 0, new ActorRetro(gst.story))
|
16
test.html
16
test.html
|
@ -1,7 +1,23 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="jqueryui/css/ui-lightness/jquery-ui-1.8.5.custom.css">
|
||||
<style>
|
||||
div.iffy-menu {
|
||||
border: 1px solid #777;
|
||||
position: absolute;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
a.iffy-word:link {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
}
|
||||
a.iffy-word:hover {
|
||||
color: #0000dd;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="jqueryui/js/jquery-ui-1.8.5.custom.min.js"></script>
|
||||
<script type="text/javascript" src="iffy.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<story title="Richard and Larry Build A Time Machine" author="Jeremy Penner">
|
||||
<loadmodule url="retrochronal.js" />
|
||||
<nev name="start">
|
||||
<p>"I still can't believe you actually used a [DeLorean]," says Larry.</p>
|
||||
<p>"Hey, if you're going to do a thing, you ought to do it right," says [Richard].</p>
|
||||
</nev>
|
||||
<word name="DeLorean">
|
||||
<description>
|
||||
Larry looks at the DeLorean with astonishment. Richard has done it up to look <i>exactly</i> like the car from
|
||||
<verb name="Examine">
|
||||
Larry looks at the [DeLorean] with astonishment. [Richard] has done it up to look <i>exactly</i> like the car from
|
||||
<i>Back to the Future</i>.
|
||||
</description>
|
||||
</verb>
|
||||
<verb name="Drive">
|
||||
<p>Larry eyes the car. "Give me the keys," he says. "I want to take this baby to 88."</p>
|
||||
<p>"Oh, it doesn't drive anymore," says Richard. "I had to use the engine to power the time machine."</p>
|
||||
<p>Larry eyes the [DeLorean car]. "Give me the keys," he says. "I want to take this baby to 88."</p>
|
||||
<p>"Oh, it doesn't drive anymore," says [Richard]. "I had to use the engine to power the time machine."</p>
|
||||
</verb>
|
||||
</word>
|
||||
<word name="Richard">
|
||||
|
@ -23,25 +24,23 @@
|
|||
<p>[Richard] waves his hands around. "Spooky action at a distance," he says.</p>
|
||||
<p>"No, I mean, augh. I mean, what can you practically change about the past with this machine?" says Larry.</p>
|
||||
<p>
|
||||
"Well, so far I've gotten my computer to crash five minutes before I push this [button]," says [Richard], gesturing at one of the
|
||||
controls inside the [DeLorean].
|
||||
"Well, so far I've gotten my computer to crash five minutes before I push this [button]," says [Richard],
|
||||
gesturing at one of the controls inside the [DeLorean].
|
||||
</p>
|
||||
</verb>
|
||||
</word>
|
||||
<word name="button">
|
||||
<verb name="Push">
|
||||
<shownev id="button.Push" main="true" /> <!-- after defaults to nevCurrent -->
|
||||
<shownev id="crash" after="start" />
|
||||
<p>Larry reaches for the button and gives it a push.</p>
|
||||
<p>"Nothing happened", says Larry, looking at the [computer].</p>
|
||||
<p>"I <i>told</i> you you were going to push it," says [Richard].</p>
|
||||
<p>Larry opens his mouth to say something, then seems to think better of it.</p>
|
||||
</verb>
|
||||
</word>
|
||||
<nev name="crash">
|
||||
<pastresponse after="start" future_cause="button.Push" name="crash">
|
||||
<p>There is a beep as [Richard Richard's] [computer] spontaneously and inexplicably reboots.</p>
|
||||
<p>"Aw, geez, you're going to push the button, aren't you?", whines [Richard].</p>
|
||||
<p>"What? What button?" asks Larry, puzzled.</p>
|
||||
<p>"Never mind," says [Richard].</p>
|
||||
</nev>
|
||||
</pastresponse>
|
||||
</story>
|
||||
|
|
Loading…
Reference in a new issue