536 lines
19 KiB
JavaScript
536 lines
19 KiB
JavaScript
var gamebook = {
|
|
'player' : {
|
|
'started' : false,
|
|
'currentSection' : -1,
|
|
'collections' : {},
|
|
counters : {},
|
|
|
|
'collect' : function(type, name) {
|
|
if (type in this.collections) {
|
|
return;
|
|
}
|
|
this.collections[type] = {
|
|
'name' : name,
|
|
'contents' : [],
|
|
'dropped' : {},
|
|
'add' : function(what) {
|
|
if (this.contents.indexOf(what) === -1 &&
|
|
!(what in this.dropped)) {
|
|
this.contents.push(what);
|
|
this.contents.sort();
|
|
}
|
|
},
|
|
'drop' : function(what) {
|
|
var i = this.contents.indexOf(what);
|
|
if (i >= 0) {
|
|
this.contents.splice(i, 1);
|
|
this.dropped[what] = true;
|
|
}
|
|
},
|
|
'has' : function(what) {
|
|
return this.contents.indexOf(what) >= 0;
|
|
}
|
|
};
|
|
gamebook.addCollectionView(type, name);
|
|
},
|
|
|
|
count : function(type, name) {
|
|
if (type in this.counters) {
|
|
return;
|
|
}
|
|
this.counters[type] = {
|
|
inited : false,
|
|
name : name,
|
|
value : 0,
|
|
minValue : null, //no minimum set
|
|
inc : function(amount) {
|
|
this.value += amount;
|
|
this.inited = true;
|
|
},
|
|
dec : function(amount) {
|
|
this.value -= amount;
|
|
this.ensureNotBelowMin();
|
|
this.inited = true;
|
|
},
|
|
set : function(value) {
|
|
this.value = value;
|
|
this.ensureNotBelowMin();
|
|
this.inited = true;
|
|
},
|
|
init : function(value) {
|
|
if (!this.inited) {
|
|
this.value = value;
|
|
this.inited = true;
|
|
}
|
|
},
|
|
min : function(limit) {
|
|
this.minValue = limit;
|
|
this.ensureNotBelowMin();
|
|
},
|
|
ensureNotBelowMin : function() {
|
|
if (this.minValue !== null && this.value < this.minValue) {
|
|
this.value = this.minValue;
|
|
}
|
|
}
|
|
};
|
|
gamebook.addCounterView(type, name);
|
|
},
|
|
|
|
'add' : function(type, what) {
|
|
this.collections[type].add(what);
|
|
gamebook.updateCollectionsView();
|
|
},
|
|
|
|
'drop' : function(type, what) {
|
|
this.collections[type].drop(what);
|
|
gamebook.updateCollectionsView();
|
|
},
|
|
|
|
'has' : function(type, what) {
|
|
return this.collections[type].has(what);
|
|
},
|
|
|
|
inc : function(type, amount) {
|
|
this.counters[type].inc(amount);
|
|
gamebook.updateCountersView();
|
|
},
|
|
|
|
dec : function(type, amount) {
|
|
this.counters[type].dec(amount);
|
|
gamebook.updateCountersView();
|
|
},
|
|
|
|
min : function(type, limit) {
|
|
this.counters[type].min(limit);
|
|
gamebook.updateCountersView();
|
|
},
|
|
|
|
set : function(type, amount) {
|
|
this.counters[type].set(amount);
|
|
gamebook.updateCountersView();
|
|
},
|
|
|
|
init : function(type, amount) {
|
|
this.counters[type].init(amount);
|
|
gamebook.updateCountersView();
|
|
},
|
|
|
|
'hasMoreThan' : function(type, amount) {
|
|
return this.counters[type].value > amount;
|
|
},
|
|
|
|
'hasLessThan' : function(type, amount) {
|
|
return this.counters[type].value < amount;
|
|
},
|
|
|
|
'getState' : function() {
|
|
return JSON.stringify({
|
|
'collections' : this.collections,
|
|
'currentSection' : this.currentSection,
|
|
'counters' : this.counters
|
|
});
|
|
},
|
|
|
|
'setState' : function(state) {
|
|
var parsedState = JSON.parse(state);
|
|
this.currentSection = parsedState.currentSection;
|
|
for (var c in parsedState.collections) {
|
|
var collection = parsedState.collections[c];
|
|
if (!(c in this.collections)) {
|
|
this.collect(c, collection.name);
|
|
} else {
|
|
this.collections[c].name = collection.name;
|
|
}
|
|
this.collections[c].contents = collection.contents;
|
|
this.collections[c].dropped = collection.dropped;
|
|
}
|
|
for (var ct in parsedState.counters) {
|
|
var counter = parsedState.counters[ct];
|
|
if (ct in this.counters) {
|
|
this.counters[ct].name = counter.name;
|
|
} else {
|
|
this.count(ct, counter.name);
|
|
}
|
|
this.counters[ct].minValue = counter.minValue;
|
|
this.counters[ct].value = counter.value;
|
|
}
|
|
}
|
|
},
|
|
|
|
'sections' : {},
|
|
|
|
'turnToFunctions' : {},
|
|
|
|
'addSection' : function(nr, element) {
|
|
var section = {'element' : element, 'nr' : nr};
|
|
this.sections[nr] = section;
|
|
},
|
|
|
|
'turnTo' : function(nr) {
|
|
if (!gamebook.player.started) {
|
|
gamebook.start();
|
|
}
|
|
if (!(nr in this.sections)) {
|
|
throw new Exception("Can not turn to non-existing section " +
|
|
nr + ".");
|
|
}
|
|
this.displaySection(nr);
|
|
},
|
|
|
|
prepare : function() {
|
|
this.addClassToClass('section', 'nodisplay');
|
|
this.runActionsInIntroSections();
|
|
},
|
|
|
|
'start' : function() {
|
|
this.hideIntroSections();
|
|
this.addClassToClass('startlink', 'nodisplay');
|
|
this.addClassToClass('resumelink', 'nodisplay');
|
|
gamebook.player.started = true;
|
|
},
|
|
|
|
'displaySection' : function(nr) {
|
|
if (this.player.currentSection > 0) {
|
|
var section = this.sections[this.player.currentSection];
|
|
section.element.style.display = 'none';
|
|
}
|
|
this.player.currentSection = nr;
|
|
this.saveGame();
|
|
var e = this.sections[nr].element;
|
|
this.runActions(e.getElementsByClassName('sectiontext')[0]);
|
|
e.style.display = 'block';
|
|
},
|
|
|
|
//FIXME move out from gamebook object
|
|
'saveGame' : function() {
|
|
if (typeof window !== 'undefined' && 'localStorage' in window) {
|
|
window.localStorage.setItem('savedGamebookPlayer',
|
|
this.player.getState());
|
|
}
|
|
},
|
|
|
|
//FIXME move out from gamebook object
|
|
'hasSavedGame' : function() {
|
|
if (typeof window !== 'undefined' && 'localStorage' in window) {
|
|
return window.localStorage.getItem('savedGamebookPlayer');
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
//FIXME move out from gamebook object
|
|
'loadGame' : function() {
|
|
if (typeof window !== 'undefined' && 'localStorage' in window) {
|
|
var state = window.localStorage.getItem('savedGamebookPlayer');
|
|
this.player.setState(state);
|
|
this.turnTo(this.player.currentSection);
|
|
this.updateCollectionsView();
|
|
} else {
|
|
//FIXME some kind of error, because we should never get here
|
|
}
|
|
},
|
|
|
|
'hideIntroSections' : function() {
|
|
this.addClassToClass('introsection', 'nodisplay');
|
|
this.removeClassFromClass('displayintrolink', 'nodisplay');
|
|
this.addClassToClass('hideintrolink', 'nodisplay');
|
|
},
|
|
|
|
'showIntroSections' : function() {
|
|
this.runActionsInIntroSections();
|
|
this.removeClassFromClass('introsection', 'nodisplay');
|
|
this.addClassToClass('displayintrolink', 'nodisplay');
|
|
this.removeClassFromClass('hideintrolink', 'nodisplay');
|
|
document.body.scrollIntoView();
|
|
},
|
|
|
|
'runActionsInIntroSections' : function() {
|
|
Array.prototype.forEach.call(
|
|
document.getElementsByClassName('introsectionbody'),
|
|
gamebook.runActions);
|
|
},
|
|
|
|
'addClassToClass' : function(className, addClass) {
|
|
Array.prototype.forEach.call(
|
|
document.getElementsByClassName(className),
|
|
function(e) {
|
|
e.classList.add(addClass);
|
|
});
|
|
},
|
|
|
|
'removeClassFromClass' : function(className, removeClass) {
|
|
Array.prototype.forEach.call(
|
|
document.getElementsByClassName(className),
|
|
function(e) {
|
|
e.classList.remove(removeClass);
|
|
});
|
|
},
|
|
|
|
'runActions' : function(e) {
|
|
var enableNextLink = true;
|
|
var hasXorScope = false;
|
|
var hasAutoScope = false;
|
|
var xorEnableNext = false;
|
|
var lastCanHaveCost = null;
|
|
var autoDisableAllRemaining = (
|
|
gamebook.player.started &&
|
|
e.classList.contains('introsectionbody'));
|
|
Array.prototype.forEach.call(e.childNodes, function(c) {
|
|
if (!c.classList) {
|
|
return;
|
|
}
|
|
// FIXME yes, this must be split up
|
|
if (c.classList.contains('sectionref')) {
|
|
if (enableNextLink && !autoDisableAllRemaining) {
|
|
gamebook.enableLink(c);
|
|
if (hasAutoScope) {
|
|
autoDisableAllRemaining = true;
|
|
}
|
|
} else {
|
|
gamebook.disableLink(c);
|
|
}
|
|
lastCanHaveCost = c;
|
|
enableNextLink = !(hasXorScope && !xorEnableNext);
|
|
hasAutoScope = false;
|
|
hasXorScope = false;
|
|
} else if (autoDisableAllRemaining) {
|
|
return; // no further actions will be taken after enabled auto
|
|
} else if (c.classList.contains('collect')) {
|
|
gamebook.player.collect(c.dataset.type, c.dataset.name);
|
|
} else if (c.classList.contains('add')) {
|
|
gamebook.player.add(c.dataset.type, c.dataset.what);
|
|
} else if (c.classList.contains('drop')) {
|
|
gamebook.player.drop(c.dataset.type, c.dataset.what);
|
|
} else if (c.classList.contains('count')) {
|
|
gamebook.player.count(c.dataset.type, c.dataset.name);
|
|
} else if (c.classList.contains('set')) {
|
|
gamebook.player.set(c.dataset.type,
|
|
parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('init')) {
|
|
gamebook.player.init(c.dataset.type,
|
|
parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('inc')) {
|
|
gamebook.player.inc(c.dataset.type,
|
|
parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('dec')) {
|
|
gamebook.player.dec(c.dataset.type,
|
|
parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('min')) {
|
|
gamebook.player.min(c.dataset.type,
|
|
parseInt(c.dataset.limit, 10));
|
|
} else if (c.classList.contains('has')) {
|
|
enableNextLink = gamebook.player.has(c.dataset.type,
|
|
c.dataset.what);
|
|
} else if (c.classList.contains('morethan')) {
|
|
enableNextLink = gamebook.player.hasMoreThan(
|
|
c.dataset.type, parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('lessthan')) {
|
|
enableNextLink = gamebook.player.hasLessThan(
|
|
c.dataset.type, parseInt(c.dataset.amount, 10));
|
|
} else if (c.classList.contains('atleast')) {
|
|
enableNextLink = gamebook.player.hasMoreThan(
|
|
c.dataset.type, parseInt(c.dataset.amount, 10) - 1);
|
|
} else if (c.classList.contains('hasnot')) {
|
|
enableNextLink = !gamebook.player.has(c.dataset.type,
|
|
c.dataset.what);
|
|
} else if (c.classList.contains('xor')) {
|
|
hasXorScope = true;
|
|
xorEnableNext = !enableNextLink;
|
|
} else if (c.classList.contains('auto')) {
|
|
hasAutoScope = true;
|
|
} else if (c.classList.contains('random')) {
|
|
c.addEventListener('click',
|
|
gamebook.enableRandomLinkAfter);
|
|
c.classList.add("enabledlink");
|
|
c.classList.remove("disabledlink");
|
|
autoDisableAllRemaining = true;
|
|
} else if (c.classList.contains('found')) {
|
|
lastCanHaveCost = c;
|
|
c.addEventListener('click', gamebook.takeFound);
|
|
} else if (c.classList.contains('cost')) {
|
|
gamebook.addCost(c, lastCanHaveCost);
|
|
} else if (c.classList.contains('trade')) {
|
|
gamebook.addTrade(c, lastCanHaveCost);
|
|
}
|
|
|
|
});
|
|
},
|
|
|
|
addCost : function(c, e) {
|
|
var cost = parseInt(c.dataset.amount, 10);
|
|
var counter = gamebook.player.counters[c.dataset.type];
|
|
if (counter.value - cost >= counter.minValue) {
|
|
e.classList.add("enabledlink");
|
|
e.classList.remove("disabledlink");
|
|
e.dataset.cost = c.dataset.amount;
|
|
e.dataset.costtype = c.dataset.type;
|
|
} else {
|
|
e.classList.add("disabledlink");
|
|
e.classList.remove("enabledlink");
|
|
}
|
|
},
|
|
|
|
addTrade : function(c, e) {
|
|
var what = c.dataset.what;
|
|
var tradetype = c.dataset.type;
|
|
if (gamebook.player.has(tradetype, what)) {
|
|
e.classList.add("enabledlink");
|
|
e.classList.remove("disabledlink");
|
|
e.dataset.trade = what;
|
|
e.dataset.tradetype = tradetype;
|
|
} else {
|
|
e.classList.add("disabledlink");
|
|
e.classList.remove("enabledlink");
|
|
}
|
|
},
|
|
|
|
'enableLink' : function(e) {
|
|
e.addEventListener('click', gamebook.getTurnToFunction(e.dataset.ref));
|
|
e.classList.add("enabledlink");
|
|
e.classList.remove("disabledlink");
|
|
},
|
|
|
|
'disableLink' : function(e) {
|
|
e.removeEventListener('click',
|
|
gamebook.getTurnToFunction(e.dataset.ref));
|
|
e.classList.remove("enabledlink");
|
|
e.classList.add("disabledlink");
|
|
},
|
|
|
|
'enableRandomLinkAfter' : function(event) {
|
|
this.classList.remove("enabledlink");
|
|
this.classList.add("disabledlink");
|
|
var links = [];
|
|
var e = this.nextSibling;
|
|
while (e) {
|
|
if (e.classList && e.classList.contains('sectionref')) {
|
|
links.push(e);
|
|
}
|
|
e = e.nextSibling;
|
|
}
|
|
if (links.length > 0) {
|
|
var selected = links[Math.floor(Math.random()*links.length)];
|
|
gamebook.enableLink(selected);
|
|
} else {
|
|
console.log("Random with nothing to select?");
|
|
}
|
|
event.preventDefault();
|
|
},
|
|
|
|
'addCollectionView' : function(type, name) {
|
|
this.addView('collection', type, name);
|
|
},
|
|
|
|
addCounterView : function(type, name) {
|
|
this.addView('counter', type, name);
|
|
},
|
|
|
|
addView : function(view, type, name) {
|
|
var ce = document.getElementById(view + 's');
|
|
var template = document.getElementById(view + 'Template');
|
|
var e = template.cloneNode(true);
|
|
e.className = view;
|
|
e.getElementsByClassName(view + 'heading')[0].innerHTML = name;
|
|
e.dataset.type = type;
|
|
ce.appendChild(e);
|
|
},
|
|
|
|
'updateCollectionsView' : function() {
|
|
var ce = document.getElementById('collections');
|
|
Array.prototype.forEach.call(ce.childNodes, function(c) {
|
|
if (c.className === 'collection') {
|
|
var type = c.dataset.type;
|
|
var collection = gamebook.player.collections[type];
|
|
var cc = c.getElementsByClassName('collectioncontents')[0];
|
|
cc.innerHTML = collection.contents.join(', ');
|
|
}
|
|
});
|
|
},
|
|
|
|
'updateCountersView' : function() {
|
|
var ce = document.getElementById('counters');
|
|
Array.prototype.forEach.call(ce.childNodes, function(c) {
|
|
if (c.className === 'counter') {
|
|
var type = c.dataset.type;
|
|
var counter = gamebook.player.counters[type];
|
|
var cc = c.getElementsByClassName('countercontents')[0];
|
|
cc.innerHTML = counter.value;
|
|
}
|
|
});
|
|
},
|
|
|
|
'getTurnToFunction' : function(nr) {
|
|
if (nr in this.turnToFunctions) {
|
|
return this.turnToFunctions[nr];
|
|
} else {
|
|
var f = function () {
|
|
if (gamebook.payPrice(this)) {
|
|
gamebook.turnTo(nr);
|
|
}
|
|
};
|
|
this.turnToFunctions[nr] = f;
|
|
return f;
|
|
}
|
|
},
|
|
|
|
'takeFound' : function(evt) {
|
|
evt.preventDefault();
|
|
if (!gamebook.payPrice(this)) {
|
|
return false;
|
|
}
|
|
this.classList.remove("enabledlink");
|
|
this.classList.add("disabledlink");
|
|
var what = this.dataset.what;
|
|
var type = this.dataset.type;
|
|
gamebook.player.add(type, what);
|
|
gamebook.disableLinksNowTooExpensive();
|
|
},
|
|
|
|
'payPrice' : function(e) {
|
|
var cost, counter, trade, tradetype;
|
|
if ('cost' in e.dataset && 'costtype' in e.dataset) {
|
|
cost = parseInt(e.dataset.cost, 10);
|
|
counter = gamebook.player.counters[e.dataset.costtype];
|
|
if (counter.value - cost < counter.minValue) {
|
|
return false;
|
|
}
|
|
}
|
|
if ('trade' in e.dataset && 'tradetype' in e.dataset) {
|
|
trade = e.dataset.trade;
|
|
tradetype = e.dataset.tradetype;
|
|
if (!gamebook.player.has(tradetype, trade)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (cost) {
|
|
counter.dec(cost);
|
|
gamebook.updateCountersView();
|
|
}
|
|
if (trade) {
|
|
gamebook.player.drop(tradetype, trade);
|
|
}
|
|
return true;
|
|
},
|
|
|
|
'disableLinksNowTooExpensive' : function() {
|
|
var e = this.sections[this.player.currentSection].element;
|
|
var cs = e.getElementsByClassName('sectiontext')[0].childNodes;
|
|
Array.prototype.forEach.call(cs, function(c) {
|
|
if (c.dataset && 'cost' in c.dataset && 'costtype' in c.dataset) {
|
|
var cost = parseInt(c.dataset.cost, 10);
|
|
var counter = gamebook.player.counters[c.dataset.costtype];
|
|
if (counter.value - cost < counter.minValue) {
|
|
c.classList.remove("enabledlink");
|
|
c.classList.add("disabledlink");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
// little hack to make easy to test from node.js
|
|
if (typeof exports !== 'undefined') {
|
|
exports.gamebook = gamebook;
|
|
}
|