2010-12-02 01:04:15 +00:00
|
|
|
###
|
2011-03-04 06:47:56 +00:00
|
|
|
Kliffy - Klikable Interactive Fiction For You!
|
|
|
|
(c)2010-2011 Jeremy Penner
|
2010-12-02 01:04:15 +00:00
|
|
|
###
|
|
|
|
|
|
|
|
#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: () ->
|
2011-02-15 16:42:27 +00:00
|
|
|
if @wst.nev.FEndsSection()
|
|
|
|
return null
|
2010-12-02 01:04:15 +00:00
|
|
|
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) ->
|
2011-02-15 01:26:48 +00:00
|
|
|
@rgwstInit = [@story.WstInit(this)]
|
|
|
|
@rgsection = [@story.SectionByName("start")]
|
2011-02-11 01:32:49 +00:00
|
|
|
@rgwst = null
|
2010-12-02 01:04:15 +00:00
|
|
|
@igenID = 0
|
|
|
|
|
2011-02-15 01:26:48 +00:00
|
|
|
GoBackOneSection: () ->
|
|
|
|
isectionRemove = @rgsection.length - 1
|
|
|
|
@rgsection.splice(isectionRemove, 1)
|
|
|
|
@rgwstInit.splice(isectionRemove, 1)
|
2011-02-15 16:42:27 +00:00
|
|
|
@Display()
|
|
|
|
|
|
|
|
PushSection: (section, wst) ->
|
|
|
|
@rgsection.push(section)
|
|
|
|
@rgwstInit.push(wst)
|
|
|
|
@Display()
|
|
|
|
|
2011-02-15 01:26:48 +00:00
|
|
|
SectionCurrent: () ->
|
|
|
|
@rgsection[@rgsection.length - 1]
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
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>")
|
2011-02-10 21:34:32 +00:00
|
|
|
dgEnter = () -> $(this).addClass('hover')
|
|
|
|
dgLeave = () -> $(this).removeClass('hover')
|
2011-02-11 01:32:49 +00:00
|
|
|
|
2011-02-15 16:42:27 +00:00
|
|
|
# show "previous section" link
|
|
|
|
if @rgsection.length > 1
|
2011-03-04 06:47:56 +00:00
|
|
|
@jDiv.append("<div class='kliffy-section-nav'>#{@Link("Previous section", () => @GoBackOneSection())}</div>")
|
2011-02-15 16:42:27 +00:00
|
|
|
|
|
|
|
# show all nevs in section
|
2011-02-11 01:32:49 +00:00
|
|
|
@rgwst = null
|
|
|
|
@rgwst = @RgwstRun()
|
|
|
|
for wst in @rgwst
|
2011-03-04 06:47:56 +00:00
|
|
|
jDivNev = $("<div class='kliffy-nev'/>")
|
2011-02-10 21:34:32 +00:00
|
|
|
jDivNev.append(wst.nev.StHtmlDisplay(this, wst))
|
|
|
|
@jDiv.append(jDivNev)
|
|
|
|
jDivNev.hover(dgEnter, dgLeave)
|
2011-02-11 01:32:49 +00:00
|
|
|
@wstLast = wst
|
2010-12-02 01:04:15 +00:00
|
|
|
|
2011-02-15 16:42:27 +00:00
|
|
|
# show "next section" link
|
|
|
|
stHtmlNav = @wstLast.nev.StHtmlNextSection(this, @wstLast)
|
|
|
|
if stHtmlNav?
|
2011-03-04 06:47:56 +00:00
|
|
|
@jDiv.append("<div class='kliffy-section-nav'>#{stHtmlNav}</div>")
|
2011-02-15 16:42:27 +00:00
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
for stId_dgOnClick in @mpstId_dgOnClick
|
|
|
|
@jDiv.find("##{stId_dgOnClick[0]}").click(stId_dgOnClick[1])
|
|
|
|
|
2011-02-11 01:32:49 +00:00
|
|
|
# only valid to call after the story has been run
|
|
|
|
FWasRun: (nev) ->
|
|
|
|
for wst in @rgwst
|
|
|
|
if wst.nev.ID() == nev.ID()
|
|
|
|
return true
|
|
|
|
return false
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
ShowMenu: (ev, dLink, rgverb) ->
|
|
|
|
@ClearMenu(true)
|
2011-03-04 06:47:56 +00:00
|
|
|
jMenu = $("<div class='kliffy-menu'/>").hide()
|
2010-12-02 01:04:15 +00:00
|
|
|
for verb in rgverb
|
2011-02-10 01:25:45 +00:00
|
|
|
dgClick = ((verbT) => () => verbT.dgActivate(); @Display())(verb)
|
|
|
|
jMenuItem = $("<a href='javascript:void(0)'>#{verb.stDisplay}</a><br/>").click(dgClick)
|
2010-12-02 01:04:15 +00:00
|
|
|
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
|
2011-02-15 16:42:01 +00:00
|
|
|
|
|
|
|
Link: (stText, dgOnClick, stExtra) ->
|
|
|
|
if not stExtra?
|
|
|
|
stExtra = ""
|
|
|
|
"<a href='javascript:void(0)' id='#{@RegOnClick(dgOnClick)}' #{stExtra}>#{stText}</a>"
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
RgwstRun: () ->
|
2011-03-03 15:40:23 +00:00
|
|
|
wst = @RunNev(@rgwstInit[@rgwstInit.length - 1], @SectionCurrent().NevByName("start"))
|
2010-12-02 01:04:15 +00:00
|
|
|
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
|
2011-02-10 21:34:32 +00:00
|
|
|
StHtmlUi: (wst) ->
|
|
|
|
stHtmlUi = ""
|
|
|
|
for actor in @story.rgactor
|
|
|
|
if actor.StHtmlUi?
|
|
|
|
stHtmlUiT = actor.StHtmlUi(wst)
|
|
|
|
if stHtmlUiT?
|
|
|
|
stHtmlUi += stHtmlUiT
|
|
|
|
stHtmlUi
|
|
|
|
|
2011-02-10 01:25:45 +00:00
|
|
|
bind = (fn, me) -> ((args...) -> fn.apply(me, args))# argh new coffeescript won't let me use __bind :(
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
# 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
|
2011-03-03 15:41:55 +00:00
|
|
|
@RgstelemReserved = ["cond", "set", "donext"]
|
2010-12-02 01:04:15 +00:00
|
|
|
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
|
2011-03-03 15:41:55 +00:00
|
|
|
FConds: (wst) ->
|
|
|
|
for dCond in $("cond", @dNev)
|
|
|
|
try
|
|
|
|
if (!(new Function("wst", "with(wst) {return #{dCond.textContent};}")(wst)))
|
|
|
|
return false
|
|
|
|
catch error
|
|
|
|
return false
|
|
|
|
true
|
|
|
|
FCanRun: (gst, wst) -> not @FHasRun(wst) and @FConds(wst)
|
2011-02-15 16:42:27 +00:00
|
|
|
FEndsSection: () -> $(@dNev).attr("nextsection")?
|
2010-12-02 01:04:15 +00:00
|
|
|
RunAction: (gst, wst) ->
|
2011-03-03 15:41:55 +00:00
|
|
|
for dSet in $("set", @dNev)
|
|
|
|
try
|
|
|
|
new Function("wst", "wst.#{$(dSet).attr('var')} = #{dSet.textContent};")(wst)
|
|
|
|
catch e
|
|
|
|
alert("error setting #{$(dSet).attr('var')} to #{dSet.textContent}")
|
|
|
|
|
2011-02-15 16:42:27 +00:00
|
|
|
StHtmlNextSection: (gst, wst) ->
|
|
|
|
stSectionNext = $(@dNev).attr("nextsection")
|
|
|
|
if stSectionNext?
|
2011-02-15 16:46:02 +00:00
|
|
|
section = gst.story.SectionByName(stSectionNext)
|
|
|
|
if section?
|
|
|
|
gst.Link("Next section", () -> gst.PushSection(section, wst))
|
2010-12-02 01:04:15 +00:00
|
|
|
StHtmlDisplay: (gst, wst) ->
|
|
|
|
jdivTmp = $("<div/>")
|
|
|
|
for dHTML in $(@dNev).contents()
|
2011-03-03 15:41:55 +00:00
|
|
|
fReserved = false
|
|
|
|
for stElemReserved in Nev.RgstelemReserved
|
|
|
|
if ($.nodeName(dHTML, stElemReserved))
|
|
|
|
fReserved = true
|
|
|
|
break
|
|
|
|
if not fReserved
|
|
|
|
jdivTmp.append(document.importNode(dHTML, true))
|
2010-12-02 01:04:15 +00:00
|
|
|
stHtml = jdivTmp[0].innerHTML
|
2011-03-04 06:47:56 +00:00
|
|
|
"<div class='kliffy-nev-ui'>#{gst.StHtmlUi(wst)}</div><div class='kliffy-nev-text'>#{gst.FilterStHtml(stHtml, wst)}</div>"
|
2010-12-02 01:04:15 +00:00
|
|
|
|
|
|
|
#[word text to display] -- links to a word, usually a noun, that the player can interact with in some way.
|
|
|
|
#[word] == [word word]
|
2011-02-16 17:41:24 +00:00
|
|
|
# $(expr) inserts as a string the result of expr
|
|
|
|
# $[statements] executes some javascript (semicolons needed)
|
|
|
|
# [[ outputs [, ]] outputs ]
|
2010-12-02 01:04:15 +00:00
|
|
|
|
|
|
|
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,
|
2011-02-16 17:41:24 +00:00
|
|
|
/\$\[/g, () -> EndText(); MatchInStmts,
|
|
|
|
/\$\(/g, () -> EndText(); MatchInExpr)
|
|
|
|
|
|
|
|
EndMatchBracket = (cbracketSquareNew, cbracketParenNew, dgPush) ->
|
|
|
|
if cbracketSquareNew <= 0 && cbracketParenNew <= 0
|
|
|
|
dgPush()
|
|
|
|
MatchInText
|
|
|
|
else
|
|
|
|
MatchBracket(cbracketSquareNew, cbracketParenNew, dgPush)
|
|
|
|
MatchBracket = (cbracketSquare, cbracketParen, dgPush) ->
|
2010-12-02 01:04:15 +00:00
|
|
|
() -> Match(dgPush, # error?
|
2011-02-16 17:41:24 +00:00
|
|
|
/"/g, () -> MatchString(MatchBracket(cbracketSquare, cbracketParen, dgPush)),
|
|
|
|
/\[/g, () -> MatchBracket(cbracketSquare + 1, cbracketParen, dgPush),
|
|
|
|
/\]/g, () -> EndMatchBracket(cbracketSquare - 1, cbracketParen, dgPush),
|
|
|
|
/\(/g, () -> MatchBracket(cbracketSquare, cbracketParen + 1, dgPush),
|
|
|
|
/\)/g, () -> EndMatchBracket(cbracketSquare, cbracketParen - 1, dgPush))
|
2010-12-02 01:04:15 +00:00
|
|
|
|
|
|
|
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, " ")
|
|
|
|
|
2011-02-15 01:26:48 +00:00
|
|
|
word = wst.gst.SectionCurrent().WordByName(wst.gst, stWord)
|
2010-12-02 01:04:15 +00:00
|
|
|
if word? and (rgverb = word.Rgverb(wst)).length > 0
|
2011-03-04 06:47:56 +00:00
|
|
|
JsStringLit(wst.gst.Link(stDisplay, ((ev) -> wst.gst.ShowMenu(ev, this, rgverb)), "class='kliffy-word'"))
|
2010-12-02 01:04:15 +00:00
|
|
|
else
|
|
|
|
JsStringLit(stDisplay)
|
2011-02-16 17:41:24 +00:00
|
|
|
MatchInWord = MatchBracket(1, 0, () -> PushText(true, StWord))
|
|
|
|
MatchInExpr = MatchBracket(0, 1, () -> PushText(true))
|
|
|
|
MatchInStmts = MatchBracket(1, 0, () -> PushText(false))
|
2010-12-02 01:04:15 +00:00
|
|
|
|
|
|
|
BuildRgxpd = (dgMatch) ->
|
|
|
|
while dgMatch?
|
|
|
|
dgMatch = dgMatch()
|
|
|
|
|
|
|
|
BuildRgxpd(MatchInText)
|
|
|
|
|
2011-02-16 17:41:24 +00:00
|
|
|
rgstJs = ["var __p=[];with(wst){"]
|
2010-12-02 01:04:15 +00:00
|
|
|
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('');")
|
2011-02-16 17:41:24 +00:00
|
|
|
new Function("wst",rgstJs.join(''))
|
2010-12-02 01:04:15 +00:00
|
|
|
|
|
|
|
class Word
|
|
|
|
constructor: (@gst, @jWord) ->
|
|
|
|
Rgverb: (wst) ->
|
|
|
|
rgverb = []
|
|
|
|
for dVerb in @jWord.find("verb")
|
|
|
|
nev = new Nev(dVerb)
|
2011-03-03 15:41:55 +00:00
|
|
|
if not @gst.FWasRun(nev) and not @gst.story.actorPlayer.FWillAttempt(wst, nev) and nev.FConds(wst)
|
2010-12-02 01:04:15 +00:00
|
|
|
stDisplay = $(dVerb).attr("display") or $(dVerb).attr("name")
|
2011-02-10 01:25:45 +00:00
|
|
|
dgActivate = ((dVerbT) => () => @gst.story.actorPlayer.RespondTo(wst.nev, new Nev(dVerbT)))(dVerb)
|
2010-12-02 01:04:15 +00:00
|
|
|
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) ->
|
2011-02-10 05:48:21 +00:00
|
|
|
nevResponse.player_nevIDRespondedTo = nev.ID();
|
2010-12-02 01:04:15 +00:00
|
|
|
rgnev = @mpnevID_rgnev[nev.ID()]
|
|
|
|
if rgnev?
|
2011-02-11 01:32:21 +00:00
|
|
|
rgnev.splice(0, 0, nevResponse)
|
2010-12-02 01:04:15 +00:00
|
|
|
else
|
|
|
|
@mpnevID_rgnev[nev.ID()] = [nevResponse]
|
2011-02-10 05:48:21 +00:00
|
|
|
RemoveResponse: (nevResponse) ->
|
|
|
|
rgnev = @mpnevID_rgnev[nevResponse.player_nevIDRespondedTo]
|
|
|
|
for nev, inev in rgnev
|
|
|
|
if nev.ID() == nevResponse.ID()
|
|
|
|
rgnev.splice(inev, 1)
|
|
|
|
break
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
FWillAttempt: (wst, nev) ->
|
|
|
|
rgnev = @mpnevID_rgnev[wst.nev.ID()]
|
|
|
|
if rgnev?
|
|
|
|
for nevResponse in rgnev
|
|
|
|
if nevResponse.ID() == nev.ID()
|
|
|
|
return true
|
|
|
|
return false
|
2011-02-11 01:32:49 +00:00
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
EninevResponse: (nev, wst) ->
|
|
|
|
rgnev = @mpnevID_rgnev[nev.ID()]
|
|
|
|
if rgnev? then new EniArray(rgnev) else EniEmpty
|
|
|
|
|
|
|
|
FilterStHtml: (stHtml, wst) ->
|
2011-02-10 21:34:32 +00:00
|
|
|
TemplateFromStNev(stHtml, wst)(wst)
|
|
|
|
|
|
|
|
StHtmlUi: (wst) ->
|
2011-02-10 05:48:21 +00:00
|
|
|
if (wst.nev.player_nevIDRespondedTo?)
|
2011-03-04 06:47:56 +00:00
|
|
|
wst.gst.Link("Undo", (() => @RemoveResponse(wst.nev); wst.gst.Display()), "class='kliffy-undo'")
|
2011-02-10 05:48:21 +00:00
|
|
|
|
2011-03-03 15:42:54 +00:00
|
|
|
class ActorNext
|
|
|
|
constructor: (@story) ->
|
|
|
|
@mpnevID_rgnev = {}
|
|
|
|
for section in @story.Rgsection()
|
|
|
|
for dDoNext in section.jSection.find("donext")
|
|
|
|
@add(new Nev($(dDoNext.parentNode)), section.NevByName($(dDoNext).attr("nev")))
|
|
|
|
add: (nev, nevResponse) ->
|
|
|
|
if not @mpnevID_rgnev[nev.ID()]?
|
|
|
|
@mpnevID_rgnev[nev.ID()] = []
|
|
|
|
@mpnevID_rgnev[nev.ID()].push(nevResponse)
|
|
|
|
EninevResponse: (nev, wst) ->
|
|
|
|
rgnev = @mpnevID_rgnev[nev.ID()]
|
|
|
|
if rgnev? then new EniArray(rgnev) else EniEmpty
|
|
|
|
|
|
|
|
FilterStHtml: (stHtml, wst) -> stHtml
|
2010-12-02 01:04:15 +00:00
|
|
|
class Story
|
|
|
|
constructor: (@jStory) ->
|
|
|
|
@actorPlayer = new ActorPlayer(this)
|
2011-03-03 15:42:54 +00:00
|
|
|
@rgactor = [@actorPlayer, new ActorNext(this)]
|
2011-02-15 01:26:48 +00:00
|
|
|
SectionByName: (stName) ->
|
|
|
|
jSection = @jStory.find("section[name=#{stName}]")
|
|
|
|
if jSection.length == 1 then new Section(this, jSection)
|
|
|
|
Rgsection: () ->
|
|
|
|
new Section(this, $(jSection)) for jSection in @jStory.find("section").toArray()
|
|
|
|
RespoResponse: (nev, wst) ->
|
|
|
|
Respo.FromEnieniNev(new EniMap(new EniArray(@rgactor), (actor) -> actor.EninevResponse(nev, wst)))
|
|
|
|
WstInit: (gst) ->
|
|
|
|
NewWst({}, gst)
|
|
|
|
|
|
|
|
class Section
|
|
|
|
constructor: (@story, @jSection) ->
|
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
NevByName: (stName) ->
|
|
|
|
if stName?
|
|
|
|
rgstName = stName.split('.')
|
2011-02-15 01:26:48 +00:00
|
|
|
for dNev in @jSection.find("[name=#{rgstName[rgstName.length - 1]}]")
|
2010-12-02 01:04:15 +00:00
|
|
|
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) ->
|
2011-02-15 01:26:48 +00:00
|
|
|
jWord = @jSection.find("word[name=#{stName}]")
|
2010-12-02 01:04:15 +00:00
|
|
|
if jWord.length > 0 then new Word(gst, jWord)
|
2011-02-15 01:26:48 +00:00
|
|
|
|
2010-12-02 01:04:15 +00:00
|
|
|
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")
|