/*
*
* 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';
things_here.forEach(function(description) {
html += '\n' + description + ' ';
})
html += ' \n';
}
html += 'wait
';
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: " + things_here.join(' ') + ' ');
}
*/
}
} 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 '' + display_text + ' ';
}
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" + draw_noun(Game.held) + ' ' + list_verbs(Game.held) + ' ';
}
// 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"; //";
var carried = [];
Game.inventory.forEach(function(thing) {
if(/* !held(thing) && */ !Game.things[thing].worn) {
//var itemHtml = Game.things[thing].description + ' ' + list_verbs(thing);
/*
var icon = '';
if(Game.things[thing].icon) {
icon = ' ';
}
var itemHtml;
if(INV_ICONS && Game.things[thing].icon) {
itemHtml = draw_button(icon,
"hold('" + thing + "')", false, false, true
);
} else {
// if(icon != '') { icon += ' ' }
itemHtml = draw_button(icon + Game.things[thing].description, "hold('" + thing + "')", false, false, true);
} */
itemHtml = draw_button(Game.things[thing].description +
(Game.things[thing].status.length ? ' (' + Game.things[thing].status.join(', ') + ')': ''),
"hold('" + thing + "')",
false, false, true, held(thing));
carried.push(itemHtml);
}
});
if(carried.length) {
html += carried.join(' '); //(' \n');
} else {
html = ''; // html += 'nothing';
}
//html += ' ';
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(a) { // smarten quotes and turn -- to m-dashes
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()") +
// ' restore a saved game \
// undo ', 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: instructions \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 buttons 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 \
square-cornered button 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!");
}
/* 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"]);
});