it works now
This commit is contained in:
parent
bbfa2addc9
commit
8be8459d9f
|
@ -1,30 +1,33 @@
|
||||||
TIME TRAVEL:
|
TIME TRAVEL:
|
||||||
actions -- things that the player has explicitly done. They generate nevs, but are not necessarily the only source of them.
|
actions -- things that the player has explicitly done. They generate nevs, but are not necessarily the only source of them.
|
||||||
nev -- narrative event -- a moment in time. nevs contain some text to display to the user to communicate what is happening, some handles with which she may generate actions, and some consequences to the state of the world.
|
nev -- narrative event -- a moment in time. nevs contain some text to display to the user to communicate what is happening, some handles with which she may generate actions, and some consequences to the state of the world.
|
||||||
|
|
||||||
CHANGING THE PAST:
|
CHANGING THE PAST:
|
||||||
in my game, a nev can be inserted in the past as a result of a later action, in direct violation of causality. I do this because it is interesting.
|
in my game, a nev can be inserted in the past as a result of a later action, in direct violation of causality. I do this because it is interesting.
|
||||||
ways we could deal with this:
|
ways we could deal with this:
|
||||||
- try to calculate a stable loop to determine whether or not the action is legal
|
- try to calculate a stable loop to determine whether or not the action is legal
|
||||||
- cons: hard for the author to reason about (?), hard to write code to deal with, high time complexity
|
- cons: hard for the author to reason about (?), hard to write code to deal with, high time complexity
|
||||||
- split the timeline
|
- split the timeline
|
||||||
- cons: the player may be able to perform an action which has consequences in the past which will prevent him from performing the action. The player will then see the consequence but not the action.
|
- cons: the player may be able to perform an action which has consequences in the past which will prevent him from performing the action. The player will then see the consequence but not the action.
|
||||||
- model this problem as inserting an action at the earlier point to begin with
|
- model this problem as inserting an action at the earlier point to begin with
|
||||||
- pros: the system should probably be able to model things this way anyway
|
- pros: the system should probably be able to model things this way anyway
|
||||||
causality is not violated
|
causality is not violated
|
||||||
- cons: maybe somewhat fragile from the author's POV
|
- cons: maybe somewhat fragile from the author's POV
|
||||||
how to specify where the nev goes?
|
how to specify where the nev goes?
|
||||||
UI for undoing should be on the nev following the player's actual action, not the modelled one
|
UI for undoing should be on the nev following the player's actual action, not the modelled one
|
||||||
same problems as timeline splitting, actually -- just giving the author the tool rather than baking it into the engine
|
same problems as timeline splitting, actually -- just giving the author the tool rather than baking it into the engine
|
||||||
these are important questions regardless of whether we implement things this way or not though
|
these are important questions regardless of whether we implement things this way or not though
|
||||||
|
|
||||||
OK
|
OK
|
||||||
ACTIONS:
|
ACTIONS:
|
||||||
- happen in response to nevs!
|
- happen in response to nevs!
|
||||||
- nevs have [generated?] stable IDs, and appear at most once in the text. (nevs can also be parameterized?)
|
- nevs have [generated?] stable IDs, and appear at most once in the text. (nevs can also be parameterized?)
|
||||||
|
|
||||||
MULTIPLE ACTORS:
|
MULTIPLE ACTORS:
|
||||||
- soooo it makes sense to have multiple actors generating actions! the player-character is merely one such actor, with a simple "AI".
|
- soooo it makes sense to have multiple actors generating actions! the player-character is merely one such actor, with a simple "AI".
|
||||||
- other actors can generate nevs according to other criteria.
|
- 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!
|
- 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
|
- 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))
|
46
test.html
46
test.html
|
@ -1,15 +1,31 @@
|
||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" src="jquery.js"></script>
|
<link rel="stylesheet" href="jqueryui/css/ui-lightness/jquery-ui-1.8.5.custom.css">
|
||||||
<script type="text/javascript" src="iffy.js"></script>
|
<style>
|
||||||
<script type="text/javascript">
|
div.iffy-menu {
|
||||||
$(document).ready(function () {
|
border: 1px solid #777;
|
||||||
PlayStory("timemachine.xml", $("#story"));
|
position: absolute;
|
||||||
});
|
background-color: #eeeeee;
|
||||||
</script>
|
}
|
||||||
</head>
|
a.iffy-word:link {
|
||||||
<body>
|
text-decoration: none;
|
||||||
<div id="story" />
|
color: #000000;
|
||||||
</body>
|
}
|
||||||
</html>
|
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 () {
|
||||||
|
PlayStory("timemachine.xml", $("#story"));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="story" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
<story title="Richard and Larry Build A Time Machine" author="Jeremy Penner">
|
<story title="Richard and Larry Build A Time Machine" author="Jeremy Penner">
|
||||||
<nev name="start">
|
<loadmodule url="retrochronal.js" />
|
||||||
<p>"I still can't believe you actually used a [DeLorean]," says Larry.</p>
|
<nev name="start">
|
||||||
<p>"Hey, if you're going to do a thing, you ought to do it right," says [Richard].</p>
|
<p>"I still can't believe you actually used a [DeLorean]," says Larry.</p>
|
||||||
</nev>
|
<p>"Hey, if you're going to do a thing, you ought to do it right," says [Richard].</p>
|
||||||
<word name="DeLorean">
|
</nev>
|
||||||
<description>
|
<word name="DeLorean">
|
||||||
Larry looks at the DeLorean with astonishment. Richard has done it up to look <i>exactly</i> like the car from
|
<verb name="Examine">
|
||||||
<i>Back to the Future</i>.
|
Larry looks at the [DeLorean] with astonishment. [Richard] has done it up to look <i>exactly</i> like the car from
|
||||||
</description>
|
<i>Back to the Future</i>.
|
||||||
<verb name="Drive">
|
</verb>
|
||||||
<p>Larry eyes the car. "Give me the keys," he says. "I want to take this baby to 88."</p>
|
<verb name="Drive">
|
||||||
<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>
|
||||||
</verb>
|
<p>"Oh, it doesn't drive anymore," says [Richard]. "I had to use the engine to power the time machine."</p>
|
||||||
</word>
|
</verb>
|
||||||
<word name="Richard">
|
</word>
|
||||||
<verb name="how" display="What have you done?">
|
<word name="Richard">
|
||||||
<p>"So, run it by me again," says Larry.</p>
|
<verb name="how" display="What have you done?">
|
||||||
<p>"I modified this [DeLorean] to send information backwards through time," says [Richard].</p>
|
<p>"So, run it by me again," says Larry.</p>
|
||||||
<p>"Just information. Not matter."</p>
|
<p>"I modified this [DeLorean] to send information backwards through time," says [Richard].</p>
|
||||||
<p>"Right."</p>
|
<p>"Just information. Not matter."</p>
|
||||||
<p>Larry ponders this for a moment. "How... how does that even work?"</p>
|
<p>"Right."</p>
|
||||||
<p>[Richard] waves his hands around. "Spooky action at a distance," he says.</p>
|
<p>Larry ponders this for a moment. "How... how does that even work?"</p>
|
||||||
<p>"No, I mean, augh. I mean, what can you practically change about the past with this machine?" says Larry.</p>
|
<p>[Richard] waves his hands around. "Spooky action at a distance," he says.</p>
|
||||||
<p>
|
<p>"No, I mean, augh. I mean, what can you practically change about the past with this machine?" says Larry.</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
|
<p>
|
||||||
controls inside the [DeLorean].
|
"Well, so far I've gotten my computer to crash five minutes before I push this [button]," says [Richard],
|
||||||
</p>
|
gesturing at one of the controls inside the [DeLorean].
|
||||||
</verb>
|
</p>
|
||||||
</word>
|
</verb>
|
||||||
<word name="button">
|
</word>
|
||||||
<verb name="Push">
|
<word name="button">
|
||||||
<shownev id="button.Push" main="true" /> <!-- after defaults to nevCurrent -->
|
<verb name="Push">
|
||||||
<shownev id="crash" after="start" />
|
<p>Larry reaches for the button and gives it a push.</p>
|
||||||
<p>Larry reaches for the button and gives it a push.</p>
|
<p>"Nothing happened", says Larry, looking at the [computer].</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>"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>
|
||||||
<p>Larry opens his mouth to say something, then seems to think better of it.</p>
|
</verb>
|
||||||
</verb>
|
</word>
|
||||||
</word>
|
<pastresponse after="start" future_cause="button.Push" name="crash">
|
||||||
<nev name="crash">
|
<p>There is a beep as [Richard Richard's] [computer] spontaneously and inexplicably reboots.</p>
|
||||||
<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>"Aw, geez, you're going to push the button, aren't you?", whines [Richard].</p>
|
<p>"What? What button?" asks Larry, puzzled.</p>
|
||||||
<p>"What? What button?" asks Larry, puzzled.</p>
|
<p>"Never mind," says [Richard].</p>
|
||||||
<p>"Never mind," says [Richard].</p>
|
</pastresponse>
|
||||||
</nev>
|
</story>
|
||||||
</story>
|
|
||||||
|
|
Loading…
Reference in a new issue