1
0
Fork 0
mirror of https://gitlab.com/Oreolek/salet.git synced 2024-07-07 01:04:25 +03:00

Salet is now passed as an argument.

Also, no frequency filters on choice list.
This commit is contained in:
Alexander Yakovlev 2016-02-01 14:22:23 +07:00
parent 566c8b27a2
commit 5e7f4087aa
8 changed files with 176 additions and 217 deletions

View file

@ -3,7 +3,15 @@ room = require("../../lib/room.coffee")
obj = require('../../lib/obj.coffee') obj = require('../../lib/obj.coffee')
dialogue = require('../../lib/dialogue.coffee') dialogue = require('../../lib/dialogue.coffee')
oneOf = require('../../lib/oneOf.coffee') oneOf = require('../../lib/oneOf.coffee')
require('../../lib/salet.coffee') Salet = require('../../lib/salet.coffee')
salet = new Salet
salet.view.init(salet)
salet.game_id = "your-game-id-here"
salet.game_version = "1.0"
$(document).ready(() ->
salet.beginGame()
)
### ###
Element helpers. There is no real need to build monsters like a().id("hello") Element helpers. There is no real need to build monsters like a().id("hello")
@ -25,7 +33,7 @@ cyclelink = (content) ->
# The first room of the game. # The first room of the game.
# For accessibility reasons the text is provided in HTML, not here. # For accessibility reasons the text is provided in HTML, not here.
room "start", room "start", salet,
dsc: """ dsc: """
""", """,
choices: "#start" choices: "#start"

View file

@ -1,7 +0,0 @@
# This is where you initialize your game.
$(document).ready(() ->
salet.game_id = "your-game-id-here"
salet.game_version = "1.0"
salet.beginGame()
)

View file

@ -1,4 +1,4 @@
room "world", room "world", salet,
tags: ["start"], tags: ["start"],
optionText: "Enter the world", optionText: "Enter the world",
ways: ["plaza"] ways: ["plaza"]
@ -13,7 +13,7 @@ room "world",
dsc: "A steep narrow {{well}} proceeds upward." dsc: "A steep narrow {{well}} proceeds upward."
act: "There is only one passage out. See the „Other rooms“ block popped up? Click it." act: "There is only one passage out. See the „Other rooms“ block popped up? Click it."
room "plaza", room "plaza", salet,
title: (from) -> title: (from) ->
if from == "world" if from == "world"
return "Upwards" return "Upwards"
@ -44,7 +44,7 @@ room "plaza",
dsc: "There are {{people shouting}} nearby." dsc: "There are {{people shouting}} nearby."
act: 'Just some weirdos shouting "Viva la Cthulhu!". Typical.' act: 'Just some weirdos shouting "Viva la Cthulhu!". Typical.'
room "shop", room "shop", salet,
title: "The Shop" title: "The Shop"
#pic: "http://loremflickr.com/640/300/room,shop" #pic: "http://loremflickr.com/640/300/room,shop"
ways: ["plaza", "shop-inside", "lair"] ways: ["plaza", "shop-inside", "lair"]
@ -55,7 +55,7 @@ room "shop",
You are standing in front of a picturesque sign. It's cold here. You are standing in front of a picturesque sign. It's cold here.
""" """
room "lair", room "lair", salet,
title: "The Lair" title: "The Lair"
before: "Finding The Lair is easy. Leaving it is impossible. Your game ends here." before: "Finding The Lair is easy. Leaving it is impossible. Your game ends here."
dsc: """ dsc: """
@ -69,11 +69,11 @@ room "lair",
here().drop(@name) here().drop(@name)
return "You eat the bugg mass. Delicious and raw. Perhaps it's a good lair to live in." return "You eat the bugg mass. Delicious and raw. Perhaps it's a good lair to live in."
dialogue "Yes", "merchant", "merchant", """ dialogue "Yes", salet, "merchant", "merchant", """
Yes. Yes.
""" """
room "shop-inside", room "shop-inside", salet,
ways: ["shop"] ways: ["shop"]
tags: ["merchant"] tags: ["merchant"]
optionText: "End the conversation" optionText: "End the conversation"
@ -100,7 +100,7 @@ lamp = obj "lamp",
lamp.put("shop-inside") lamp.put("shop-inside")
### ###
room "merchdialogue", room "merchdialogue", salet,
choices: "#merchant", choices: "#merchant",
dsc: """ dsc: """
Nice day, isn't it? Nice day, isn't it?

