import { RollStats } from "./rollstats"
import { Outcomes } from "./asphodice"
import palette from "google-palette"
import { Chart } from "chart.js"
import "bootstrap";
// TODO: more descriptive name
interface ResultPropertyOptions {
rollstats: RollStats,
diceClass: string,
diceVariant: string,
numDice: number,
}
/**
* Provide properties for displaying roll results
*
* - rollstats is an instance of a RollStatsClass
* - diceClass is the shortname of a dice class (eg "aphodice")
* - diceVariant is a string/number combo to disambiguate (eg "c8" for successCutOff = 8)
* - numDice is the number of dice being rolled at a particular time (eg 4; NB negative numbers indicate randomised dice number though not used at present)
*
*/
class ResultProperties {
rollstats: RollStats;
diceClass: string;
diceVariant: string;
numDice: number;
constructor(rpOptions: ResultPropertyOptions) {
this.rollstats = rpOptions.rollstats;
this.diceClass = rpOptions.diceClass;
this.diceVariant = rpOptions.diceVariant;
this.numDice = rpOptions.numDice;
}
/**
* Provide unique itemId for use in DOM elements, eg "asphodice-c6-d4"
* for use in, say:
*/
itemId(): string {
return `${this.diceClass}-${this.diceVariant}-${this.numDice}`;
}
/**
* Provide 'variant class', intended use is for result card visibility group toggling
*/
variantClass(): string {
return `${this.diceClass}-${this.diceVariant}`;
}
}
let barChartOptions = { scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
},
legend: {
display: false,
}
}
function hexColours(numColours: number): Array {
return palette(["cb-Set1", "tol-dv"], numColours).map( function (hex: string) {
return `#${String(hex)}`; })
}
/**
* Make a table with headings
*
* TODO: boolean for striped
*/
function makeTableSkeleton(tableId: string, headings: Array) {
let output = `
`;
for (let head of headings) {
output += `
${head}
`;
}
output += "
";
return output;
}
/**
* Remap true to "rerolled" and false to "Not rerolled"
*/
function mapRerolledKeys(Keys: Array): Array {
return Keys.map((k) => {return (k == "true") ? "Rerolled" : "Not Rerolled"});
}
function buildData(): string {
let output = "";
return output;
}
function buildCharts(): string {
let output = "";
return output;
}
/**
* h2 wrapper
*/
function bigTitle(text:string): string {
return `
`;
}
/**
* Report on rerolls
* ie:
* - make chart
* - make number boxes
*/
function rerollReport(resultProperties: ResultProperties): string {
// TODO: this mixes structure and content, probably a good idea to separate it out
let rerollsChartId = `rerollsChart-${resultProperties.itemId()}`;
// Column structure
let output = `
`;
// Heading
output += subTitle("Rerolls");
// Preamble (ie it looks super confusing)
//output += "
This is super confusing because the colours are swapped on the legend and the chart, then the data below swaps back.
`;
return output;
}
/**
* Do the charts after we've added them to the canvas
*
* The more javascripty way of doing this would be a callback or a custom event (TODO?)
*/
function generateCharts(resultProperties: ResultProperties): void {
let itemId = resultProperties.itemId();
let rerollsChartId = `rerollsChart-${itemId}`;
let outcomesChartId = `outcomesChart-${itemId}`;
let balanceChartId = `balanceChart-${itemId}`;
/*
* Rerolls
*/
let rerollsCanvas: any = $(`#${rerollsChartId}`);
let rerollsChart = new Chart(rerollsCanvas, {
type: "doughnut",
data: {
labels: mapRerolledKeys(Object.keys(resultProperties.rollstats.rerollCounts)),
datasets: [{
label: "Reroll Counts",
data: Object.values(resultProperties.rollstats.rerollCounts),
backgroundColor: ["#d1e7dd", "#fff3cd"],
//backgroundColor: hexColours(Object.values(rollstats.balanceCounts).length),
}],
},
options: {
title: {
text: "Rerolls Chart",
display: false,
},
legend: {
reverse: true,
}
}
});
/*
* Outcomes
*/
// sort so we have failure before success
let oc = resultProperties.rollstats.outcomeCounts;
let okeys = Object.keys(oc).sort();
let ovalues = [];
for (let i = 0; i < okeys.length; i++) {
ovalues.push(oc[okeys[i] as Outcomes]);
}
let outcomesCanvas: any = $(`#${outcomesChartId}`);
let outcomesChart = new Chart(outcomesCanvas, {
type: "bar",
data: {
labels: okeys,
datasets: [{
label: "Outcome Counts",
data: ovalues,
backgroundColor: hexColours(Object.values(resultProperties.rollstats.outcomeCounts).length),
}],
},
options: barChartOptions,
});
/**
* Outcome Balances
*/
let bc = resultProperties.rollstats.balanceCounts;
let keys = Object.keys(bc);
keys.sort(function(a: string, b: string){return Number(a) - Number(b)});
// sort values too
let values = [];
for (let i = 0; i < keys.length; i++) {
values.push(bc[keys[i]]);
}
let balanceCanvas: any = $(`#${balanceChartId}`);
let balanceChart = new Chart(balanceCanvas, {
type: "bar",
data: {
labels: keys,
datasets: [{
label: "Outcome Balance Counts",
data: values,
backgroundColor: hexColours(Object.values(resultProperties.rollstats.balanceCounts).length),
}],
},
options: barChartOptions,
});
}
/**
* Generate tables after we've inserted them into the DOM,
*
* Also should be callback/event-driven. (TODO?)
*/
function generateTables(resultProperties: ResultProperties): void {
let itemId = resultProperties.itemId();
/*
* Outcomes
*/
let outcomesTableId = `outcomesTable-${itemId}`;
let oc = resultProperties.rollstats.outcomeCounts;
let okeys = Object.keys(oc).sort();
for (var i = 0; i < okeys.length; i++) {
let tb = $(`#${outcomesTableId}`).find("tbody");
let outcome: Outcomes = okeys[i] as Outcomes;
let outcomeCount = resultProperties.rollstats.outcomeCounts[outcome];
let outcomePercent: string = "";
if (outcomeCount) {
// Needed to avoid TS2532:
outcomePercent = (outcomeCount / resultProperties.rollstats.numRolls * 100).toFixed(2);
}
tb.append(`
${outcome}
${outcomeCount}
${outcomePercent}
`);
}
/*
* Outcome Balance Counts
*/
let balanceTableId = `balanceTable-${itemId}`;
let bc = resultProperties.rollstats.balanceCounts;
let keys = Object.keys(bc);
keys.sort(function(a: string, b: string){return Number(a) - Number(b)});
for (var i = 0; i < keys.length; i++) {
let tb = $(`#${balanceTableId}`).find("tbody");
tb.append(`
`);
}
}
/**
* For pretty-printing. See https://stackoverflow.com/a/2901298
*/
function numberWithCommas(x:number): string {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
/**
* Give some info on the dice used to generate these results
*/
function describeRolls(resultsProperties: ResultProperties): string {
let output = subTitle("Description");
output += `
There were ${resultsProperties.numDice} Asphodice rolled ${numberWithCommas(resultsProperties.rollstats.numRolls)} times.
`;
return output;
}
/**
* Set up results card that contains one set of RollStats results
*/
function resultsCard(resultsProperties: ResultProperties): JQuery{
//
is the general results div to append results to
// TODO: include class shortname in id (?)
let cardId = `results-${resultsProperties.itemId()}`;
$("#results").append(`
`);
let resultsCard = $("#results").find(`#${cardId}`);
resultsCard.append(`
`);
let resultsCardBody: JQuery = $(`#${cardId}`).find("div.card-body");
return resultsCardBody;
}
/**
* Set up results 'header' - class name (TODO) and number of dice rolled
*/
function resultsHeader(resultProperties: ResultProperties): string {
// Use flexbox (d-flex) for LHS/RHS justification
let resultsBodyId = `resultsBody-${resultProperties.itemId()}`;
let resultsTitle = `
`;
return resultsTitle;
}
/**
* Write out results 'body'. Set up in columns, the 'skeletons' of charts and tables are set up first,
* to be filled in afterwards
*/
function resultsBody(resultProperties: ResultProperties): string {
let resultsBodyId = `resultsBody-${resultProperties.itemId()}`;
let resultsBody = `
`;
return resultsBody;
}
/**
* Put the results together - card, head, body → fill charts and tables
*/
function addResults(resultProperties: ResultProperties): void {
let resultsCardBody = resultsCard(resultProperties);
resultsCardBody.append(resultsHeader(resultProperties));
resultsCardBody.append(resultsBody(resultProperties));
// Post-DOM-Construction generation
//
// For tables: we need this as the way that tables were implemented
// we .find() the
//
// For charts: the Chart() generation function expects to passed an extant
//