| /* | |
| * | |
| * A keyboardless text adventure engine | |
| * Version 1.1, 2016 | |
| * Copy and distribute freely, preserving this licence. | |
| * | |
| */ | |
| function update_display() { | |
| // if using conversifier.js, this is not the update_display() that gets used! | |
| describe_room(); | |
| show_holding(); | |
| show_inventory(); | |
| show_holding(); | |
| show_score(); | |
| animate_heights(); | |
| } | |
| function show_score() { | |
| document.getElementById('score').innerHTML = | |
| // (is_landscape() ? ("Moves: " + Game.turn_number + " Score: " ) : "") + | |
| (Game.score_percentage ? (Math.round((Game.score / Game.max_score) * 100) + '%') : (Game.score + '/' + Game.max_score)); | |
| } | |
| function get_point_for(gamevar) { | |
| // give the player a point, then set a variable to true so that they can only get this point once | |
| if(!Game.vars[gamevar]) { | |
| Game.vars[gamevar] = true; | |
| Game.score++; | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| function describe_room() { | |
| var html = ''; | |
| var lit = is_lit(Game.hero_location); | |
| if(lit) { | |
| html += Game.rooms[Game.hero_location].description; | |
| } else { | |
| html += "It's dark."; | |
| } | |
| html += '<br/>'; | |
| var exitOrder = ['north', 'northeast', 'east', 'southeast', | |
| 'south', 'southwest', 'west', 'northwest', | |
| 'up', 'down', 'in', 'out']; | |
| var exits = []; | |
| exitOrder.forEach(function(dir) { | |
| if(Game.rooms[Game.hero_location].directions[dir]) { | |
| //exits.push('<a onclick="go(\'' + dir + '\')" href="javascript:void(0);">' + dir + '</a>'); | |
| exits.push(draw_button(dir, "go('" + dir + "')")); | |
| } | |
| /* else push an unavailable exit marker of same width? */ | |
| }); | |
| /* for(var key in Game.rooms[Game.hero_location].directions) { | |
| exits.push('<a onclick="go(\'' + key + '\')" href="javascript:void(0);">' + key + '</a>'); | |
| } */ | |
| if(exits.length) { | |
| html += 'Exits: ' + exits.join(' ') + '<br/>'; | |
| } | |
| if(Game.rooms[Game.hero_location].active_verbs && Game.rooms[Game.hero_location].active_verbs.length) { | |
| if(Game.rooms[Game.hero_location].actions_name) { | |
| html += Game.rooms[Game.hero_location].actions_name + ': '; | |
| } else { | |
| html += "Actions: " | |
| } | |
| html += list_verbs(Game.hero_location, true) + '<br/>'; | |
| } | |
| /* document.getElementById('also_see').innerHTML = html; | |
| show_can_also_see(); | |
| } | |
| function show_can_also_see() { | |
| var html = '<br/>'; | |
| var lit = is_lit(Game.hero_location); */ | |
| var things_here = []; | |
| // rooms that are e.g. buildings can be listed in with the things if they have the 'enter_from' property | |
| /* actually this opens a can of worms by allowing a room (not a thing) to be the object of the verb) | |
| for(var room in Game.rooms) { | |
| if(Game.rooms[room].enter_from == Game.hero_location) { | |
| things_here.push(room + ' [<a onClick="do_verb(\'enter\', \'' + room + '\')" href="javascript:void(0)">enter</a>]'); | |
| } | |
| } | |
| */ | |
| // alive things come first... | |
| for(var key in Game.things) { | |
| if(Game.things[key].location == Game.hero_location && Game.things[key].is_alive && !Game.things[key].list_last) { | |
| var this_thing = Game.things[key].description + ' ' + list_verbs(key); | |
| things_here.push(this_thing); | |
| } | |
| } | |
| // ... then non-portable non-alive things | |
| for(var key in Game.things) { | |
| if(Game.things[key].location == Game.hero_location && !Game.things[key].is_alive && | |
| !Game.things[key].portable && !Game.things[key].list_last) { | |
| var this_thing = Game.things[key].description + ' ' + list_verbs(key); | |
| things_here.push(this_thing); | |
| } | |
| } | |
| // then portable non-alive things | |
| for(var key in Game.things) { | |
| if(Game.things[key].location == Game.hero_location && Game.things[key].portable | |
| && !Game.things[key].is_alive && !Game.things[key].list_last) { | |
| var this_thing = Game.things[key].description + ' ' + list_verbs(key); | |
| things_here.push(this_thing); | |
| } | |
| } | |
| // then anything with list_last | |
| for(var key in Game.things) { | |
| if(Game.things[key].location == Game.hero_location && Game.things[key].list_last) { | |
| var this_thing = Game.things[key].description + ' ' + list_verbs(key); | |
| things_here.push(this_thing); | |
| } | |
| } | |
| if(lit && things_here.length) { | |
| html += 'I can also see:\n<ul>'; | |
| things_here.forEach(function(description) { | |
| html += '\n<li>' + description + '</li>'; | |
| }) | |
| html += '</ul>\n'; | |
| } | |
| html += '<div class="wait"><button type="button" onClick="do_verb(\'wait\', \'\')">wait</button></div>'; | |
| document.getElementById('room_description').innerHTML = html; | |
| } | |
| function describe_room_in_scroller() { | |
| if(Game.suppress_room_message) { | |
| Game.suppress_room_message = false; | |
| return; | |
| } | |
| if(is_lit(Game.hero_location)) { | |
| if(!Game.rooms[Game.hero_location].description) { | |
| console.log("room '" + Game.hero_location + "' has no description"); | |
| } | |
| say(Game.rooms[Game.hero_location].description); | |
| if(listobj_in_scroller() && !Game.suppress_room_message) { | |
| var things_here = []; | |
| // same order as in scroller! | |
| // alive things first | |
| for(var thing in Game.things) { | |
| if(Game.things[thing].is_alive && Game.things[thing].location == Game.hero_location && !Game.things[thing].list_last) { | |
| things_here.push(Game.things[thing].description); | |
| } | |
| } | |
| // then non-portable non-alive things | |
| for(var thing in Game.things) { | |
| if(!Game.things[thing].is_alive && !Game.things[thing].portable && | |
| Game.things[thing].location == Game.hero_location && !Game.things[thing].list_last) { | |
| things_here.push(Game.things[thing].description); | |
| } | |
| } | |
| // then portable non-alive things | |
| for(var thing in Game.things) { | |
| if(!Game.things[thing].is_alive && Game.things[thing].portable && | |
| Game.things[thing].location == Game.hero_location && !Game.things[thing].list_last) { | |
| things_here.push(Game.things[thing].description); | |
| } | |
| } | |
| // then anything with list_last | |
| for(var thing in Game.things) { | |
| if(Game.things[thing].list_last && Game.things[thing].location == Game.hero_location) { | |
| things_here.push(Game.things[thing].description); | |
| } | |
| } | |
| if(things_here.length) { | |
| say("I can also see:<br/> <ul class=\"room_contents_in_scroller\"><li>" + things_here.join('</li><li>') + '</li></ul>'); | |
| } | |
| } | |
| } else { | |
| say("It's dark."); | |
| } | |
| }; | |
| function draw_button(display_text, script) { | |
| return '<button type="button" onClick="' + script + '">' + display_text + '</button>'; | |
| } | |
| function show_holding() { | |
| var html = ''; | |
| if(Game.held) { | |
| //var itemHtml = Game.things[Game.held].description + ' ' + list_verbs(Game.held); | |
| html = "I'm holding:\n<ul><li>" + Game.things[Game.held].description + ' ' + list_verbs(Game.held) + '</li></ul>'; | |
| } | |
| // also show worn things in here | |
| var worn = []; | |
| for(var thing in Game.things) { | |
| if(Game.things[thing].worn) { | |
| worn.push(Game.things[thing].description + ' ' + list_verbs(thing)); | |
| } | |
| } | |
| if(worn.length) { | |
| html += "\nI'm wearing:\n<ul><li>" + worn.join('</li>\n<li>') + "</li></ul>"; | |
| } | |
| document.getElementById('holding').innerHTML = html; | |
| } | |
| function show_inventory() { | |
| var html = "I'm " + (Game.held ? "also " : "") + "carrying:\n"; //<ul><li>"; | |
| var carried = []; | |
| Game.inventory.forEach(function(thing) { | |
| if(!held(thing) && !Game.things[thing].worn) { | |
| // var itemHtml = Game.things[thing].description + ' ' + list_verbs(thing); | |
| var itemHtml = draw_button(Game.things[thing].description, "hold('" + thing + "')"); | |
| carried.push(itemHtml); | |
| } | |
| }); | |
| if(carried.length) { | |
| html += carried.join(' '); //('</li>\n<li>'); | |
| } else { | |
| html = ''; // html += 'nothing'; | |
| } | |
| //html += '</li></ul>'; | |
| document.getElementById('inventory').innerHTML = html; | |
| } | |
| function hold(thing) { | |
| if(Game.held) { | |
| unhold(true); | |
| } | |
| removeFrom(Game.inventory, thing); | |
| Game.held = thing; | |
| do_onholds(); | |
| update_display(); | |
| } | |
| function unhold(dont_update) { | |
| if(!Game.held) { | |
| return; | |
| } | |
| Game.inventory.unshift(Game.held); | |
| Game.held = ''; | |
| do_onholds(); | |
| if(!dont_update) { | |
| update_display(); | |
| } | |
| } | |
| function say(txt) { | |
| if(GAME_OVER) { return; } | |
| $('#scroller').append('<p><span class="new_message">' + replaceAll(txt, '\n', '<br/>') + '</span></p>'); | |
| //document.getElementById('scroller').innerHTML += '<br/><span class="new_message">' + replaceAll(txt, '\n', '<br/>') + '</span>'; | |
| //document.getElementById('scroller').scrollTop = document.getElementById('scroller').scrollHeight; | |
| //typing sound | |
| if(typing_sound()) { | |
| document.getElementById('typing_audio').play(); | |
| stopTyping = setTimeout(function() { document.getElementById('typing_audio').pause(); }, 2000); | |
| } | |
| // animated scroll | |
| $('#scroller').stop().animate({scrollTop: $('#scroller').prop("scrollHeight")}, 500); | |
| } | |
| function say_if_lit(txt, darktxt) { | |
| if(is_lit(Game.hero_location)) { | |
| say(txt); | |
| } else if(darktxt) { | |
| say(darktxt); | |
| } | |
| } | |
| function dim_old_messages() { | |
| $('#scroller span.prompt').addClass('old_prompt'); | |
| $('#scroller span.new_message').removeClass('new_message').addClass('old_message'); | |
| } | |
| function die(message, deathMessage) { | |
| say(message); | |
| if(!deathMessage) { deathMessage = "I am dead"; } | |
| say("*** " + deathMessage + " ***"); | |
| //say('(<a onclick="restart_game()" href="javascript:void(0)">restart game</a>)'); | |
| say("(" + draw_button("restart game", "confirm_restart()") + ")"); | |
| for(var fuse in Game.fuses) { | |
| stop_fuse(fuse); | |
| } | |
| update_display(); | |
| GAME_OVER = true; | |
| } | |
| function replaceAll(txt, find, replace) { | |
| return txt.split(find).join(replace); | |
| } | |
| function add_rule(when, verb, object, func) { | |
| Rules.push({ when: when, verb: verb, object: object, func: func }); | |
| } | |
| function before(verb, object, func) { | |
| add_rule('before', verb, object, func); | |
| } | |
| function before(verb, object, func) { | |
| add_rule('after', verb, object, func); | |
| } | |
| function find_rule(when, verb, object) { | |
| for(var i=0; i<Rules.length; ++i) { | |
| var rule = Rules[i]; | |
| if(rule.when==when && rule.verb==verb && rule.object==object) { | |
| return rule; | |
| } | |
| } | |
| return null; | |
| } | |
| current_verb = ''; | |
| function go(direction) { | |
| Game.just_moved = false; | |
| if(GAME_OVER) { | |
| return; | |
| } | |
| push_undo_state(direction); | |
| current_verb = 'go'; | |
| var oldloc = Game.hero_location; | |
| dim_old_messages(); | |
| say('\n<span class="prompt">> ' + direction + '</span>'); | |
| var instead = false; | |
| if(Game.before) { | |
| Game.before.forEach(function(before) { | |
| var thisInstead = before('depart'); | |
| if(thisInstead) instead = true; | |
| }); | |
| } | |
| if(!instead) { | |
| if(Game.rooms[Game.hero_location].before) { | |
| if(Game.rooms[Game.hero_location].before.depart) { | |
| instead = Game.rooms[Game.hero_location].before.depart(direction); | |
| } | |
| } | |
| } | |
| if(!instead && !GAME_OVER) { | |
| if(Game.suppress_ok) { | |
| delete Game.suppress_ok; | |
| } else { | |
| say('Ok'); | |
| } | |
| Game.hero_location = Game.rooms[Game.hero_location].directions[direction]; | |
| } | |
| var newroom = Game.hero_location; | |
| if(newroom != oldloc && roomdesc_in_scroller()) { | |
| describe_room_in_scroller(); | |
| } | |
| if(newroom != oldloc) { | |
| Game.just_moved = true; | |
| } | |
| if(!instead) { | |
| if(Game.rooms[oldloc].after && Game.rooms[oldloc].after.depart) { | |
| Game.rooms[oldloc].after.depart(direction); | |
| } | |
| if(Game.rooms[Game.hero_location].after && Game.rooms[Game.hero_location].after.arrive) { | |
| Game.rooms[Game.hero_location].after.arrive(); | |
| } | |
| } | |
| if(!GAME_OVER) { | |
| do_fuses(); | |
| do_alwayses(); | |
| } | |
| if(Game.hero_location != newroom && roomdesc_in_scroller()) { | |
| describe_room_in_scroller(); | |
| } | |
| //describe_room(); | |
| update_display(); | |
| } | |
| function roomdesc_in_scroller() { | |
| return $('#roomdesc_always').prop('checked') || ($('#roomdesc_landscape').prop('checked') && is_landscape()); | |
| } | |
| function listobj_in_scroller() { | |
| return $('#listobj_always').prop('checked') || ($('#listobj_landscape').prop('checked') && is_landscape()); | |
| } | |
| function typing_sound() { | |
| //try { | |
| // return localStorage.getItem(Game.id + 'typing_sound')=='1'; | |
| //} catch(e) { | |
| return $('#typing_sound').prop('checked'); | |
| //} | |
| } | |
| function verbose() { | |
| return $('#verbose').prop('checked') | |
| } | |
| function music_on() { | |
| return $('#music').prop('checked'); | |
| } | |
| function do_verb(verb, thing, not_a_turn) { | |
| Game.just_moved = false; | |
| if(GAME_OVER) { | |
| return; | |
| } | |
| if(!Verbs[verb]) { | |
| console.log("tried to do nonexistent verb '" + verb + "' (to " + thing + ")"); | |
| } | |
| var phrasing; | |
| if(Verbs[verb] && Verbs[verb].phrasing) { | |
| phrasing = Verbs[verb].phrasing(thing); | |
| } else if(Verbs[verb] && Verbs[verb].intransitive) { | |
| phrasing = display_verb(verb); | |
| } else { | |
| phrasing = display_verb(verb); | |
| if(thing && Game.things[thing].preposition && Game.things[thing].preposition[verb]) { | |
| phrasing += ' ' + Game.things[thing].preposition[verb]; | |
| } | |
| phrasing += ' ' + name(thing); | |
| } | |
| if(!not_a_turn) { | |
| // not_a_turn is used e.g. when one command redirects to another | |
| // (like "fire pistol" to "shoot werewolf" if the werewolf is resent) | |
| // so undo state isn't pushed and alwayses don't happen (e.g. time doesn't pass) for the "shoot werewolf" | |
| // - they'll happen for the "fire pistol", which is what the player 'typed' | |
| dim_old_messages(); | |
| say('\n<span class="prompt">> ' + phrasing + '</span>'); | |
| push_undo_state(phrasing); | |
| current_verb = verb; | |
| } | |
| var oldroom = Game.hero_location; | |
| var instead = false; | |
| if(Game.before) { | |
| Game.before.forEach(function(func) { | |
| var thisInstead = func(verb, thing); | |
| if(thisInstead) instead = true; | |
| }); | |
| } | |
| if(!instead) { | |
| if(Game.rooms[Game.hero_location].before[verb]) { | |
| instead = Game.rooms[Game.hero_location].before[verb](thing); | |
| } | |
| } | |
| if(!instead) { | |
| var rule = find_rule('before', verb, thing); | |
| if(rule) { | |
| instead = rule.func(); | |
| } else if(thing && Game.things[thing].before) { | |
| if(Game.things[thing].before[verb]) { | |
| instead = Game.things[thing].before[verb](); | |
| } | |
| } | |
| } | |
| if(!instead) { | |
| if(Verbs[verb].func) { | |
| Verbs[verb].func(thing); | |
| } else { | |
| say("I don't know how to " + verb + " that."); // shouldn't happen! | |
| } | |
| } | |
| var newroom = Game.hero_location | |
| if(newroom != oldroom && roomdesc_in_scroller()) { | |
| describe_room_in_scroller(); | |
| } | |
| if(newroom != oldroom) { | |
| Game.just_moved = true; | |
| } | |
| if(!instead) { | |
| if(Game.rooms[Game.hero_location].after) { | |
| for(var after_verb in Game.rooms[Game.hero_location].after) { | |
| if(after_verb == verb) { | |
| Game.rooms[Game.hero_location].after[after_verb](thing); | |
| } | |
| } | |
| } | |
| var rule = find_rule('after', verb, thing); | |
| if(rule) { | |
| rule.func(); | |
| } else if(thing && Game.things[thing].after) { | |
| if(Game.things[thing].after[verb]) { | |
| Game.things[thing].after[verb](); | |
| } | |
| } | |
| } | |
| if(!not_a_turn) { | |
| do_fuses(); | |
| do_alwayses(); | |
| } | |
| // todo: check other alwayses | |
| if(Game.hero_location != newroom && roomdesc_in_scroller()) { | |
| describe_room_in_scroller(); | |
| } | |
| update_display(); | |
| } | |
| function do_alwayses() { | |
| if(Game.always) { | |
| for(var func in Game.always) { | |
| Game.always[func](); | |
| } | |
| } | |
| if(Game.rooms[Game.hero_location].always) { | |
| Game.rooms[Game.hero_location].always(); | |
| }; | |
| for(var thing in Game.things) { | |
| if(in_scope(thing) && Game.things[thing].always) { | |
| // console.log('doing always for ' + thing); | |
| Game.things[thing].always(); | |
| } | |
| } | |
| for(var thing in Game.things) { | |
| if(Game.things[thing].active_when_carried) { | |
| Game.things[thing].active_when_carried.forEach(function(verb) { | |
| set_active(thing, verb, carried(thing)); | |
| }); | |
| } | |
| } | |
| do_onholds(); | |
| } | |
| function do_onholds() { | |
| On_hold.forEach(function(onhold) { | |
| onhold(); | |
| }); | |
| } | |
| function do_fuses() { | |
| for(var fuse in Game.fuses) { | |
| if(Game.fuses[fuse].time > 0) { | |
| Game.fuses[fuse].time = Game.fuses[fuse].time - 1; | |
| // console.log(fuse + ' fires in ' + Game.fuses[fuse].time); | |
| } | |
| var time = Game.fuses[fuse].time; | |
| if (time == 0) { | |
| // fuses don't need to have an explode, or a burn | |
| if(Game.fuses[fuse].explode) { | |
| Game.fuses[fuse].explode(); | |
| Game.fuses[fuse].time = -1; | |
| } | |
| } else if(time > 0 && Game.fuses[fuse].burn) { | |
| Game.fuses[fuse].burn(); | |
| } | |
| } | |
| } | |
| function set_fuse(fuse, time) { | |
| //console.log("setting fuse '" + fuse + "' to " + time); | |
| if(!Game.fuses[fuse]) { | |
| Game.fuses[fuse] = {}; | |
| } | |
| Game.fuses[fuse].time = time; | |
| } | |
| function stop_fuse(fuse) { | |
| //console.log('stopping fuse "' + fuse + '"'); | |
| if(Game.fuses[fuse]) { | |
| Game.fuses[fuse].time = -1; // doesn't explode | |
| } | |
| } | |
| function explode_fuse(fuse) { | |
| Game.fuses[fuse].time = 0; | |
| Game.fuses[fuse].explode(); | |
| } | |
| function list_verbs(thing, is_room) { | |
| var things_or_rooms = is_room ? 'rooms' : 'things'; | |
| var snippets = []; | |
| for(var verb in Verbs) { | |
| //console.log('looking at ' + verb + ' for ' + things_or_rooms + '.' + thing) | |
| if(Game[things_or_rooms][thing].active_verbs.indexOf(verb) != -1) { | |
| // snippets.push('<a onClick="do_verb(\'' + verb + '\',\'' + thing + '\')" href="javascript:void(0)">' + display_verb(verb) + '</a>'); | |
| snippets.push(draw_button(display_verb(verb), "do_verb('" + verb + "','" + thing + "')")); | |
| } | |
| } | |
| // allow things to have active_verbs that don't appear in Verbs | |
| // (in this case the thing MUST catch the verb with a 'before', and return true) | |
| Game[things_or_rooms][thing].active_verbs.forEach(function(verb) { | |
| if(!Verbs[verb]) { | |
| // snippets.push('<a onClick="do_verb(\'' + verb + '\',\'' + thing + '\')" href="javascript:void(0)">' + display_verb(verb) + '</a>'); | |
| snippets.push(draw_button(display_verb(verb), "do_verb('" + verb + "','" + thing + "')")); | |
| } | |
| }); | |
| if(snippets.length) { | |
| return snippets.join(' '); | |
| } else return ''; | |
| } | |
| function display_verb(verb) { | |
| if(Verbs[verb] && Verbs[verb].display) { | |
| return Verbs[verb].display; | |
| } else { | |
| return verb; | |
| } | |
| } | |
| function activate_verb(thing, verb) { | |
| // console.log('activating ' + verb + ' for ' + thing); | |
| if(!Game.things[thing].active_verbs) { | |
| Game.things[thing].active_verbs = []; | |
| } | |
| if(Game.things[thing].active_verbs.indexOf(verb) == -1) { | |
| Game.things[thing].active_verbs.push(verb); | |
| } | |
| } | |
| function activate_room_verb(room, verb) { | |
| //console.log('activating ' + verb + ' for room ' + room); | |
| if(!Game.rooms[room].active_verbs) { | |
| Game.rooms[room].active_verbs = []; | |
| } | |
| if(Game.rooms[room].active_verbs.indexOf(verb) == -1) { | |
| Game.rooms[room].active_verbs.push(verb); | |
| } | |
| } | |
| function deactivate_verb(thing, verb) { | |
| //console.log('deactivating ' + verb + ' for ' + thing); | |
| if(!Game.things[thing]) { | |
| console.log('tried to deactivate "' + verb + '" for nonexistent thing "' + thing + '"'); | |
| } | |
| removeFrom(Game.things[thing].active_verbs, verb); | |
| } | |
| function deactivate_room_verb(room, verb) { | |
| //console.log('deactivating ' + verb + ' for room ' + room); | |
| removeFrom(Game.rooms[room].active_verbs, verb); | |
| } | |
| function deactivate_all(thing) { | |
| //console.log('deactivating all verbs for ' + thing); | |
| Game.things[thing].active_verbs = []; | |
| } | |
| function set_active(thing, verb, bool) { | |
| if(bool) { | |
| activate_verb(thing, verb); | |
| } else { | |
| deactivate_verb(thing, verb); | |
| } | |
| } | |
| function activate_if_held(thing, verb, tool) { | |
| if(held(tool)) { | |
| activate_verb(thing, verb); | |
| } else { | |
| deactivate_verb(thing, verb); | |
| } | |
| } | |
| function removeFrom(array, item) { | |
| for(var i=array.length - 1; i >= 0; --i) { | |
| if(array[i]==item) { | |
| array.splice(i, 1); | |
| } | |
| } | |
| } | |
| var INITIAL_GAME = {}; | |
| var PLAYED = false; | |
| var GAME_OVER = false; | |
| function initialise() { | |
| if(!PLAYED) { | |
| INITIAL_GAME = JSONfn.stringify(Game); | |
| PLAYED = true; | |
| } | |
| document.title = Game.title; | |
| document.getElementById('title').innerHTML = Game.title; | |
| if(Game.initialise) { | |
| Game.initialise(); | |
| } | |
| for(var thing in Game.things) { | |
| if(typeof Game.things[thing].active_verbs === 'undefined') { | |
| Game.things[thing].active_verbs = []; | |
| } | |
| if(typeof Game.things[thing].active_when_carried === 'undefined') { | |
| Game.things[thing].active_when_carried = []; | |
| } | |
| if(Game.things[thing].portable) { | |
| set_active(thing, 'take', !carried(thing)); | |
| set_active(thing, 'drop', carried(thing)); | |
| } | |
| if(Game.things[thing].active_when_carried) { | |
| Game.things[thing].active_when_carried.forEach(function(verb) { | |
| set_active(thing, verb, carried(thing)); | |
| }); | |
| } | |
| if(!Game.things[thing].description) { | |
| Game.things[thing].description = replaceAll(thing, '_', ' '); | |
| } | |
| if(Game.things[thing].initialise) { | |
| Game.things[thing].initialise(); | |
| } | |
| } | |
| for(var room in Game.rooms) { | |
| if(typeof Game.rooms[room].directions === 'undefined') { | |
| Game.rooms[room].directions = {}; | |
| } | |
| if(typeof Game.rooms[room].before === 'undefined') { | |
| Game.rooms[room].before = {}; | |
| } | |
| if(typeof Game.rooms[room].after === 'undefined') { | |
| Game.rooms[room].after = {}; | |
| } | |
| if(Game.rooms[room].initialise) { | |
| Game.rooms[room].initialise(); | |
| } | |
| } | |
| say(Game.intro); | |
| if(is_landscape()) { | |
| say("<u>" + Game.title.toUpperCase() + (Game.author ? ", by " + Game.author + "</u>" : "")); | |
| } | |
| //say('For instructions, touch: <a href="javascript:void(0)" onClick="instructions()">instructions</a>\n'); | |
| say("For instructions, touch: " + draw_button("instructions", "instructions()") + "\n"); | |
| restore_checkboxes(); | |
| if(roomdesc_in_scroller()) { | |
| describe_room_in_scroller(); | |
| } | |
| set_heights(); | |
| //do_alwayses(); | |
| } | |
| function confirm_restart() { | |
| if(GAME_OVER || confirm("Really restart this story?")) { | |
| restart_game(); | |
| } | |
| } | |
| function restart_game() { | |
| GAME_OVER = false; | |
| Game = JSONfn.parse(INITIAL_GAME); | |
| document.getElementById('scroller').innerHTML = ''; | |
| initialise(); | |
| //describe_room(); | |
| update_display(); | |
| set_heights(); | |
| } | |
| function is_lit(room) { | |
| if(!Game.rooms[room]) { | |
| console.log("tried to check lit status of nonexistent room '" + room + "'"); | |
| } | |
| if(!Game.rooms[room].dark) { | |
| return true; | |
| } | |
| for(var thing in Game.things) { | |
| if(Game.things[thing].is_lit && ( | |
| (Game.things[thing].location == room) || | |
| (Game.hero_location == room && carried(thing)) | |
| ) | |
| ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| function uncarry(thing) { | |
| if(held(thing)) { | |
| unhold(true); | |
| } | |
| if(Game.things[thing].wearable) { | |
| deactivate_verb(thing, 'wear'); | |
| } | |
| removeFrom(Game.inventory, thing); | |
| } | |
| function give_hero(thing, sneak) { // if SNEAK is true, put the thing in the inventory, not the PC's hands (i.e. carrying not held) | |
| if(!carried(thing)) { | |
| if(sneak) { | |
| Game.inventory.unshift(thing); | |
| } else { | |
| hold(thing); | |
| } | |
| } | |
| Game.things[thing].location = ''; | |
| activate_verb(thing, 'drop'); | |
| deactivate_verb(thing, 'take'); | |
| if(Game.things[thing].wearable) { | |
| activate_verb(thing, 'wear'); | |
| } | |
| } | |
| function take_away(thing) { | |
| uncarry(thing); | |
| deactivate_verb(thing, 'drop'); | |
| if(Game.things[thing].portable) { | |
| activate_verb(thing, 'take'); | |
| } | |
| } | |
| function put(thing, location) { | |
| //console.log('putting ' + thing + ' in ' + (location ? location : '(nowhere)' )); | |
| if(!Game.things[thing]) { | |
| console.log("Tried to put nonexistent thing '" + thing + "' in " + location); | |
| } | |
| if(!Game.rooms[location] && location != '') { | |
| console.log("Tried to put " + thing + " in nonexistent room '" + location + "'"); | |
| } | |
| take_away(thing); | |
| Game.things[thing].location = location; | |
| deactivate_verb(thing, 'drop'); | |
| if(Game.things[thing].portable) { | |
| activate_verb(thing, 'take'); | |
| } | |
| } | |
| function hide(thing) { | |
| put(thing, ''); | |
| } | |
| function name(thing) { | |
| //return thing; | |
| if(!thing) { return '' }; | |
| if(!Game.things[thing]) { | |
| console.log("tried to get name of nonexistent thing '" + thing + "'"); | |
| } | |
| if(Game.things[thing].name) { | |
| return Game.things[thing].name; | |
| } else { | |
| return replaceAll(thing, '_', ' '); | |
| } | |
| } | |
| function singular(thing) { | |
| if(Game.things[thing].singular) { | |
| return Game.things[thing].singular; | |
| } else { | |
| return name(thing); | |
| } | |
| } | |
| function the_thing(thing, caps) { | |
| if(!Game.things[thing]) { | |
| console.log("Tried to call the_thing() for nonexistent thing '" + thing + "'"); | |
| } | |
| if(Game.things[thing].proper_name) { | |
| return capitalise(name(thing)); | |
| } else { | |
| return (caps ? "The " : "the ") + name(thing); | |
| } | |
| } | |
| function capitalise(str) { | |
| if(str == '') { | |
| return '' | |
| } else { | |
| return str.substring(0,1).toUpperCase() + str.substring(1); | |
| } | |
| } | |
| function location_of(thing) { | |
| if(!Game.things[thing]) { | |
| console.log("tried to get location of nonexistent thing '" + thing + "'"); | |
| } | |
| return Game.things[thing].location; | |
| } | |
| function hero_location() { | |
| if(!Game.rooms[Game.hero_location]) { | |
| console.log("hero is in nonexistent location '" + Game.hero_location + "'"); | |
| } | |
| return Game.rooms[Game.hero_location]; | |
| } | |
| function at(room) { | |
| return Game.hero_location == room; | |
| } | |
| function put_hero(room, suppress_message) { | |
| if(!Game.rooms[room]) { | |
| console.log('tried to put hero at nonexistent location "' + room + '"'); | |
| } else { | |
| //console.log('putting hero at ' + room); | |
| } | |
| Game.hero_location = room; | |
| if(suppress_message) { | |
| Game.suppress_room_message = true; | |
| } | |
| do_room_music(); | |
| } | |
| function held(thing) { | |
| return Game.held == thing; | |
| } | |
| function held_thing() { | |
| return Game.things[Game.held]; | |
| } | |
| function carried(thing) { | |
| if(!Game.things[thing]) { | |
| console.log("Tried to check carried status of nonexistent thing '" + thing + "'"); | |
| } | |
| return (Game.held == thing) || Game.inventory.indexOf(thing) != -1; | |
| } | |
| function worn(thing) { | |
| if(!Game.things[thing]) { | |
| console.log("Tried to check worn status of nonexistent thing '" + thing + "'"); | |
| } | |
| return Game.things[thing].worn; | |
| } | |
| function in_scope(thing) { | |
| return carried(thing) || Game.things[thing].location == Game.hero_location; | |
| } | |
| function visible(thing) { | |
| return carried(thing) || (Game.things[thing].location == Game.hero_location && is_lit(Game.hero_location)); | |
| } | |
| function pronoun(thing, n, caps) { | |
| var pronoun = ''; | |
| if(Game.things[thing].pronouns) { | |
| pronoun = Game.things[thing].pronouns[n]; | |
| } else if(Game.things[thing].is_plural) { | |
| pronoun = ["they", "them", "their", "theirs"][n]; | |
| } else { | |
| pronoun = ["it", "it", "its", "its"][n]; | |
| } | |
| return caps ? capitalise(pronoun) : pronoun; | |
| } | |
| function s(thing) { | |
| if(Game.things[thing].is_plural) { | |
| return ""; | |
| } else { | |
| return "s"; | |
| } | |
| } | |
| function pick(n) { // random number 0 to n-1 | |
| return Math.floor(n * Math.random()); | |
| } | |
| function pickOne(arr) { // random element of an array | |
| return arr[pick(arr.length)]; | |
| } | |
| /* | |
| * Undo | |
| */ | |
| Undo_states = []; | |
| Undo_states.length = 20; // number of UNDO states to keep in memory | |
| clear_undo_stack(); | |
| function clear_undo_stack() { | |
| for(var i=0; i < Undo_states.length; ++i) { | |
| Undo_states[i] = ''; | |
| } | |
| } | |
| function push_undo_state(command) | |
| { | |
| for(var i = Undo_states.length - 1; i > 0; --i) | |
| Undo_states[i] = Undo_states[i - 1]; | |
| Game.undo_message = command; | |
| Undo_states[0] = JSONfn.stringify(Game); | |
| } | |
| function pop_undo_state() | |
| { | |
| var old_state = Undo_states[0]; | |
| for(var i = 0; i < Undo_states.length - 1; ++i) { | |
| Undo_states[i] = Undo_states[i + 1]; | |
| } | |
| Undo_states[Undo_states.length - 1] = ''; | |
| return old_state; | |
| } | |
| function undo() { | |
| say('\n<span class="prompt">> undo</span>'); | |
| var popped_state = pop_undo_state(); | |
| if(popped_state == '') { | |
| say("Can't undo, sorry"); | |
| return; | |
| } | |
| GAME_OVER = false; | |
| Game = JSONfn.parse(popped_state); | |
| do_onholds(); | |
| update_display(); | |
| say('Undone "' + Game.undo_message + '"'); | |
| do_room_music(true); | |
| } | |
| /* | |
| * Load and save games | |
| */ | |
| function save_game() { | |
| if(GAME_OVER) { | |
| // document.getElementById('scroller').innerHTML += ("<br/>You can't save, because the game is over<br/>\ | |
| // Try undoing, restoring or restarting"); | |
| return; | |
| } | |
| var game_name = prompt("Enter a name for your saved game", Game.title); | |
| if(!game_name) { | |
| say("\n<span class=\"prompt\">> save</span>\nCancelled"); | |
| return; | |
| } | |
| game_name = game_name.toUpperCase(); | |
| game_name = game_name.split("'").join(""); | |
| game_name = game_name.split('"').join(""); | |
| game_name = game_name.split('\n').join(""); | |
| game_name = game_name.split('\r').join(""); | |
| game_name = game_name.split('\t').join(""); | |
| game_name = game_name.split('\\').join(""); | |
| if(!game_name.split(" ").join("")) { // spaces are allowed, but not ONLY spaces | |
| say("Please use a more sensible name"); | |
| return; | |
| } | |
| say('\n<span class="prompt">> save ' + game_name + '</span>'); | |
| try { | |
| localStorage.setItem(Game.id + '_' + game_name, JSONfn.stringify(Game)); | |
| say("Game saved as '" + game_name + "'"); | |
| } catch(e) { | |
| say("Couldn't save game, sorry"); | |
| } | |
| } | |
| function load_game(game_name) { | |
| say('\n<span class="prompt">> restore</span>'); | |
| GAME_OVER = false; | |
| Game = JSONfn.parse(localStorage.getItem(Game.id + '_' + game_name)); | |
| $('#saved_games').fadeOut(); | |
| $('#room_description').html(''); | |
| say("Game restored from '" + game_name + "'"); | |
| clear_undo_stack(); | |
| do_onholds(); | |
| update_display(); | |
| do_room_music(true); | |
| } | |
| function delete_game(game_name) { | |
| if(confirm("Really delete game '" + game_name + "'?")) { | |
| localStorage.removeItem(Game.id + '_' + game_name); | |
| $('#saved_games').fadeOut(); | |
| say('\n<span class="prompt">> delete ' + game_name + '</span>'); | |
| say("Saved game '" + game_name + "' deleted"); | |
| } | |
| } | |
| function goto_loader() { | |
| var html = 'Restore saved game<br/><br/>'; | |
| var i=0; | |
| for(var key in localStorage) { | |
| if(key.indexOf(Game.id + '_')==0) { | |
| ++i; | |
| game_name = key.substring(1 + Game.id.length); | |
| // html += '<a onClick="load_game(\'' + game_name + '\')" href="javascript:void(0)">' + game_name + '</a> ' + | |
| // '[<a onClick="delete_game(\'' + game_name + '\')" href="javascript:void(0)">delete</a>]<br/>'; | |
| html += draw_button(game_name, "load_game('" + game_name + "')") + | |
| draw_button("delete", "delete_game('" + game_name + "')") + "<br/>\n"; | |
| } | |
| } | |
| if(!i) { | |
| html += 'No saved games found.<br/>'; | |
| } | |
| //html += '<br/>[<a onClick="remove_loader()" href="javascript:void(0)">cancel</a>]'; | |
| html += "<br/>" + draw_button("cancel", "remove_loader()"); | |
| $('#saved_games').html(html); | |
| $('#saved_games').fadeIn(); | |
| } | |
| function remove_loader() { | |
| $('#saved_games').fadeOut(); | |
| } | |
| /* | |
| * JSONfn utility | |
| * allows an object structure including functions to be stringified/parsed like JSON | |
| * (so that the whole Game object can be stashed in local storage/undo history) | |
| */ | |
| var JSONfn; | |
| if (!JSONfn) { | |
| JSONfn = {}; | |
| } | |
| (function () { | |
| JSONfn.stringify = function(obj) { | |
| return JSON.stringify(obj,function(key, value){ | |
| return (typeof value === 'function' ) ? value.toString() : value; | |
| }); | |
| } | |
| JSONfn.parse = function(str) { | |
| return JSON.parse(str,function(key, value){ | |
| if(typeof value != 'string') return value; | |
| return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value; | |
| }); | |
| } | |
| }()); | |
| /* | |
| * display options | |
| */ | |
| function show_options() { | |
| $('#options').fadeIn(); | |
| } | |
| function hide_options() { | |
| $('#options').fadeOut(); | |
| } | |
| function show_credits() { | |
| $('#credits').fadeIn(); | |
| } | |
| function hide_credits() { | |
| $('#credits').fadeOut(); | |
| } | |
| $(document).ready(function() { | |
| $('#listobj_always').change(function() { | |
| var checked = $('#listobj_always').prop('checked'); | |
| $('#listobj_landscape').prop('checked', !checked); | |
| $('#listobj_never').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_listobj', 'always'); | |
| } | |
| if(checked && !$('#roomdesc_always').prop('checked')) { | |
| $('#roomdesc_always').prop('checked', true); | |
| $('#roomdesc_always').trigger('change'); | |
| } | |
| }); | |
| $('#listobj_landscape').change(function() { | |
| var checked = $('#listobj_landscape').prop('checked'); | |
| $('#listobj_always').prop('checked', !checked); | |
| $('#listobj_never').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_listobj', 'landscape'); | |
| } | |
| if(checked && $('#roomdesc_never').prop('checked')) { | |
| $('#roomdesc_landscape').prop('checked', true); | |
| $('#roomdesc_landscape').trigger('change'); | |
| } | |
| }); | |
| $('#listobj_never').change(function() { | |
| var checked = $('#listobj_never').prop('checked'); | |
| $('#listobj_landscape').prop('checked', !checked); | |
| $('#listobj_always').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_listobj', 'never'); | |
| } | |
| }); | |
| $('#roomdesc_always').change(function() { | |
| var checked = $('#roomdesc_always').prop('checked'); | |
| $('#roomdesc_landscape').prop('checked', !checked); | |
| $('#roomdesc_never').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_roomdesc', 'always'); | |
| } | |
| }); | |
| $('#roomdesc_landscape').change(function() { | |
| var checked = $('#roomdesc_landscape').prop('checked'); | |
| $('#roomdesc_always').prop('checked', !checked); | |
| $('#roomdesc_never').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_roomdesc', 'landscape'); | |
| } | |
| if(checked && $('#listobj_always').prop('checked')) { | |
| $('#listobj_landscape').prop('checked', true); | |
| $('#listobj_landscape').trigger('change'); | |
| } | |
| }); | |
| $('#roomdesc_never').change(function() { | |
| var checked = $('#roomdesc_never').prop('checked'); | |
| $('#roomdesc_landscape').prop('checked', !checked); | |
| $('#roomdesc_always').prop('checked', !checked); | |
| if(checked) { | |
| localStorage.setItem(Game.id + 'options_roomdesc', 'never'); | |
| } | |
| if(checked && !$('#listobj_never').prop('checked')) { | |
| $('#listobj_never').prop('checked', true); | |
| $('#listobj_never').trigger('change'); | |
| } | |
| }); | |
| $('#verbose').change(function() { | |
| var checked = $('#verbose').prop('checked'); | |
| $('#noverbose').prop('checked', !checked); | |
| localStorage.setItem(Game.id + 'option_verbose', checked?1:0); | |
| }); | |
| $('#noverbose').change(function() { | |
| var checked = $('#noverbose').prop('checked'); | |
| $('#verbose').prop('checked', !checked); | |
| localStorage.setItem(Game.id + 'option_verbose', checked?0:1); | |
| }); | |
| $('#typing_sound').change(function() { | |
| var checked = $('#typing_sound').prop('checked'); | |
| if(!checked) { | |
| document.getElementById('typing_audio').pause(); | |
| } | |
| localStorage.setItem(Game.id + 'typing_sound', checked?1:0); | |
| }); | |
| $('#music').change(function() { | |
| var checked = $('#music').prop('checked'); | |
| if(!checked) { | |
| stopAllMusic(); | |
| } else { | |
| playMusic(currentMusic); | |
| } | |
| localStorage.setItem(Game.id + 'music', checked?1:0); | |
| }); | |
| if(!localStorage.getItem(Game.id + 'typing_sound')) { | |
| change_font(); | |
| change_sounds(); | |
| } | |
| }); | |
| function change_font_size(n) { // if n==1, make bigger; if n==-1, make smaller; if n==0, reset | |
| var min_size = 10; | |
| var default_size = 12; | |
| var set_to = default_size; | |
| if(n != 0) { | |
| var font_size_px = parseFloat( $('body').css('font-size') ); | |
| var font_size_pt = Math.round( font_size_px * (72/96) ); | |
| set_to = font_size_pt + n; | |
| } | |
| $('body').css('font-size', set_to + 'pt'); | |
| localStorage.setItem(Game.id + 'options_fontsize', set_to); | |
| update_display(); | |
| } | |
| function change_font() { | |
| var font = $('#font_select').val(); | |
| $('body').css('font-family', font + ', ' + font + '_ie'); | |
| localStorage.setItem(Game.id + 'options_font', font); | |
| update_display(); | |
| } | |
| function change_sounds() { | |
| var musicp = $('#music').prop('checked'); | |
| } | |
| function restore_display_options() { | |
| try { | |
| var font = localStorage.getItem(Game.id + 'options_font'); | |
| var size = localStorage.getItem(Game.id + 'options_fontsize'); | |
| $('body').css({ | |
| 'font-family': font + ', ' + font + '_ie', | |
| 'font-size': size + 'pt' | |
| }); | |
| //$('#font_select option[value="' + font + '"]').prop('selected', 'selected') | |
| $('#font_select').val(font); | |
| // StackOverflow insists that works, but it's not working for me on all browsers, so: | |
| $('#font_select option[val="' + font + '"]').attr('selected', 'selected'); | |
| update_display(); | |
| } catch(e) { | |
| } | |
| } | |
| $(document).ready(function() { | |
| try { | |
| if(localStorage.getItem(Game.id + 'options_font')) { | |
| restore_display_options(); | |
| } | |
| } catch(e) { | |
| // localStorage not working | |
| say("\nCould not access local storage. Saving and restoring games may not work."); | |
| $('body').css({ 'font-family' : 'typewriter', 'font-size' : '12pt' }); | |
| // could not restore settings | |
| } | |
| restore_checkboxes(); | |
| }); | |
| function restore_checkboxes() { | |
| try { | |
| var listobj = localStorage.getItem(Game.id + 'options_listobj'); | |
| if(listobj=='always') { | |
| $('#listobj_always').prop('checked', true); | |
| $('#listobj_always').trigger('change'); | |
| } else if(listobj == 'landscape') { | |
| $('#listobj_landscape').prop('checked', true); | |
| $('#listobj_landscape').trigger('change'); | |
| } else if(listobj == 'never') { | |
| $('#listobj_never').prop('checked', true); | |
| $('#listobj_never').trigger('change'); | |
| } | |
| var roomdesc = localStorage.getItem(Game.id + 'options_roomdesc'); | |
| if(roomdesc=='always') { | |
| $('#roomdesc_always').prop('checked', true); | |
| $('#roomdesc_always').trigger('change'); | |
| } else if(roomdesc == 'landscape') { | |
| $('#roomdesc_landscape').prop('checked', true); | |
| $('#roomdesc_landscape').trigger('change'); | |
| } else if(roomdesc == 'never') { | |
| $('#roomdesc_never').prop('checked', true); | |
| $('#roomdesc_never').trigger('change'); | |
| } | |
| var verbose = localStorage.getItem(Game.id + 'option_verbose')=='1'; | |
| $('#verbose').prop('checked', verbose); | |
| $('#noverbose').prop('checked', !verbose); | |
| if(!localStorage.getItem(Game.id + 'typing_sound')) { | |
| localStorage.setItem(Game.id + 'typing_sound', "1"); | |
| } | |
| if(!localStorage.getItem(Game.id + 'music')) { | |
| localStorage.setItem(Game.id + 'music', "1"); | |
| } | |
| var typingSound = localStorage.getItem(Game.id + 'typing_sound')=='1'; | |
| $('#typing_sound').prop('checked', typingSound); | |
| $('#typing_sound').trigger('change'); | |
| var musicp = localStorage.getItem(Game.id + 'music')=='1'; | |
| $('#music').prop('checked', musicp); | |
| $('#music').trigger('change'); | |
| /* if(localStorage.getItem(Game.id + 'typing_sound') != null) { | |
| var typing_sound = localStorage.getItem(Game.id + 'typing_sound')=='1'; | |
| $('#typing_sound').prop('checked', typing_sound); | |
| var music = localStorage.getItem(Game.id + 'music')=='1'; | |
| $('#music').prop('checked', music); | |
| } */ | |
| } catch(e) { | |
| // could not restore settings | |
| } | |
| } | |
| // detect if landscape | |
| function is_landscape() { | |
| return (window.innerWidth >= window.innerHeight); // >? >=? | |
| //return (window.innerWidth >= 800); | |
| } | |
| var room_description_height = $('#room_description').height(); | |
| var inventory_height = $('#inventory').height(); | |
| function animate_heights() { | |
| animate_room_description_height(); | |
| animate_holding_height(); | |
| animate_inventory_height(); | |
| } | |
| function animate_inventory_height() { | |
| $('#inventory_container').stop().animate({height: $('#inventory').outerHeight()}, 300); | |
| set_heights(); | |
| } | |
| function animate_holding_height() { | |
| $('#holding_container').stop().animate({height: $('#holding').outerHeight()}, 300); | |
| } | |
| function animate_room_description_height() { | |
| $('#room_description_container').stop().animate({height: $('#room_description').outerHeight()}, 300); | |
| } | |
| function set_heights() { // no animation | |
| $('#room_description_container').height($('#room_description').outerHeight()); | |
| $('#holding_container').height($('#holding').outerHeight()); | |
| $('#inventory_container').height($('#inventory').outerHeight()); | |
| } | |
| /* | |
| * User instructions | |
| */ | |
| function instructions() { | |
| dim_old_messages(); | |
| say('\n<span class="prompt">> instructions</span>'); | |
| say("This is a keyboardless text adventure game. You are taking part in an interactive story - \ | |
| I will tell you what is going on in this pane. In the panes " + ( | |
| is_landscape() ? "on the right (or below this one, for portrait screens)" : | |
| "below this one (or to the right, for landscape screens)" | |
| ) + ", I'll tell you where I am, what I can see, and what I\'m carrying. To give me an instruction, \ | |
| click or touch one of the <button type=\"button\">buttons</button> there – either a direction telling \ | |
| me where to go, or an action applying to something I can see. Click an item in the \"I'm carrying\" list \ | |
| to hold that item, which may make more actions available. Have fun, and happy adventuring!"); | |
| } | |
| /* set_heights() doesn't work at first, I don't know why! | |
| */ | |
| $(document).ready(function() { | |
| setTimeout(set_heights, 200); | |
| }); | |
| function join_with_and(words) { | |
| if(!words.length) { | |
| return ''; | |
| } | |
| if(words.length == 1) { | |
| return words[0]; | |
| } | |
| if(words.length == 2) { | |
| return words[0] + ', and ' + words[1]; | |
| } | |
| return words[0] + ', ' + join_with_and(words.slice(1)); | |
| } | |
| $(document).ready(function() { | |
| setTimeout(animate_heights, 500); | |
| //setInterval(set_heights, 10000); | |
| }); | |
Xet Storage Details
- Size:
- 41.7 kB
- Xet hash:
- 846bb45a8a1d9574cfae62d19c21af86339beeaeb40f94bd92667a64c89b248c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.