/* * * VERSIFICATOR 2 * A keyboardless text adventure engine * April 2018 * Copy and distribute freely, preserving this licence. * */ var first_person = false; var box_examine = true; function I(caps) { return first_person ? "I" : (caps ? "You" : "you"); } function Im(caps) { return first_person ? "I'm" : (caps ? "You're" : "you're"); } function me(caps) { return first_person ? (caps ? "Me" : "me") : (caps ? "You" : "you"); } function my(caps) { return first_person ? (caps ? "My" : "my") : (caps ? "Your" : "your"); } function mine(caps) { return first_person ? (caps ? "Mine" : "mine") : (caps ? "Yours" : "yours"); } function myself(caps) { return first_person ? (caps ? "Myself" : "myself") : (caps ? "Yourself" : "yourself"); } 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(); set_heights(); //animate_heights(); } function show_score() { document.getElementById('score').innerHTML = 'Moves: ' + Game.turn_number + ' Score: ' + (Game.dont_show_max_score ? Game.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 += hero_location().description; } else { html += "It's dark."; } html += '
'; var exitOrder = ['north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest', 'fore', 'aft', 'port', 'starboard', 'up', 'down', 'in', 'out']; var exits = []; exitOrder.forEach(function(dir) { if(hero_location().directions[dir]) { //exits.push('' + dir + ''); exits.push(draw_button(dir, "go('" + dir + "')", false, true, true)); } /* else push an unavailable exit marker of same width? */ }); /* for(var key in hero_location().directions) { exits.push('' + key + ''); } */ if(exits.length) { html += 'Exits: ' + exits.join(' ') + '
'; } if(hero_location().active_verbs && hero_location().active_verbs.length) { if(hero_location().actions_name) { html += hero_location().actions_name + ': '; } else { html += "Actions: " } html += list_verbs(Game.hero_location, true) + '
'; } /* document.getElementById('also_see').innerHTML = html; show_can_also_see(); } function show_can_also_see() { var html = '
'; 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 + ' [enter]'); } } */ // 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 = draw_noun(key) + ' ' + 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 = draw_noun(key) + ' ' + 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 = draw_noun(key) + ' ' + 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 = draw_noun(key) + ' ' + list_verbs(key); things_here.push(this_thing); } } if(lit && things_here.length) { html += I(true) + ' can also see:\n\n'; } html += '
'; 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(!hero_location().description) { console.log("room '" + Game.hero_location + "' has no description"); } if(!Game.suppress_room_message) { var txt = hero_location().description; if(hero_location().directions) { var dirs = Object.keys(hero_location().directions); if(dirs.length==1) { txt += ' An exit leads ' + dirs[0] + '.'; } else if(dirs.length > 1) { txt += ' Exits lead ' + join_with_and(dirs, true) + '.'; } } if(listobj_in_scroller()) { var things = things_here(Game.hero_location); if(things != "") { txt += (" You can also see " + things + "."); } } say(txt); /* 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(true) + " can also see: '); } */ } } else { say("It's dark."); } }; INV_ICONS = false; function draw_button(display_text, script, is_examine, is_direction, is_inventory, is_held) { // is_examine and is_direction shouldn't both be true return ''; } function draw_noun(thing) { var desc = Game.things[thing].description; var img = ''; if(INV_ICONS && Game.things[thing].icon) { img = ' ' } if(Game.things[thing].status.length) { desc += ' (' + Game.things[thing].status.join(', ') + ')'; } if(box_examine && !Game.things[thing].no_examine) { return draw_button(img + desc, "do_verb('examine','" + thing + "')", true); } else { return img + desc; } } function show_holding() { var html = ''; if(Game.held) { //var itemHtml = Game.things[Game.held].description + ' ' + list_verbs(Game.held); html = Im(true) + " holding:\n'; } // also show worn things in here var worn = []; for(var thing in Game.things) { if(Game.things[thing].worn) { worn.push(draw_noun(thing) + ' ' + list_verbs(thing)); } } if(worn.length) { if(html) { html += '
\n' } html += Im(true) + " wearing:\n"; } document.getElementById('holding').innerHTML = html; } function show_inventory() { var html = Im(true) + " " /* + (Game.held ? "also " : "") */ + "carrying:\n"; //'; document.getElementById('inventory').innerHTML = html; } function hold(thing) { if(Game.held) { unhold(true); } //removeFrom(Game.inventory, thing); if(!carried(thing)) { Game.inventory.unshift(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, dontSmarten) { if(GAME_OVER) { return; } if(typeof txt === 'function') { say(txt()); return; } $('#scroller_content').append('

' + replaceAll((dontSmarten ? txt : smarten(txt)), '\n', '
') + '

'); //document.getElementById('scroller').innerHTML += '
' + replaceAll(txt, '\n', '
') + '
'; //document.getElementById('scroller').scrollTop = document.getElementById('scroller').scrollHeight; // animated scroll var scrTop = $('#scroller').prop("scrollHeight"); $('#scroller').stop().animate({scrollTop: scrTop}, 500); } function say_if_lit(txt, darktxt) { if(is_lit(Game.hero_location)) { say(txt); } else if(darktxt) { say(darktxt); } } function say_if(cond, txt, txtIfFalse) { if(cond) { say(txt); } else if(txtIfFalse) { say(txtIfFalse); } } function say_where(thing, txt, txtIfFalse) { if(in_scope(thing)) { say(txt); } else if(txtIfFalse) { say(txtIfFalse); } } function sayOne(arr) { say(pickOne(arr)); } function smarten(txt) { // smarten quotes and turn -- to m-dashes var ltPos = txt.indexOf('<'); if(ltPos == -1) { return smartenThisBit(txt); } else { var bitBeforeLt = txt.substring(0, ltPos); var gtPos = txt.indexOf('>'); if(gtPos==-1 || gtPos < ltPos) { // angles don't match so give up return txt; } var tag = txt.substring(ltPos, 1 + gtPos); var bitAfterGt = txt.substring(1+gtPos); return smartenThisBit(bitBeforeLt) + tag + smarten(bitAfterGt); } } function smartenThisBit(a) { a = a.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018"); // opening singles a = a.replace(/'/g, "\u2019"); // closing singles & apostrophes a = a.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c"); // opening doubles a = a.replace(/"/g, "\u201d"); // closing doubles a = a.replace(/--/g, "\u2014"); // em-dashes return a; }; 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 = first_person ? "I am dead" : "You are dead"; } say("*** " + deathMessage + " ***"); //say('(restart game)'); //say(draw_button("restart game", "confirm_restart()") + // ' \ // ', true); for(var fuse in Game.fuses) { stop_fuse(fuse); } $('#room_description').slideUp(); delete_stored_game(); 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> ' + direction + ''); var instead = false; if(Game.before) { Game.before.forEach(function(before) { var thisInstead = before('depart'); if(thisInstead) instead = true; }); } if(!instead) { if(hero_location().before) { if(hero_location().before.depart) { instead = hero_location().before.depart(direction); } } } if(!instead && !GAME_OVER) { if(Game.suppress_ok) { delete Game.suppress_ok; } else { say('OK'); } Game.hero_location = 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(hero_location().after && hero_location().after.arrive) { 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 verbose() { return $('#verbose').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> ' + phrasing + ''); 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(hero_location().before[verb]) { instead = 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(hero_location().after) { for(var after_verb in hero_location().after) { if(after_verb == verb) { 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(); if(!GAME_OVER) { autosave_game(); } } function do_alwayses() { if(!GAME_OVER) { autosave_game(); } if(Game.always) { for(var func in Game.always) { Game.always[func](); } } if(hero_location().always) { 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 constantly(func) { On_hold.push(func); Game.always.push(func); } 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].time = -1; Game.fuses[fuse].explode(); } } 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('' + display_verb(verb) + ''); 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) //console.log('Game[things_or_rooms][thing].active_verbs : things_or_rooms is ' + things_or_rooms + '; thing is ' + thing); //console.log(things_or_rooms + ': ' + thing); Game[things_or_rooms][thing].active_verbs.forEach(function(verb) { // n.b. if there's an error here, a thing was probably given active_verbs: "foo" instead of active_verbs: ["foo"] if(!Verbs[verb]) { // snippets.push('' + display_verb(verb) + ''); 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 has_active_verb(thing, verb) { return Game.things[thing].active_verbs.indexOf(verb) != -1; }; 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(typeof Game.things[thing].status === 'undefined') { Game.things[thing].status = []; } if(typeof Game.things[thing].active_verbs === 'string') { console.log('Watch out - Game.things.' + thing + '.active_verbs is a string!'); } if(typeof Game.things[thing].status === 'string') { console.log('Watch out - Game.things.' + thing + '.status is a string!'); } 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("" + Game.title.toUpperCase() + (Game.author ? ", by " + Game.author + "" : "")); } //say('For instructions, touch: instructions\n'); say("For instructions, click: \n", true); 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() { $('#room_description').slideDown(); GAME_OVER = false; Game = JSONfn.parse(INITIAL_GAME); document.getElementById('scroller_content').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.hero_blind) { return false; } 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'); deactivate_verb(thing, 'remove'); Game.things[thing].worn = false; } 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); Game.things[thing].location = ''; 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; if(Game.things[thing].wearable) { deactivate_verb(thing, 'wear'); deactivate_verb(thing, 'remove'); Game.things[thing].worn = false; } deactivate_verb(thing, 'drop'); if(Game.things[thing].portable) { activate_verb(thing, 'take'); } } function bring(thing) { put(thing, Game.hero_location); } function hide(thing) { put(thing, ''); } function add_status(thing, status) { if(Game.things[thing].status.indexOf(status)==-1) { Game.things[thing].status.push(status); } update_display(); } function remove_status(thing, status) { var i = Game.things[thing].status.indexOf(status); if(i==-1) { return false; } Game.things[thing].status.splice(i); return true; } function clear_status(thing) { Game.things[thing].status = []; update_display(); } function has_status(thing, status) { if(!Game.things[thing]) { console.log('tried to get status of nonexistent thing "' + thing + '"??'); } return Game.things[thing].status.indexOf(status) != -1; } 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; } } function held(thing) { return Game.held == thing; } function a_thing(thing) { if(!Game.things[thing]) { console.log('tried to get indefinite form of nonexistent thing "' + thing + '"???'); return 'a strange object'; } var desc = Game.things[thing].description; // not name! if(Game.things[thing].indefinite) { return Game.things[thing].indefinite; } else if(Game.things[thing].proper_name) { return desc; } else if(Game.things[thing].is_plural) { return 'some ' + desc; } else if('aeiou'.indexOf(desc.charAt(0).toLowerCase())!=-1) { return 'an ' + desc; } else { return 'a ' + desc; } } function things_here(room) { var things = []; for(var i in Game.things) { if(Game.things[i].is_alive && !Game.things[i].list_last && location_of(i)==room && !Game.things[i].dont_list_in_scroller) { things.push(a_thing(i)); } } for(var i in Game.things) { if(!Game.things[i].is_alive && !Game.things[i].portable && !Game.things[i].list_last && location_of(i)==room && !Game.things[i].dont_list_in_scroller) { things.push(a_thing(i)); } } for(var i in Game.things) { if(!Game.things[i].is_alive && Game.things[i].portable && !Game.things[i].list_last && location_of(i)==room && !Game.things[i].dont_list_in_scroller) { things.push(a_thing(i)); } } for(var i in Game.things) { if(Game.things[i].list_last && location_of(i)==room && !Game.things[i].dont_list_in_scroller) { things.push(a_thing(i)); } } return join_with_and(things); } 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 if(typeof arr == 'string') { console.error("watch out - pickOne was called with a string argument!"); } 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); autosave_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() { $('#room_description').slideDown(); say('\n> undo'); var popped_state = pop_undo_state(); if(popped_state == '') { say("Can't undo, sorry"); return; } GAME_OVER = false; Game = JSONfn.parse(popped_state); autosave_game(); do_onholds(); update_display(); say('Undone "' + Game.undo_message + '"'); } /* * Load and save games */ function save_game() { if(GAME_OVER) { // document.getElementById('scroller').innerHTML += ("
You can't save, because the game is over
\ // Try undoing, restoring or restarting"); return; } var game_name = prompt("Enter a name for your saved game", Game.title); if(!game_name) { say("\n> save\nCancelled"); return; } store_game(game_name) } function store_game(game_name, suppress_message) { 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((!suppress_message && game_name=='AUTOSAVE') || !game_name.split(" ").join("")) { // spaces are allowed, but not ONLY spaces say_if(!suppress_message, "Game not saved. Please use a more sensible name."); return; } say_if(!suppress_message, '\n> save ' + game_name + ''); Game.timestamp = new Date().getTime(); // FUTURE SELF: watch out for continuing autosaved games from before an update that broke savefiles. You're welcome! try { localStorage.setItem(Game.id + '_' + game_name, JSONfn.stringify(Game)); say_if(!suppress_message, "Game saved as '" + game_name + "'"); } catch(e) { say_if(!suppress_message, "Couldn't save game, sorry"); if(suppress_message) { console.log('couldn\'t autosave'); } } } function load_game(game_name, suppress_message) { say_if(!suppress_message, '\n> restore'); GAME_OVER = false; Game = JSONfn.parse(localStorage.getItem(Game.id + '_' + game_name)); $('#saved_games').fadeOut(); $('#room_description').html(''); say_if(!suppress_message, "Game restored from '" + game_name + "'"); clear_undo_stack(); do_onholds(); if(suppress_message) { $('#scroller_content').html(''); say('Game restored from previous session.\n '); } describe_room_in_scroller(); update_display(); } function delete_game(game_name, suppress_message) { if(suppress_message || confirm("Really delete game '" + game_name + "'?")) { localStorage.removeItem(Game.id + '_' + game_name); $('#saved_games').fadeOut(); say_if(!suppress_message, '\n> delete ' + game_name + ''); say_if(!suppress_message, "Saved game '" + game_name + "' deleted"); } } function continue_game() { load_game('AUTOSAVE', true); } function autosave_game() { //say('autosaved'); store_game('AUTOSAVE', true); //console.log('autosaved game'); } function delete_stored_game() { delete_game('AUTOSAVE', true); } function goto_loader() { $('#room_description').slideDown(); var html = 'Restore saved game

'; var i=0; for(var key in localStorage) { if(key.indexOf(Game.id + '_')==0) { ++i; game_name = key.substring(1 + Game.id.length); if(game_name != 'AUTOSAVE') { // html += '' + game_name + ' ' + // '[delete]
'; html += draw_button(game_name, "load_game('" + game_name + "')") + draw_button("delete", "delete_game('" + game_name + "')") + "
\n"; } } } if(!i) { html += 'No saved games found.
'; } //html += '
[cancel]'; html += "
" + 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); }); $('#plain_colours').change(function() { var checked = $('#plain_colours').prop('checked'); localStorage.setItem(Game.id + 'option_plain_colours', checked?0:1); set_plain_colours(checked); }); }); 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(); } BLACK_AND_WHITE = { color: '#000000', 'background-color': '#ffffff', }; NOT_PLAIN = { color: '', 'background-color': '', } function set_plain_colours(plainp) { /* * DEFERRED * if(plainp) { //$('body').css(BLACK_AND_WHITE); $('#scroller').css(BLACK_AND_WHITE); $('#room_description').css(BLACK_AND_WHITE); $('#inventory').css(BLACK_AND_WHITE); $('#holding').css(BLACK_AND_WHITE); $('#options').css(BLACK_AND_WHITE); } else { $('#scroller').css(NOT_PLAIN); $('#room_description').css(NOT_PLAIN); $('#inventory').css(NOT_PLAIN); $('#holding').css(NOT_PLAIN); $('#options').css(NOT_PLAIN); } * */ } 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 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); var use_plain = localStorage.getItem(Game.id + 'option_plain_colours'); if(use_plain) { $('#plain_colours').prop('checked', use_plain); $('#plain_colours').trigger('change'); } } 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> instructions'); 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 pane below, I'll tell you " + ( first_person ? "where I am, what I can see, and what I\'m carrying." : "where you are, what you can see, and what you're carrying." ) + " To give me an instruction, \ click or touch one of the there – either a direction telling \ me where to go, or an action applying to something " + I() + " can see. Click on the name of an item in a \ to examine the item for a longer description. \ Click an item in the \"" + Im(true) + " carrying\" list to hold that item, \ which may make more actions available. Have fun, and happy adventuring!", true); } /* set_heights() doesn't work at first, I don't know why! */ $(document).ready(function() { setTimeout(set_heights, 200); }); function join_with_and(words, noOxfordComma) { if(!words.length) { return ''; } if(words.length == 1) { return words[0]; } if(words.length == 2) { return words[0] + (!noOxfordComma ? ',' : '') + ' and ' + words[1]; } return words[0] + ', ' + join_with_and(words.slice(1), noOxfordComma); } $(document).ready(function() { //setTimeout(animate_heights, 500); //setInterval(set_heights, 10000); if(localStorage && localStorage[Game.id + "_AUTOSAVE"]) { $('#continue_button').removeAttr('disabled'); } else { $('#continue_button').attr('disabled', 'disabled'); } //$('#continue_button').prop('disabled', !localStorage[Game.id + "currentgame"]); });