// Scorecard.js
(function () {
/*global adventurejs A*/
"use strict";
/**
* @class adventurejs.Scorecard
* @ajsinternal
* @param {Game} game A reference to the game instance.
* @ajsnavheading FrameworkReference
* @summary Manages score for a {@link adventurejs.Game|Game} instance.
* @classdesc
* <p>
* <strong>Scorecard</strong> is a repository for Game score
* options. Scorecard is created automatically
* by {@link adventurejs.Game|Game}. This is an internal class
* that authors should not need to construct. However,
* authors can set scoring options from their game file as
* shown below, and call score updates from custom scripts.
* </p>
* <h3 class="examples">Example:</h3>
* <pre class="display"><code class="language-javascript">var MyGame = new adventurejs.Game( "MyGame", "GameDisplay" );
* MyGame.scorecard.set({
* score_events: {
* "unlock door": { points: 1, complete: false, bonus: false, message: '', recorded: false },
* "unlock chest": { points: 1, complete: false, bonus: false, recorded: message: '', false },
* "drink potion": { points: 1, complete: false, bonus: false, recorded: message: '', false },
* }
* });
* </code></pre>
*/
class Scorecard {
constructor(game) {
this.game = game;
this.game.world._scorecard = {
initialize: {
complete: true,
points: 0,
recorded: true,
message: "",
bonus: false,
},
};
this.score = 0;
this.newscore = 0;
this.diff = 0;
this.aggregate_updates = true;
this.score_message = "";
this.score_format = {};
this.aggregate_updates = false;
// set scorecard to listen for end of turn,
// then check to see if score has changed,
// and if it has, print score updates
this.game.reactor.addEventListener("inputParseComplete", function (e) {
this.game.scorecard.updateScore();
});
}
/**
* Create a new event.
* @method adventurejs.Scorecard#createEvent
* @returns {String}
*/
createEvent() {
let event = {
points: 0,
complete: false,
bonus: false,
recorded: false,
message: "",
};
return JSON.stringify(event);
}
/**
* Mark the selected event as complete and update the score.
* @method adventurejs.Scorecard#completeEvent
* @param {String} event A string matching an event key.
* @returns {Boolean}
*/
completeEvent(event) {
if (!this.game.world._scorecard[event]) return false;
this.game.world._scorecard[event].complete = true;
return true;
}
/**
* Get the current score.
* @method adventurejs.Scorecard#updateScore
*/
updateScore() {
let score = 0;
let total = 0;
let msg = "";
for (var prop in this.game.world._scorecard) {
//console.warn('getScore prop:',prop);
if (!this.game.world._scorecard[prop].bonus) {
total += this.game.world._scorecard[prop].points;
}
if (this.game.world._scorecard[prop].complete) {
score += this.game.world._scorecard[prop].points;
}
}
this.total = total;
this.newscore = score;
msg += this.aggregate_updates
? this.aggregateUpdates()
: this.stackUpdates();
this.score = score;
this.game.display.setScore(this.formatScore(score, total));
if (msg) this.game.print(msg);
return true;
}
/**
* Print score updates in the aggregate.
* @method adventurejs.Scorecard#aggregateUpdates
*/
aggregateUpdates() {
let msg = "";
if (this.newscore !== this.score) {
let pole = this.newscore > this.score ? "up" : "down";
let diff = this.newscore - this.score;
let s = Math.abs(diff) > 1 ? "s" : "";
this.diff = diff;
// the aggregate uses a single message setting
msg = this.score_message
? A.getSAF.call(this.game, this.score_message, this)
: `[ ** $(Our) score went ${pole} by ${diff} point${s} ** ]`;
msg = `<span class="score_msg ${pole}">${msg}</span>`;
}
return msg;
}
/**
* Print score updates in a stack.
* @method adventurejs.Scorecard#stackUpdates
*/
stackUpdates() {
let msg = "";
for (let prop in this.game.world._scorecard) {
if (
this.game.world._scorecard[prop].complete &&
!this.game.world._scorecard[prop].recorded
) {
let eventmsg = "";
let diff = this.game.world._scorecard[prop].points;
let pole = diff > 0 ? "up" : "down";
let s = Math.abs(diff) > 1 ? "s" : "";
this.diff = diff;
this.game.world._scorecard[prop].recorded = true;
if (this.game.world._scorecard[prop].message) {
eventmsg = A.getSAF.call(
this.game,
this.game.world._scorecard[prop].message,
this
);
} else {
eventmsg = this.score_message
? A.getSAF.call(this.game, this.score_message, this)
: `[ ** $(Our) score went ${pole} by ${diff} point${s} ** ]`;
}
msg += `<span class="score_msg ${pole}">${eventmsg}</span>`;
}
}
return msg;
}
/**
* Format the score/total before printing it to display.
* @method adventurejs.Scorecard#setScore
*/
formatScore(score, total) {
// @TODO add a more robust check for score_format
if (Object.keys(this.score_format).length)
return A.getSAF.call(this.game, this.score_format, this);
else return `${score}/${total}`;
}
/**
* Provides a chainable shortcut method for setting a number of properties on the instance.
* @method adventurejs.Scorecard#set
* @param {Object} props A generic object containing properties to copy to the instance.
* @returns {adventurejs.Scorecard} Returns the instance the method is called on (useful for chaining calls.)
* @chainable
*/
set(scorecard) {
if (scorecard.score_events) {
for (var event in scorecard.score_events) {
this.game.world._scorecard[event] = JSON.parse(this.createEvent());
// we accept a number in the form of
// "open window": 1,
if ("number" === typeof scorecard.score_events[event]) {
this.game.world._scorecard[event].points =
scorecard.score_events[event];
continue;
}
// or we accept an object with any subset of fields
// "take andy": { points: 1 },
for (var prop in scorecard.score_events[event]) {
this.game.world._scorecard[event][prop] = JSON.parse(
JSON.stringify(scorecard.score_events[event][prop])
);
}
}
}
if ("undefined" !== scorecard.aggregate_updates)
this.aggregate_updates = scorecard.aggregate_updates;
if (scorecard.score_message) this.score_message = scorecard.score_message;
if (scorecard.score_format) this.score_format = scorecard.score_format;
return this;
}
}
adventurejs.Scorecard = Scorecard;
})();