View file

@ -15,8 +15,8 @@ Usage:
Point out a thing in her purse (mildly) Point out a thing in her purse (mildly)
""", "character.sandbox.mild = true" """, "character.sandbox.mild = true"
### ###
dialogue = (title, startTag, endTag, text, effect) -> dialogue = (title, salet, startTag, endTag, text, effect) ->
retval = room(randomid(), { retval = room(randomid(), salet, {
optionText: title optionText: title
dsc: text dsc: text
clear: false # backlog is useful in dialogues clear: false # backlog is useful in dialogues

View file

@ -4,7 +4,7 @@ Implies that you don't mix up your tabs and spaces.
Copyright 2015 Bruno Dias Copyright 2015 Bruno Dias
### ###
normaliseTabs = (text) -> normaliseTabs = (text) ->
unless text? unless text? and typeof(text) == "string"
return "" return ""
lines = text.split('\n'); lines = text.split('\n');
indents = lines indents = lines

View file

@ -1,14 +1,12 @@
# I confess that this world model heavily borrows from INSTEAD engine. - A.Y. # I confess that this world model heavily borrows from INSTEAD engine. - A.Y.
require('./salet.coffee')
obj = require('./obj.coffee') obj = require('./obj.coffee')
markdown = require('./markdown.coffee') markdown = require('./markdown.coffee')
cycle = require('./cycle.coffee') cycle = require('./cycle.coffee')
Random = require('./random.js')
languages = require('./localize.coffee')
require('./salet.coffee')
# Assertion # Assertion
assert = console.assert assert = (msg, assertion) -> console.assert assertion, msg
way_to = (content, ref) -> way_to = (content, ref) ->
return "<a href='#{ref}' class='way' id='waylink-#{ref}'>#{content}</a>" return "<a href='#{ref}' class='way' id='waylink-#{ref}'>#{content}</a>"
@ -21,43 +19,6 @@ addClass = (element, className) ->
else else
element.className += ' ' + className element.className += ' ' + className
update_ways = (ways, name) ->
content = ""
distances = []
if ways then for way in ways
if salet.rooms[way]?
title = salet.rooms[way].title.fcall(this, name)
content += way_to(title, way)
distances.push({
key: way
distance: salet.rooms[way].distance
})
else
document.querySelector(".ways h2").style.display = "none"
document.getElementById("ways").innerHTML = content
min = Infinity
min_key = []
for node in distances
if node.distance < min
min = node.distance
min_key = [node.key]
if node.distance == min
min_key.push(node.key)
if min < Infinity
for node in min_key
addClass(document.getElementById("waylink-#{node}"), "destination")
picture_tag = (picture) ->
extension = picture.substr((~-picture.lastIndexOf(".") >>> 0) + 2)
if (extension == "webm")
return """
<video src="#{picture}" controls>
Your browser does not support the video tag for some reason.
You won't be able to view this video in this browser.
</video>
"""
return "<img class='img-responsive' src='#{picture}' alt='Room illustration'>"
class SaletRoom class SaletRoom
constructor: (spec) -> constructor: (spec) ->
for index, value of spec for index, value of spec
@ -73,7 +34,6 @@ class SaletRoom
canView: true canView: true
canChoose: true canChoose: true
priority: 1 priority: 1
frequency: 1
displayOrder: 1 displayOrder: 1
tags: [] tags: []
choices: "" choices: ""
@ -84,14 +44,14 @@ class SaletRoom
distance: Infinity # distance to the destination distance: Infinity # distance to the destination
clear: true # clear the screen on entering the room? clear: true # clear the screen on entering the room?
entering: (character, system, from) => entering: (system, from) =>
### ###
I call SaletRoom.exit every time the player exits to another room. I call SaletRoom.exit every time the player exits to another room.
Unlike @after this gets called after the section is closed. Unlike @after this gets called after the section is closed.
It's a styling difference. It's a styling difference.
### ###
exit: (character, system, to) => exit: (system, to) =>
return true return true
### ###
@ -102,7 +62,7 @@ class SaletRoom
The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one. The upstream Undum version does not allow you to redefine @enter function easily but allows custom @exit one.
It was renamed as @entering to achieve API consistency. It was renamed as @entering to achieve API consistency.
### ###
enter: (character, system, from) => enter: (system, from) =>
return true return true
### ###
@ -148,25 +108,25 @@ class SaletRoom
if not @extendSection if not @extendSection
room_content += "</section>" room_content += "</section>"
system.write(room_content) system.view.write(room_content)
if @choices if @choices
system.writeChoices(system.getSituationIdChoices(@choices, @minChoices, @maxChoices)) system.view.writeChoices(system, system.getSituationIdChoices(@choices, @maxChoices))
### ###
An internal function to get the room's description and the descriptions of An internal function to get the room's description and the descriptions of
every object in this room. every object in this room.
### ###
look: (character, system, f) => look: (system, f) =>
update_ways(@ways, @name) system.view.updateWays(system, @ways, @name)
retval = "" retval = ""
if @pic if @pic
retval += '<div class="pic">'+picture_tag(@pic.fcall(this, character, system, f))+'</div>' retval += '<div class="pic">'+system.view.pictureTag(@pic.fcall(this, system, f))+'</div>'
# Print the room description # Print the room description
if @dsc if @dsc
retval += markdown(@dsc.fcall(this, character, system, f)) retval += markdown(@dsc.fcall(this, system, f))
for name, thing of @objects for name, thing of @objects
retval += thing.look() retval += thing.look()
@ -189,7 +149,7 @@ class SaletRoom
Object action. A function or a string which comes when you click on the object link. Object action. A function or a string which comes when you click on the object link.
You could interpret this as an EXAMINE verb or USE one, it's your call. You could interpret this as an EXAMINE verb or USE one, it's your call.
### ###
act: (character, system, action) => act: (system, action) =>
if (link = action.match(/^_(act|cycle)_(.+)$/)) #object action if (link = action.match(/^_(act|cycle)_(.+)$/)) #object action
for name, thing of @objects for name, thing of @objects
if name == link[2] if name == link[2]
@ -215,15 +175,15 @@ class SaletRoom
responses = { responses = {
writer: (ref) -> writer: (ref) ->
content = that.writers[ref].fcall(that, character, system, action) content = that.writers[ref].fcall(that, system, action)
output = markdown(content) output = markdown(content)
system.writeInto(output, '#current-room') system.writeInto(output, '#current-room')
replacer: (ref) -> replacer: (ref) ->
content = that.writers[ref].fcall(that, character, system, action) content = that.writers[ref].fcall(that, system, action)
output = "<span>"+content+"</span>" # <p> tags are usually bad for replacers output = "<span>"+content+"</span>" # <p> tags are usually bad for replacers
system.replaceWith(output, '#'+ref) system.replaceWith(output, '#'+ref)
inserter: (ref) -> inserter: (ref) ->
content = that.writers[ref].fcall(that, character, system, action) content = that.writers[ref].fcall(that, system, action)
output = markdown(content) output = markdown(content)
system.writeInto(output, '#'+ref) system.writeInto(output, '#'+ref)
} }
@ -236,7 +196,7 @@ class SaletRoom
throw new Error("Tried to call undefined writer: #{action}"); throw new Error("Tried to call undefined writer: #{action}");
responses[responder](ref); responses[responder](ref);
else if (@actions.hasOwnProperty(action)) else if (@actions.hasOwnProperty(action))
@actions[action].call(this, character, system, action); @actions[action].call(this, system, action);
else else
throw new Error("Tried to call undefined action: #{action}"); throw new Error("Tried to call undefined action: #{action}");
@ -253,7 +213,7 @@ class SaletRoom
node.distance = current_room.distance + 1 node.distance = current_room.distance + 1
candidates.push(node) candidates.push(node)
register: () => register: (salet) =>
if not @name? if not @name?
console.error("Situation has no name") console.error("Situation has no name")
return this return this
@ -264,17 +224,9 @@ class SaletRoom
cyclewriter: (character) -> cyclewriter: (character) ->
cycle(this.cycle, this.name, character) cycle(this.cycle, this.name, character)
room = (name, spec) -> room = (name, salet, spec) ->
spec ?= {} spec ?= {}
spec.name = name spec.name = name
retval = new SaletRoom(spec) return new SaletRoom(spec).register(salet)
$(document).ready(() ->
if salet
retval.register()
else
sleep(1000)
retval.register()
)
return retval
module.exports = room module.exports = room

View file

@ -1,6 +1,7 @@
markdown = require('./markdown.coffee') markdown = require('./markdown.coffee')
SaletView = require('./view.coffee') SaletView = require('./view.coffee')
Random = require('./random.js') Random = require('./random.js')
languages = require('./localize.coffee')
### ###
fcall() (by analogy with fmap) is added to the prototypes of both String and fcall() (by analogy with fmap) is added to the prototypes of both String and
@ -9,6 +10,8 @@ when called on a String, it only returns the string itself, discarding any input
### ###
Function.prototype.fcall = Function.prototype.call; Function.prototype.fcall = Function.prototype.call;
Boolean.prototype.fcall = () ->
return this
String.prototype.fcall = () -> String.prototype.fcall = () ->
return this return this
@ -33,31 +36,6 @@ parseFn = (str) ->
# Salet's default is a general URL-safe expression. # Salet's default is a general URL-safe expression.
linkRe = /^([0-9A-Za-z_-]+|\.)(\/([0-9A-Za-z_-]+))?$/ linkRe = /^([0-9A-Za-z_-]+|\.)(\/([0-9A-Za-z_-]+))?$/
# Returns HTML from the given content with the non-raw links wired up.
augmentLinks = (content) ->
output = $(content)
# Wire up the links for regular <a> tags.
output.find("a").each((index, element) ->
a = $(element)
href = a.attr('href')
if (!a.hasClass("raw")|| href.match(/[?&]raw[=&]?/))
if (href.match(linkRe))
a.click((event) ->
event.preventDefault()
# If we're a once-click, remove all matching links.
if (a.hasClass("once") || href.match(/[?&]once[=&]?/))
@view.clearLinks(href)
processClick(href)
return false
)
else
a.addClass("raw")
)
return output
### ###
This is the control structure, it has minimal amount of data and This is the control structure, it has minimal amount of data and
this data is volatile anyway (as in, it won't get saved). this data is volatile anyway (as in, it won't get saved).
@ -149,17 +127,6 @@ class Salet
situations that force the player to go to one destination if situations that force the player to go to one destination if
the player is out of money, for example. the player is out of money, for example.
If a minChoices value is given, then the function will attempt
to return at least that many results. If not enough results are
available at the highest priority, then lower priorities will
be considered in turn, until enough situations are found. In
the example above, if we had a minChoices of three, then all
three situations would be returned, even though they have
different priorities. If you need to return all valid
situations, regardless of their priorities, set minChoices to a
large number, such as `Number.MAX_VALUE`, and leave maxChoices
undefined.
If a maxChoices value is given, then the function will not If a maxChoices value is given, then the function will not
return any more than the given number of results. If there are return any more than the given number of results. If there are
more than this number of results possible, then the highest more than this number of results possible, then the highest
@ -181,90 +148,65 @@ class Salet
Before this function returns its result, it sorts the Before this function returns its result, it sorts the
situations in increasing order of their displayOrder values. situations in increasing order of their displayOrder values.
### ###
getSituationIdChoices: (listOfOrOneIdsOrTags, minChoices, maxChoices) -> getSituationIdChoices: (listOfOrOneIdsOrTags, maxChoices) ->
datum = null datum = null
i = 0 i = 0
# First check if we have a single string for the id or tag. # First check if we have a single string for the id or tag.
if ($.type(listOfOrOneIdsOrTags) == 'string') if (typeof(listOfOrOneIdsOrTags) == 'string')
listOfOrOneIdsOrTags = [listOfOrOneIdsOrTags] listOfOrOneIdsOrTags = [listOfOrOneIdsOrTags]
# First we build a list of all candidate ids. # First we build a list of all candidate ids.
allIds = {} allIds = []
for tagOrId in listOfOrOneIdsOrTags for tagOrId in listOfOrOneIdsOrTags
if (tagOrId.substr(0, 1) == '#') if (tagOrId.substr(0, 1) == '#')
ids = getSituationIdsWithTag(tagOrId.substr(1)) ids = @getRoomsTagged(tagOrId.substr(1))
for id in ids for id in ids
allIds[id] = true allIds.push(id)
else else #it's an id, not a tag
allIds[tagOrId] = true allIds.push(tagOrId)
#Filter out anything that can't be viewed right now. #Filter out anything that can't be viewed right now.
currentSituation = @getCurrentRoom() currentRoom = @getCurrentRoom()
viewableSituationData = [] viewableRoomData = []
for situationId in allIds for roomId in allIds
situation = @rooms[situationId] room = @rooms[roomId]
assert(situation, "unknown_situation".l({id:situationId})) assert(room, "unknown_situation".l({id:roomId}))
if (situation.canView(character, salet, currentSituation)) if (room.canView.fcall(this, currentRoom))
#While we're here, get the selection data. viewableRoomData.push({
viewableSituationDatum = situation.choiceData(character, salet, currentSituation) priority: room.priority
viewableSituationDatum.id = situationId id: roomId
viewableSituationData.push(viewableSituationDatum) displayOrder: room.displayOrder
})
# Then we sort in descending priority order. # Then we sort in descending priority order.
viewableSituationData.sort((a, b) -> viewableRoomData.sort((a, b) ->
return b.priority - a.priority return b.priority - a.priority
) )
committed = [] committed = []
candidatesAtLastPriority = []
lastPriority
# In descending priority order.
for datum in viewableSituationData
if (datum.priority != lastPriority)
if (lastPriority != undefined)
# We've dropped a priority group, see if we have enough
# situations so far, and stop if we do.
if (minChoices == undefined || i >= minChoices)
break
# Continue to acccumulate more options.
committed.push.apply(committed, candidatesAtLastPriority);
candidatesAtLastPriority = [];
lastPriority = datum.priority;
candidatesAtLastPriority.push(datum);
# So the values in committed we're committed to, because without # if we need to filter out the results
# them we wouldn't hit our minimum. But those in if (maxChoices? && viewableRoomData.length > maxChoices)
# candidatesAtLastPriority might take us over our maximum, so viewableRoomData = viewableRoomData[-maxChoices..]
# figure out how many we should choose.
totalChoices = committed.length + candidatesAtLastPriority.length for candidateRoom in viewableRoomData
if (maxChoices == undefined || maxChoices >= totalChoices) committed.push({
# We can use all the choices. id: candidateRoom.id
committed.push.apply(committed, candidatesAtLastPriority) displayOrder: candidateRoom.displayOrder
else if (maxChoices >= committed.length) })
# We can only use the commited ones.
# NO-OP
else
# We have to sample the candidates, using their relative frequency.
candidatesToInclude = maxChoices - committed.length;
for datum in candidatesAtLastPriority
datum._frequencyValue = this.rnd.random() / datum.frequency;
candidatesToInclude.sort((a, b) ->
return a.frequencyValue - b.frequencyValue;
)
chosen = candidatesToInclude.slice(0, candidatesToInclude)
committed.push.apply(committed, chosen)
# Now sort in ascending display order. # Now sort in ascending display order.
committed.sort((a, b) -> committed.sort((a, b) ->
return a.displayOrder - b.displayOrder return a.displayOrder - b.displayOrder
) )
# And return as a list of ids only. # And return as a list of ids only.
result = [] result = []
for i in committed for i in committed
result.push(i.id) result.push(i.id)
return result return result
# This is the data on the player's progress that gets saved. # This is the data on the player's progress that gets saved.
@ -313,7 +255,7 @@ class Salet
@linkStack = [] @linkStack = []
# Handle each link in turn. # Handle each link in turn.
processOneLink(code); @processOneLink(code);
while (@linkStack.length > 0) while (@linkStack.length > 0)
code = linkStack.shift() code = linkStack.shift()
processOneLink(code) processOneLink(code)
@ -322,10 +264,10 @@ class Salet
@linkStack = null; @linkStack = null;
# Scroll to the top of the new content. # Scroll to the top of the new content.
@endOutputTransaction() @view.endOutputTransaction()
# We're able to save, if we weren't already. # We're able to save, if we weren't already.
@enableSaving() @view.enableSaving()
### ###
This gets called to actually do the work of processing a code. This gets called to actually do the work of processing a code.
@ -341,9 +283,8 @@ class Salet
action = match[3] action = match[3]
# Change the situation # Change the situation
if situation != '.' if situation != '.' and situation != @current_room
if situation != current @doTransitionTo(situation)
@doTransitionTo(situation)
else else
# We should have an action if we have no situation change. # We should have an action if we have no situation change.
assert(action, "link_no_action".l()) assert(action, "link_no_action".l())
@ -370,9 +311,9 @@ class Salet
# This gets called when the user clicks a link to carry out an action. # This gets called when the user clicks a link to carry out an action.
processClick: (code) -> processClick: (code) ->
now = (new Date()).getTime() * 0.001 now = (new Date()).getTime() * 0.001
@time = now - startTime @time = now - @startTime
@progress.sequence.push({link:code, when:@time}) @progress.sequence.push({link:code, when:@time})
processLink(code) @processLink(code)
# Transitions between situations. # Transitions between situations.
doTransitionTo: (newRoomId) -> doTransitionTo: (newRoomId) ->
@ -385,11 +326,11 @@ class Salet
# We might not have an old situation if this is the start of the game. # We might not have an old situation if this is the start of the game.
if (oldRoom and @exit) if (oldRoom and @exit)
@exit(oldRoomId, newRoomId) @exit(oldRoomId, newRoomId)
@current = newRoomId @current = newRoomId
# Remove links and transient sections. # Remove links and transient sections.
@view.remove_transient(@interactive) @view.removeTransient(@interactive)
# Notify the incoming situation. # Notify the incoming situation.
if (@enter) if (@enter)
@ -407,19 +348,19 @@ class Salet
game state across save/erase cycles, meaning that character.sandbox game state across save/erase cycles, meaning that character.sandbox
no longer has to be the end-all be-all repository of game state. no longer has to be the end-all be-all repository of game state.
### ###
erase_save: (force = false) => eraseSave: (force = false) =>
save_id = @getSaveId() # save slot save_id = @getSaveId() # save slot
if (localStorage.getItem(saveId) and (force or confirm("erase_message".l()))) if (localStorage.getItem(saveId) and (force or confirm("erase_message".l())))
localStorage.removeItem(saveId) localStorage.removeItem(saveId)
window.location.reload() window.location.reload()
# Find and return a list of ids for all situations with the given tag. # Find and return a list of ids for all situations with the given tag.
getSituationIdsWithTag: (tag) => getRoomsTagged: (tag) =>
result = [] result = []
for situationId, situation of @situations for id, room of @rooms
for i in situation.tags for i in room.tags
if (i == tag) if (i == tag)
result.push(situationId) result.push(id)
break break
return result return result
@ -477,7 +418,7 @@ class Salet
@view.disableSaving() @view.disableSaving()
@view.enableErasing() @view.enableErasing()
catch err catch err
@erase_save(true) @eraseSave(true)
else else
@progress.seed = new Date().toString() @progress.seed = new Date().toString()
@ -485,8 +426,6 @@ class Salet
@rnd = new Random(@progress.seed) @rnd = new Random(@progress.seed)
@progress.sequence = [{link:@start, when:0}] @progress.sequence = [{link:@start, when:0}]
@view.clearContent()
# Start the game # Start the game
@startTime = new Date().getTime() * 0.001 @startTime = new Date().getTime() * 0.001
if (@init) if (@init)
@ -515,11 +454,4 @@ class Salet
return Boolean place.visited return Boolean place.visited
return 0 return 0
# Set up the game when everything is loaded. module.exports = Salet
$(document).ready(() ->
salet = new Salet
salet.view.init()
if (salet.view.hasLocalStorage())
$("#erase").click(salet.erase_save) # is Salet defined here?
$("#save").click(salet.saveGame)
)

View file

@ -1,3 +1,4 @@
markdown = require('./markdown.coffee')
### ###
Salet interface configuration. Salet interface configuration.
In a typical MVC structure, this is the View. In a typical MVC structure, this is the View.
@ -10,8 +11,13 @@ game as he wants to. The save and erase buttons are not necessary buttons,
but they could be something else entirely. (That's why IDs are hardcoded.) but they could be something else entirely. (That's why IDs are hardcoded.)
### ###
assert = (msg, assertion) -> console.assert assertion, msg
way_to = (content, ref) ->
return "<a href='#{ref}' class='way'>#{content}</a>"
class SaletView class SaletView
init: () -> init: (salet) ->
$("#content, #ways").on("click", "a", (event) -> $("#content, #ways").on("click", "a", (event) ->
event.preventDefault() event.preventDefault()
salet.processClick($(this).attr("href")) salet.processClick($(this).attr("href"))
@ -23,6 +29,9 @@ class SaletView
$("#load").on("click", "a", (event) -> $("#load").on("click", "a", (event) ->
window.location.reload() window.location.reload()
) )
if (@hasLocalStorage())
$("#erase").click(salet.erase_save) # is Salet defined here?
$("#save").click(salet.saveGame)
disableSaving: () -> disableSaving: () ->
$("#save").addClass('disabled') $("#save").addClass('disabled')
@ -67,11 +76,15 @@ class SaletView
return return
if typeof content == "function" if typeof content == "function"
content = content() content = content()
if content instanceof jQuery
content = content[0].outerHTML
block = document.getElementById("current-room") block = document.getElementById("current-room")
if block if block
block.innerHTML = block.innerHTML + markdown(content) block.innerHTML = block.innerHTML + markdown(content)
else else
console.error("No current situation found.") # most likely this is the starting room
block = document.getElementById("content")
block.innerHTML = content
### ###
Turns any links that target the given href into plain Turns any links that target the given href into plain
@ -98,25 +111,25 @@ class SaletView
manually, ot else use the `getSituationIdChoices` method to manually, ot else use the `getSituationIdChoices` method to
return an ordered list of valid viewable situation ids. return an ordered list of valid viewable situation ids.
### ###
writeChoices: (listOfIds) -> writeChoices: (salet, listOfIds) ->
if (listOfIds.length == 0) if (not listOfIds? or listOfIds.length == 0)
return return
currentSituation = getCurrentSituation(); currentRoom = salet.getCurrentRoom();
$options = $("<ul>").addClass("options"); $options = $("<ul>").addClass("options");
for situationId in listOfIds for roomId in listOfIds
situation = game.situations[situationId] room = salet.rooms[roomId]
assert(situation, "unknown_situation".l({id:situationId})) assert(room, "unknown_situation".l({id:roomId}))
if (situation == currentSituation) if (room == currentRoom)
continue continue
optionText = situation.optionText.fcall(this, character, this, currentSituation) optionText = room.optionText.fcall(salet, currentRoom)
if (!optionText) if (!optionText)
optionText = "choice".l({number:i+1}) optionText = "choice".l({number:i+1})
$option = $("<li>") $option = $("<li>")
$a = $("<span>") $a = $("<span>")
if (situation.canChoose(character, this, currentSituation)) if (room.canChoose.fcall(this, salet, currentRoom))
$a = $("<a>").attr({href: situationId}) $a = $("<a>").attr({href: roomId})
$a.html(optionText) $a.html(optionText)
$option.html($a) $option.html($a)
$options.append($option) $options.append($option)
@ -129,8 +142,9 @@ class SaletView
# Removes links and transient sections. # Removes links and transient sections.
# Arguments: # Arguments:
# interactive - if we're working in interactive mode (or we're loading a save) # interactive - if we're working in interactive mode (or we're loading a save)
remove_transient: (interactive = false) -> removeTransient: (interactive = false) ->
for a in $('#content a') for a in $('#content').find('a')
a = $(a)
if (a.hasClass('sticky') || a.attr("href").match(/[?&]sticky[=&]?/)) if (a.hasClass('sticky') || a.attr("href").match(/[?&]sticky[=&]?/))
return; return;
a.replaceWith($("<span>").addClass("ex_link").html(a.html())) a.replaceWith($("<span>").addClass("ex_link").html(a.html()))
@ -193,4 +207,64 @@ class SaletView
hasStorage = false hasStorage = false
return hasStorage return hasStorage
updateWays: (salet, ways, name) ->
content = ""
distances = []
if ways then for way in ways
if salet.rooms[way]?
title = salet.rooms[way].title.fcall(this, name)
content += way_to(title, way)
distances.push({
key: way
distance: salet.rooms[way].distance
})
else
document.querySelector(".ways h2").style.display = "none"
document.getElementById("ways").innerHTML = content
min = Infinity
min_key = []
for node in distances
if node.distance < min
min = node.distance
min_key = [node.key]
if node.distance == min
min_key.push(node.key)
if min < Infinity
for node in min_key
addClass(document.getElementById("waylink-#{node}"), "destination")
pictureTag = (picture) ->
extension = picture.substr((~-picture.lastIndexOf(".") >>> 0) + 2)
if (extension == "webm")
return """
<video src="#{picture}" controls>
Your browser does not support the video tag for some reason.
You won't be able to view this video in this browser.
</video>
"""
return "<img class='img-responsive' src='#{picture}' alt='Room illustration'>"
# Returns HTML from the given content with the non-raw links wired up.
augmentLinks: (content) ->
output = $(content)
# Wire up the links for regular <a> tags.
for index, element in output.find("a")
a = $(element)
href = a.attr('href')
if (!a.hasClass("raw")|| href.match(/[?&]raw[=&]?/))
if (href.match(linkRe))
a.click((event) ->
event.preventDefault()
# If we're a once-click, remove all matching links.
if (a.hasClass("once") || href.match(/[?&]once[=&]?/))
@view.clearLinks(href)
processClick(href)
return false
)
else
a.addClass("raw")
return output
module.exports = SaletView module.exports = SaletView