update
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"jsxBracketSameLine": true
|
"jsxBracketSameLine": true,
|
||||||
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,25 @@ import RulesetCollection from "../ObjectCollections/RulesetCollection";
|
|||||||
|
|
||||||
export const getRuleset: RequestHandler = async (req, res) => {
|
export const getRuleset: RequestHandler = async (req, res) => {
|
||||||
const ruleset = await RulesetCollection().read(req.params.id);
|
const ruleset = await RulesetCollection().read(req.params.id);
|
||||||
res.json(ruleset.getSchemaJSON());
|
res.json(ruleset.getSchema());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addRuleset: RequestHandler = async (req, res) => {
|
||||||
|
const submission = req.body;
|
||||||
|
if (validateRulesetSchema(submission)) {
|
||||||
|
const savedRuleset = await RulesetCollection().create(submission);
|
||||||
|
res.send({result: "success", newRuleset: savedRuleset})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.status(400).send({result: "failure", message: "Invalid ruleset submission format."});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllRulesets: RequestHandler = async (req, res) => {
|
||||||
|
const rulesets = await RulesetCollection().getAllRulesets();
|
||||||
|
res.send({rulesets: rulesets.map(ruleset => ruleset.getSchema())});
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateRulesetSchema(object: any): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import KadiUserCollection from "../ObjectCollections/KadiUserCollection";
|
import KadiUserCollection from "../ObjectCollections/KadiUserCollection";
|
||||||
import PlayerCollection from "../ObjectCollections/PlayerCollection";
|
import PlayerCollection from "../ObjectCollections/PlayerCollection";
|
||||||
import Player from "../Objects/Player";
|
import Player from "../Objects/Player";
|
||||||
import {RequestHandler} from "express";
|
import { RequestHandler } from "express";
|
||||||
import ScoreCalculator from "../Objects/ScoreCalculator";
|
import ScoreCalculator from "../Objects/ScoreCalculator";
|
||||||
import KadiUser from "../Objects/KadiUser";
|
import KadiUser from "../Objects/KadiUser";
|
||||||
import {CellFlag} from "../enums";
|
import { CellFlag } from "../enums";
|
||||||
import RulesetCollection from "../ObjectCollections/RulesetCollection";
|
import RulesetCollection from "../ObjectCollections/RulesetCollection";
|
||||||
import Ruleset from "../Objects/Ruleset";
|
import Ruleset from "../Objects/Ruleset";
|
||||||
import {OutcomeType} from "../Objects/DefaultStatsMongoData";
|
import { OutcomeType } from "../Objects/DefaultStatsMongoData";
|
||||||
|
|
||||||
interface GameSubmission {
|
interface GameSubmission {
|
||||||
ruleset: string;
|
ruleset: string;
|
||||||
@@ -17,7 +17,7 @@ interface GameSubmission {
|
|||||||
|
|
||||||
export interface ProcessedGameSubmission {
|
export interface ProcessedGameSubmission {
|
||||||
ruleset: string;
|
ruleset: string;
|
||||||
players: {id: string, nick: string}[];
|
players: { id: string; nick: string }[];
|
||||||
results: ScoredResultsWithOutcome[];
|
results: ScoredResultsWithOutcome[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,16 +32,18 @@ enum ResultType {
|
|||||||
loser,
|
loser,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScoredResults = {score: number, results: PlayerGameResult};
|
type ScoredResults = { score: number; results: PlayerGameResult };
|
||||||
export type ScoredResultsWithOutcome = {score: number, results: PlayerGameResult & {outcome: OutcomeType}};
|
export type ScoredResultsWithOutcome = {
|
||||||
|
score: number;
|
||||||
|
results: PlayerGameResult & { outcome: OutcomeType };
|
||||||
|
};
|
||||||
|
|
||||||
export const listGames: RequestHandler = async (req, res) => {
|
export const listGames: RequestHandler = async (req, res) => {
|
||||||
const user = req.user as KadiUser;
|
const user = req.user as KadiUser;
|
||||||
const gamesList = await KadiUserCollection().getSavedGamesForUser(user);
|
const gamesList = await KadiUserCollection().getSavedGamesForUser(user);
|
||||||
if (gamesList) {
|
if (gamesList) {
|
||||||
res.json({ games: gamesList });
|
res.json({ games: gamesList });
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -55,7 +57,10 @@ export const saveGame: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
const rulesetUsed = await RulesetCollection().read(submission.ruleset);
|
const rulesetUsed = await RulesetCollection().read(submission.ruleset);
|
||||||
const scoredResultsWithOutcomes = await processStats(rulesetUsed, submission.results, user);
|
const scoredResultsWithOutcomes = await processStats(rulesetUsed, submission.results, user);
|
||||||
const newGame = await KadiUserCollection().addGameForUser(user, {...submission, results: scoredResultsWithOutcomes});
|
const newGame = await KadiUserCollection().addGameForUser(user, {
|
||||||
|
...submission,
|
||||||
|
results: scoredResultsWithOutcomes,
|
||||||
|
});
|
||||||
res.send({ message: "Game submitted successfully!", newGame: newGame });
|
res.send({ message: "Game submitted successfully!", newGame: newGame });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,11 +69,13 @@ export const getStats: RequestHandler = async (req, res) => {
|
|||||||
const stats = await KadiUserCollection().getAllStatsForUser(user);
|
const stats = await KadiUserCollection().getAllStatsForUser(user);
|
||||||
if (stats) {
|
if (stats) {
|
||||||
res.json({
|
res.json({
|
||||||
pStats: stats.pStats.map(pStats => ({...pStats, stats: pStats.stats.getData()})),
|
pStats: stats.pStats.map((pStats) => ({
|
||||||
accStats: stats.accStats.getData()
|
...pStats,
|
||||||
|
stats: pStats.stats.getData(),
|
||||||
|
})),
|
||||||
|
accStats: stats.accStats.getData(),
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -78,20 +85,30 @@ async function addNewGuests(submission: GameSubmission, user: KadiUser): Promise
|
|||||||
for (const playerDetails of submission.players) {
|
for (const playerDetails of submission.players) {
|
||||||
const isNewPlayer = playerDetails.id === playerDetails.nick;
|
const isNewPlayer = playerDetails.id === playerDetails.nick;
|
||||||
if (isNewPlayer) {
|
if (isNewPlayer) {
|
||||||
const newGuest: Player = await KadiUserCollection().addGuestForUser(user, playerDetails.nick);
|
const newGuest: Player = await KadiUserCollection().addGuestForUser(
|
||||||
|
user,
|
||||||
|
playerDetails.nick
|
||||||
|
);
|
||||||
newGuestIds.push(newGuest);
|
newGuestIds.push(newGuest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newGuestIds;
|
return newGuestIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillOutSubmissionWithNewIds(submission: GameSubmission, newGuestList: Player[]): GameSubmission {
|
function fillOutSubmissionWithNewIds(
|
||||||
|
submission: GameSubmission,
|
||||||
|
newGuestList: Player[]
|
||||||
|
): GameSubmission {
|
||||||
for (const newGuest of newGuestList) {
|
for (const newGuest of newGuestList) {
|
||||||
const gameResultsFromNewGuest = submission.results.find((result) => result.playerId === newGuest.getNick());
|
const gameResultsFromNewGuest = submission.results.find(
|
||||||
|
(result) => result.playerId === newGuest.getNick()
|
||||||
|
);
|
||||||
if (gameResultsFromNewGuest) {
|
if (gameResultsFromNewGuest) {
|
||||||
gameResultsFromNewGuest.playerId = newGuest.getId().toString();
|
gameResultsFromNewGuest.playerId = newGuest.getId().toString();
|
||||||
}
|
}
|
||||||
const playerEntryForNewGuest = submission.players.find((player) => player.id === newGuest.getNick());
|
const playerEntryForNewGuest = submission.players.find(
|
||||||
|
(player) => player.id === newGuest.getNick()
|
||||||
|
);
|
||||||
if (playerEntryForNewGuest) {
|
if (playerEntryForNewGuest) {
|
||||||
playerEntryForNewGuest.id = newGuest.getId().toString();
|
playerEntryForNewGuest.id = newGuest.getId().toString();
|
||||||
}
|
}
|
||||||
@@ -99,35 +116,45 @@ function fillOutSubmissionWithNewIds(submission: GameSubmission, newGuestList: P
|
|||||||
return submission;
|
return submission;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processStats(ruleset: Ruleset, results: PlayerGameResult[], account: KadiUser): Promise<ScoredResultsWithOutcome[]> {
|
async function processStats(
|
||||||
|
ruleset: Ruleset,
|
||||||
|
results: PlayerGameResult[],
|
||||||
|
account: KadiUser
|
||||||
|
): Promise<ScoredResultsWithOutcome[]> {
|
||||||
const calc = new ScoreCalculator(ruleset);
|
const calc = new ScoreCalculator(ruleset);
|
||||||
let playerScoreList: ScoredResults[] = [];
|
let playerScoreList: ScoredResults[] = [];
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
calc.hydrateWithJSON(result);
|
calc.hydrateWithJSON(result);
|
||||||
playerScoreList.push({
|
playerScoreList.push({
|
||||||
score: calc.getTotal(),
|
score: calc.getTotal(),
|
||||||
results: result
|
results: result,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const playerScoreListWithOutcomes = updateScoreListWithOutcomes(playerScoreList);
|
const playerScoreListWithOutcomes = updateScoreListWithOutcomes(playerScoreList);
|
||||||
await updateStatsForIndividualPlayers(playerScoreListWithOutcomes, ruleset);
|
await updateStatsForIndividualPlayers(playerScoreListWithOutcomes, ruleset);
|
||||||
const gameResults = playerScoreListWithOutcomes.map(scoredResults => scoredResults.results);
|
const gameResults = playerScoreListWithOutcomes.map((scoredResults) => scoredResults.results);
|
||||||
await KadiUserCollection().updateAccountStats(account.getId(), gameResults, ruleset);
|
await KadiUserCollection().updateAccountStats(account.getId(), gameResults, ruleset);
|
||||||
return playerScoreListWithOutcomes;
|
return playerScoreListWithOutcomes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScoreListWithOutcomes(scoreResultsList: ScoredResults[]): ScoredResultsWithOutcome[] {
|
function updateScoreListWithOutcomes(
|
||||||
|
scoreResultsList: ScoredResults[]
|
||||||
|
): ScoredResultsWithOutcome[] {
|
||||||
scoreResultsList = sortDescendingByScore(scoreResultsList);
|
scoreResultsList = sortDescendingByScore(scoreResultsList);
|
||||||
const playerScoreListWithOutcomes: ScoredResultsWithOutcome[] = scoreResultsList.map(scoredResults => {
|
const playerScoreListWithOutcomes: ScoredResultsWithOutcome[] = scoreResultsList.map(
|
||||||
const newResults = {...scoredResults.results, outcome: OutcomeType.loss};
|
(scoredResults) => {
|
||||||
return {...scoredResults, results: newResults};
|
const newResults = {
|
||||||
});
|
...scoredResults.results,
|
||||||
|
outcome: OutcomeType.loss,
|
||||||
|
};
|
||||||
|
return { ...scoredResults, results: newResults };
|
||||||
|
}
|
||||||
|
);
|
||||||
let runnerUpsStart: number;
|
let runnerUpsStart: number;
|
||||||
if (playerScoreListWithOutcomes[0].score !== playerScoreListWithOutcomes[1].score) {
|
if (playerScoreListWithOutcomes[0].score !== playerScoreListWithOutcomes[1].score) {
|
||||||
playerScoreListWithOutcomes[0].results.outcome = OutcomeType.win;
|
playerScoreListWithOutcomes[0].results.outcome = OutcomeType.win;
|
||||||
runnerUpsStart = 1;
|
runnerUpsStart = 1;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
runnerUpsStart = updateScoreListWithDraws(playerScoreListWithOutcomes);
|
runnerUpsStart = updateScoreListWithDraws(playerScoreListWithOutcomes);
|
||||||
}
|
}
|
||||||
const losersStart = updateScoreListWithRunnerUps(playerScoreListWithOutcomes, runnerUpsStart);
|
const losersStart = updateScoreListWithRunnerUps(playerScoreListWithOutcomes, runnerUpsStart);
|
||||||
@@ -139,28 +166,32 @@ function updateScoreListWithDraws(scoreResultsList: ScoredResultsWithOutcome[]):
|
|||||||
for (let i = 0; i < scoreResultsList.length; i++) {
|
for (let i = 0; i < scoreResultsList.length; i++) {
|
||||||
if (scoreResultsList[i].score === scoreResultsList[0].score) {
|
if (scoreResultsList[i].score === scoreResultsList[0].score) {
|
||||||
scoreResultsList[i].results.outcome = OutcomeType.draw;
|
scoreResultsList[i].results.outcome = OutcomeType.draw;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scoreResultsList.length;
|
return scoreResultsList.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScoreListWithRunnerUps(scoreResultsList: ScoredResultsWithOutcome[], runnerUpsStartIndex: number): number {
|
function updateScoreListWithRunnerUps(
|
||||||
|
scoreResultsList: ScoredResultsWithOutcome[],
|
||||||
|
runnerUpsStartIndex: number
|
||||||
|
): number {
|
||||||
scoreResultsList[runnerUpsStartIndex].results.outcome = OutcomeType.runnerUp;
|
scoreResultsList[runnerUpsStartIndex].results.outcome = OutcomeType.runnerUp;
|
||||||
for (let i = runnerUpsStartIndex + 1; i < scoreResultsList.length; i++) {
|
for (let i = runnerUpsStartIndex + 1; i < scoreResultsList.length; i++) {
|
||||||
if (scoreResultsList[i].score === scoreResultsList[i - 1].score) {
|
if (scoreResultsList[i].score === scoreResultsList[i - 1].score) {
|
||||||
scoreResultsList[i].results.outcome = OutcomeType.runnerUp;
|
scoreResultsList[i].results.outcome = OutcomeType.runnerUp;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return scoreResultsList.length;
|
return scoreResultsList.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScoreListWithLosses(scoreResultsList: ScoredResultsWithOutcome[], losersStartIndex: number) {
|
function updateScoreListWithLosses(
|
||||||
|
scoreResultsList: ScoredResultsWithOutcome[],
|
||||||
|
losersStartIndex: number
|
||||||
|
) {
|
||||||
for (let i = losersStartIndex; i < scoreResultsList.length; i++) {
|
for (let i = losersStartIndex; i < scoreResultsList.length; i++) {
|
||||||
scoreResultsList[i].results.outcome = OutcomeType.loss;
|
scoreResultsList[i].results.outcome = OutcomeType.loss;
|
||||||
}
|
}
|
||||||
@@ -170,11 +201,18 @@ function sortDescendingByScore(playerScoreList: ScoredResults[]) {
|
|||||||
return playerScoreList.sort((a, b) => b.score - a.score);
|
return playerScoreList.sort((a, b) => b.score - a.score);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateStatsForIndividualPlayers(playerScoreListWithOutcomes: ScoredResultsWithOutcome[], rulesetUsed: Ruleset): Promise<void> {
|
async function updateStatsForIndividualPlayers(
|
||||||
|
playerScoreListWithOutcomes: ScoredResultsWithOutcome[],
|
||||||
|
rulesetUsed: Ruleset
|
||||||
|
): Promise<void> {
|
||||||
for (const scoredResults of playerScoreListWithOutcomes) {
|
for (const scoredResults of playerScoreListWithOutcomes) {
|
||||||
await PlayerCollection().updateStatsForPlayer(
|
await PlayerCollection().updateStatsForPlayer(
|
||||||
scoredResults.results.playerId,
|
scoredResults.results.playerId,
|
||||||
{...scoredResults.results, outcome: scoredResults.results.outcome},
|
{
|
||||||
rulesetUsed);
|
...scoredResults.results,
|
||||||
|
outcome: scoredResults.results.outcome,
|
||||||
|
},
|
||||||
|
rulesetUsed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
import {CredentialsTakenError} from "../errors";
|
import { CredentialsTakenError } from "../errors";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import {SupportedLang} from "../enums";
|
import { SupportedLang } from "../enums";
|
||||||
import {AccountStatsMongoData, defaultAccountStatsMongoData, OutcomeType, PlayerGameResults} from "../Objects/DefaultStatsMongoData";
|
import {
|
||||||
import {getMongoObjectCollection} from "../database";
|
AccountStatsMongoData,
|
||||||
import KadiUser, {LoginDetails} from "../Objects/KadiUser";
|
defaultAccountStatsMongoData,
|
||||||
import {ActiveRecordId, OrId} from "../Objects/ActiveRecord";
|
OutcomeType,
|
||||||
|
PlayerGameResults,
|
||||||
|
} from "../Objects/DefaultStatsMongoData";
|
||||||
|
import { getMongoObjectCollection } from "../database";
|
||||||
|
import KadiUser, { LoginDetails } from "../Objects/KadiUser";
|
||||||
|
import { ActiveRecordId, OrId } from "../Objects/ActiveRecord";
|
||||||
import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
|
import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
|
||||||
import SavedGameCollection from "./SavedGameCollection";
|
import SavedGameCollection from "./SavedGameCollection";
|
||||||
import SavedGame from "../Objects/SavedGame";
|
import SavedGame from "../Objects/SavedGame";
|
||||||
import PlayerCollection from "../ObjectCollections/PlayerCollection";
|
import PlayerCollection from "../ObjectCollections/PlayerCollection";
|
||||||
import Player from "../Objects/Player";
|
import Player from "../Objects/Player";
|
||||||
import {ProcessedGameSubmission} from "../Controllers/statsController";
|
import { ProcessedGameSubmission } from "../Controllers/statsController";
|
||||||
import Ruleset from "../Objects/Ruleset";
|
import Ruleset from "../Objects/Ruleset";
|
||||||
import RulesetCollection from "./RulesetCollection";
|
import RulesetCollection from "./RulesetCollection";
|
||||||
import AccountStats from "../Objects/AccountStats";
|
import AccountStats from "../Objects/AccountStats";
|
||||||
@@ -29,6 +34,15 @@ export interface KadiUserMongoData {
|
|||||||
savedGames: ActiveRecordId[];
|
savedGames: ActiveRecordId[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StatsListing {
|
||||||
|
accStats: AccountStats;
|
||||||
|
pStats: {
|
||||||
|
nick: string;
|
||||||
|
playerId: ActiveRecordId;
|
||||||
|
stats: PlayerStats;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData> {
|
class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData> {
|
||||||
private static instance?: KadiUserCollection;
|
private static instance?: KadiUserCollection;
|
||||||
private constructor() {
|
private constructor() {
|
||||||
@@ -43,16 +57,11 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.mongoDbClientCollection = getMongoObjectCollection("users")
|
this.mongoDbClientCollection = getMongoObjectCollection("users");
|
||||||
}
|
}
|
||||||
|
|
||||||
private kadiUserFrom(data: KadiUserMongoData): KadiUser {
|
private kadiUserFrom(data: KadiUserMongoData): KadiUser {
|
||||||
return new KadiUser(
|
return new KadiUser(data.id, data.username, data.email, data.password, data.lang);
|
||||||
data.id,
|
|
||||||
data.username,
|
|
||||||
data.email,
|
|
||||||
data.password,
|
|
||||||
data.lang);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(id: string): Promise<KadiUser> {
|
async read(id: string): Promise<KadiUser> {
|
||||||
@@ -64,8 +73,7 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
const foundUser = await this.mongoFindByAttribute("email", emailQuery);
|
const foundUser = await this.mongoFindByAttribute("email", emailQuery);
|
||||||
if (foundUser) {
|
if (foundUser) {
|
||||||
return this.kadiUserFrom(foundUser);
|
return this.kadiUserFrom(foundUser);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,9 +83,8 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
const emailTaken = await this.userWithEmailExists(loginDetails.email);
|
const emailTaken = await this.userWithEmailExists(loginDetails.email);
|
||||||
if (usernameTaken || emailTaken) {
|
if (usernameTaken || emailTaken) {
|
||||||
throw new CredentialsTakenError(usernameTaken, emailTaken);
|
throw new CredentialsTakenError(usernameTaken, emailTaken);
|
||||||
}
|
} else {
|
||||||
else {
|
return this.addNewUser({ ...loginDetails });
|
||||||
return this.addNewUser({...loginDetails})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,16 +122,21 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
async addGuestForUser(userOrId: OrId<KadiUser>, newGuestNick: string): Promise<Player> {
|
async addGuestForUser(userOrId: OrId<KadiUser>, newGuestNick: string): Promise<Player> {
|
||||||
const newGuest = await PlayerCollection().create(newGuestNick);
|
const newGuest = await PlayerCollection().create(newGuestNick);
|
||||||
await this.mongoDbClientCollection!.findOneAndUpdate(
|
await this.mongoDbClientCollection!.findOneAndUpdate(
|
||||||
{_id: this.idFromRecordOrId(userOrId)},
|
{ _id: this.idFromRecordOrId(userOrId) },
|
||||||
{$push: {guests: newGuest.getId()}});
|
{ $push: { guests: newGuest.getId() } }
|
||||||
|
);
|
||||||
return newGuest;
|
return newGuest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteGuestFromUser(userOrId: OrId<KadiUser>, guestOrGuestId: OrId<Player>): Promise<Player> {
|
async deleteGuestFromUser(
|
||||||
|
userOrId: OrId<KadiUser>,
|
||||||
|
guestOrGuestId: OrId<Player>
|
||||||
|
): Promise<Player> {
|
||||||
const deletedGuest = await PlayerCollection().delete(this.idFromRecordOrId(guestOrGuestId));
|
const deletedGuest = await PlayerCollection().delete(this.idFromRecordOrId(guestOrGuestId));
|
||||||
await this.mongoDbClientCollection!.findOneAndUpdate(
|
await this.mongoDbClientCollection!.findOneAndUpdate(
|
||||||
{_id: this.idFromRecordOrId(userOrId)},
|
{ _id: this.idFromRecordOrId(userOrId) },
|
||||||
{$pull: {guests: this.idFromRecordOrId(deletedGuest)}});
|
{ $pull: { guests: this.idFromRecordOrId(deletedGuest) } }
|
||||||
|
);
|
||||||
return deletedGuest;
|
return deletedGuest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +145,8 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
return Promise.all<Player>(
|
return Promise.all<Player>(
|
||||||
guestIdList.map(async (guestId) => {
|
guestIdList.map(async (guestId) => {
|
||||||
return await PlayerCollection().read(guestId);
|
return await PlayerCollection().read(guestId);
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMainPlayerForUser(userOrId: OrId<KadiUser>): Promise<Player> {
|
async getMainPlayerForUser(userOrId: OrId<KadiUser>): Promise<Player> {
|
||||||
@@ -147,50 +159,56 @@ class KadiUserCollection extends MongoStoredObjectCollection<KadiUserMongoData>
|
|||||||
return Promise.all<SavedGame>(
|
return Promise.all<SavedGame>(
|
||||||
savedGameIds.map(async (savedGameId) => {
|
savedGameIds.map(async (savedGameId) => {
|
||||||
return await SavedGameCollection().read(savedGameId);
|
return await SavedGameCollection().read(savedGameId);
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addGameForUser(userOrId: OrId<KadiUser>, submission: ProcessedGameSubmission): Promise<SavedGame> {
|
async addGameForUser(
|
||||||
|
userOrId: OrId<KadiUser>,
|
||||||
|
submission: ProcessedGameSubmission
|
||||||
|
): Promise<SavedGame> {
|
||||||
const newGame = await SavedGameCollection().create(submission);
|
const newGame = await SavedGameCollection().create(submission);
|
||||||
await this.mongoDbClientCollection!.findOneAndUpdate(
|
await this.mongoDbClientCollection!.findOneAndUpdate(
|
||||||
{_id: this.idFromRecordOrId(userOrId)},
|
{ _id: this.idFromRecordOrId(userOrId) },
|
||||||
{$push: {savedGames: newGame.getId()}});
|
{ $push: { savedGames: newGame.getId() } }
|
||||||
|
);
|
||||||
return newGame;
|
return newGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAccountStats(userOrId: OrId<KadiUser>, gameResults: (PlayerGameResults & {outcome: OutcomeType})[], rulesetUsedOrId: OrId<Ruleset>): Promise<void> {
|
async updateAccountStats(
|
||||||
|
userOrId: OrId<KadiUser>,
|
||||||
|
gameResults: (PlayerGameResults & { outcome: OutcomeType })[],
|
||||||
|
rulesetUsedOrId: OrId<Ruleset>
|
||||||
|
): Promise<void> {
|
||||||
const userId = this.idFromRecordOrId(userOrId);
|
const userId = this.idFromRecordOrId(userOrId);
|
||||||
const rulesetUsed = rulesetUsedOrId instanceof Ruleset ? rulesetUsedOrId : await RulesetCollection().read(rulesetUsedOrId);
|
const rulesetUsed =
|
||||||
|
rulesetUsedOrId instanceof Ruleset
|
||||||
|
? rulesetUsedOrId
|
||||||
|
: await RulesetCollection().read(rulesetUsedOrId);
|
||||||
const accountStatsMongoData = (await this.mongoRead(userId)).accountStats;
|
const accountStatsMongoData = (await this.mongoRead(userId)).accountStats;
|
||||||
const accountStatsObject = new AccountStats(accountStatsMongoData);
|
const accountStatsObject = new AccountStats(accountStatsMongoData);
|
||||||
accountStatsObject.updateStats(gameResults, rulesetUsed);
|
accountStatsObject.updateStats(gameResults, rulesetUsed);
|
||||||
this.mongoUpdate({
|
this.mongoUpdate({
|
||||||
id: userId,
|
id: userId,
|
||||||
accountStats: accountStatsObject.getData()
|
accountStats: accountStatsObject.getData(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllStatsForUser(userOrId: OrId<KadiUser>): Promise<StatsListing> {
|
async getAllStatsForUser(userOrId: OrId<KadiUser>): Promise<StatsListing> {
|
||||||
const players = [...(await this.getAllGuestsForUser(userOrId)), await this.getMainPlayerForUser(userOrId)];
|
const players = [
|
||||||
const playerStats = players.map(player => ({
|
...(await this.getAllGuestsForUser(userOrId)),
|
||||||
|
await this.getMainPlayerForUser(userOrId),
|
||||||
|
];
|
||||||
|
const playerStats = players.map((player) => ({
|
||||||
nick: player.getNick(),
|
nick: player.getNick(),
|
||||||
playerId: player.getId(),
|
playerId: player.getId(),
|
||||||
stats: player.getStats()
|
stats: player.getStats(),
|
||||||
}));
|
}));
|
||||||
const accountStatsMongoData = (await this.mongoRead(this.idFromRecordOrId(userOrId))).accountStats;
|
const accountStatsMongoData = (await this.mongoRead(this.idFromRecordOrId(userOrId)))
|
||||||
|
.accountStats;
|
||||||
const accountStats = new AccountStats(accountStatsMongoData);
|
const accountStats = new AccountStats(accountStatsMongoData);
|
||||||
return {pStats: playerStats, accStats: accountStats};
|
return { pStats: playerStats, accStats: accountStats };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatsListing {
|
export default KadiUserCollection.getInstance;
|
||||||
accStats: AccountStats;
|
|
||||||
pStats: {
|
|
||||||
nick: string,
|
|
||||||
playerId: ActiveRecordId,
|
|
||||||
stats: PlayerStats
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default KadiUserCollection.getInstance;
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import mongo from "mongodb";
|
|||||||
import {tryQuery} from "../database";
|
import {tryQuery} from "../database";
|
||||||
import {InvalidIdError, MongoError} from "../errors";
|
import {InvalidIdError, MongoError} from "../errors";
|
||||||
import ActiveRecord, {ActiveRecordId} from "../Objects/ActiveRecord";
|
import ActiveRecord, {ActiveRecordId} from "../Objects/ActiveRecord";
|
||||||
|
import {FilterQuery} from "mongoose";
|
||||||
|
|
||||||
|
|
||||||
abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}> {
|
abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}> {
|
||||||
@@ -37,6 +38,22 @@ abstract class MongoStoredObjectCollection<IRawData extends {id: ActiveRecordId}
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async mongoFindBy(query: FilterQuery<any>): Promise<IRawData[]> {
|
||||||
|
return tryQuery(async () => {
|
||||||
|
const results = await this.mongoDbClientCollection!.find(query).toArray();
|
||||||
|
if (results.length > 0) {
|
||||||
|
results.forEach(result => {
|
||||||
|
result.id = result._id;
|
||||||
|
result._id = undefined;
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected async mongoFindByAttribute(attribute: string, value: any): Promise<IRawData | null> {
|
protected async mongoFindByAttribute(attribute: string, value: any): Promise<IRawData | null> {
|
||||||
return tryQuery(async () => {
|
return tryQuery(async () => {
|
||||||
const result = await this.mongoDbClientCollection!.findOne({[attribute]: value});
|
const result = await this.mongoDbClientCollection!.findOne({[attribute]: value});
|
||||||
|
|||||||
@@ -68,5 +68,4 @@ class PlayerCollection extends MongoStoredObjectCollection<PlayerMongoData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default PlayerCollection.getInstance;
|
export default PlayerCollection.getInstance;
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
|
import MongoStoredObjectCollection from "./MongoStoredObjectCollection";
|
||||||
import {DEFAULT_RULESET, DEFAULT_RULESET_NAME, RulesetSchema} from "../rulesets";
|
import {
|
||||||
import {getMongoObjectCollection} from "../database";
|
DEFAULT_RULESET,
|
||||||
|
DEFAULT_RULESET_NAME,
|
||||||
|
RulesetSchema,
|
||||||
|
} from "../rulesets";
|
||||||
|
import { getMongoObjectCollection } from "../database";
|
||||||
import Ruleset from "../Objects/Ruleset";
|
import Ruleset from "../Objects/Ruleset";
|
||||||
import {ActiveRecordId} from "../Objects/ActiveRecord";
|
import { ActiveRecordId } from "../Objects/ActiveRecord";
|
||||||
|
|
||||||
type RulesetMongoData = RulesetSchema & {id: ActiveRecordId};
|
type RulesetMongoData = RulesetSchema & { id: ActiveRecordId };
|
||||||
|
|
||||||
class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> {
|
class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> {
|
||||||
private static instance?: RulesetCollection;
|
private static instance?: RulesetCollection;
|
||||||
@@ -23,19 +27,33 @@ class RulesetCollection extends MongoStoredObjectCollection<RulesetMongoData> {
|
|||||||
this.mongoDbClientCollection = getMongoObjectCollection("rulesets");
|
this.mongoDbClientCollection = getMongoObjectCollection("rulesets");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async rulesetFrom(data: RulesetMongoData): Promise<Ruleset> {
|
private rulesetFrom(data: RulesetMongoData): Ruleset {
|
||||||
return new Ruleset(data.id, data);
|
return new Ruleset(data.id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async create(rulesetSchema: RulesetSchema): Promise<Ruleset> {
|
||||||
|
const newRuleset = await this.mongoCreate(rulesetSchema);
|
||||||
|
return this.rulesetFrom(newRuleset);
|
||||||
|
}
|
||||||
|
|
||||||
async read(id: ActiveRecordId): Promise<Ruleset> {
|
async read(id: ActiveRecordId): Promise<Ruleset> {
|
||||||
if (id === DEFAULT_RULESET_NAME) {
|
if (id === DEFAULT_RULESET_NAME) {
|
||||||
return new Ruleset(DEFAULT_RULESET_NAME, DEFAULT_RULESET);
|
return new Ruleset(DEFAULT_RULESET_NAME, DEFAULT_RULESET);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const foundRuleset = await this.mongoRead(id);
|
const foundRuleset = await this.mongoRead(id);
|
||||||
return this.rulesetFrom(foundRuleset);
|
return this.rulesetFrom(foundRuleset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAllRulesets(): Promise<Ruleset[]> {
|
||||||
|
const rulesets = await this.mongoFindBy({});
|
||||||
|
return [
|
||||||
|
...rulesets.map((ruleset) =>
|
||||||
|
this.rulesetFrom(ruleset as RulesetMongoData)
|
||||||
|
),
|
||||||
|
await this.read(DEFAULT_RULESET_NAME),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RulesetCollection.getInstance;
|
export default RulesetCollection.getInstance;
|
||||||
|
|||||||
@@ -1,34 +1,21 @@
|
|||||||
import {RulesetSchema} from "../rulesets";
|
import { RulesetSchema } from "../rulesets";
|
||||||
import {AccountStatsMongoData, OutcomeType, PlayerGameResults} from "./DefaultStatsMongoData";
|
import { AccountStatsMongoData, OutcomeType, PlayerGameResults } from "./DefaultStatsMongoData";
|
||||||
import {UpdateError} from "../errors";
|
import { UpdateError } from "../errors";
|
||||||
import StatsUpdater from "./StatsUpdater";
|
import StatsUpdater, { PlayerGameResultsWithOutcomes } from "./StatsUpdater";
|
||||||
import Ruleset from "./Ruleset";
|
import Ruleset from "./Ruleset";
|
||||||
|
|
||||||
class AccountStats {
|
class AccountStats {
|
||||||
private data: AccountStatsMongoData;
|
private readonly data: AccountStatsMongoData;
|
||||||
private readonly updater: StatsUpdater;
|
|
||||||
constructor(data: AccountStatsMongoData) {
|
constructor(data: AccountStatsMongoData) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.updater = new StatsUpdater();
|
|
||||||
this.updater.use(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use(data: AccountStatsMongoData) {
|
updateStats(playerGameResults: PlayerGameResultsWithOutcomes[], ruleset: Ruleset): void {
|
||||||
this.data = data;
|
const updater = new StatsUpdater(this.data, ruleset);
|
||||||
this.updater.use(data);
|
for (const playerGameResult of playerGameResults) {
|
||||||
}
|
updater.updateStats(playerGameResult);
|
||||||
|
|
||||||
updateStats(playerGameResults: (PlayerGameResults & {outcome: OutcomeType})[], ruleset: Ruleset): void {
|
|
||||||
if (this.data) {
|
|
||||||
for (const playerGameResult of playerGameResults) {
|
|
||||||
this.updater.updateStats(playerGameResult, ruleset);
|
|
||||||
}
|
|
||||||
this.data.gamesPlayed += 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data
|
|
||||||
to analyse.`);
|
|
||||||
}
|
}
|
||||||
|
this.data.gamesPlayed += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(): AccountStatsMongoData {
|
getData(): AccountStatsMongoData {
|
||||||
@@ -36,4 +23,4 @@ class AccountStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AccountStats;
|
export default AccountStats;
|
||||||
|
|||||||
@@ -22,9 +22,14 @@ export interface RulesetStatsMongoData {
|
|||||||
losses: number;
|
losses: number;
|
||||||
grandTotal: TotalFieldStatsMongoData;
|
grandTotal: TotalFieldStatsMongoData;
|
||||||
}
|
}
|
||||||
export interface BlockStatsMongoData {
|
export type BlockStatsMongoData = BonusBlockStatsMongoData | NoBonusBlockStatsMongoData;
|
||||||
|
export interface BonusBlockStatsMongoData {
|
||||||
|
cellStats: Record<string, CellStatsMongoData>;
|
||||||
|
timesHadBonus: number;
|
||||||
|
total: TotalFieldStatsMongoData;
|
||||||
|
}
|
||||||
|
export interface NoBonusBlockStatsMongoData {
|
||||||
cellStats: Record<string, CellStatsMongoData>;
|
cellStats: Record<string, CellStatsMongoData>;
|
||||||
timesHadBonus?: number;
|
|
||||||
total: TotalFieldStatsMongoData;
|
total: TotalFieldStatsMongoData;
|
||||||
}
|
}
|
||||||
export interface BaseCellStatsMongoData {
|
export interface BaseCellStatsMongoData {
|
||||||
@@ -133,7 +138,7 @@ function defaultBlockStatsMongoData(cellSchemas: Record<string, CellDef>, hasBon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultRulesetStatsMongoData(ruleset: RulesetSchema): RulesetStatsMongoData {
|
export function defaultRulesetStatsMongoData(ruleset: RulesetSchema): RulesetStatsMongoData {
|
||||||
const blockStatsRecord: Record<string, BlockStatsMongoData> = {};
|
const blockStatsRecord: Record<string, BlockStatsMongoData> = {};
|
||||||
for (const blockLabel in ruleset.blocks) {
|
for (const blockLabel in ruleset.blocks) {
|
||||||
blockStatsRecord[blockLabel] = defaultBlockStatsMongoData(ruleset.blocks[blockLabel].cells, ruleset.blocks[blockLabel].hasBonus);
|
blockStatsRecord[blockLabel] = defaultBlockStatsMongoData(ruleset.blocks[blockLabel].cells, ruleset.blocks[blockLabel].hasBonus);
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
import {RulesetSchema} from "../rulesets";
|
import {PlayerStatsMongoData} from "./DefaultStatsMongoData";
|
||||||
import {OutcomeType, PlayerGameResults, PlayerStatsMongoData} from "./DefaultStatsMongoData";
|
import StatsUpdater, {PlayerGameResultsWithOutcomes} from "./StatsUpdater";
|
||||||
import StatsUpdater from "./StatsUpdater";
|
|
||||||
import Ruleset from "./Ruleset";
|
import Ruleset from "./Ruleset";
|
||||||
|
|
||||||
class PlayerStats {
|
class PlayerStats {
|
||||||
private data: PlayerStatsMongoData;
|
private readonly data: PlayerStatsMongoData;
|
||||||
private readonly updater: StatsUpdater;
|
|
||||||
constructor(data: PlayerStatsMongoData) {
|
constructor(data: PlayerStatsMongoData) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.updater = new StatsUpdater();
|
|
||||||
this.updater.use(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use(data: PlayerStatsMongoData) {
|
updateStats(playerGameResults: PlayerGameResultsWithOutcomes, ruleset: Ruleset): void {
|
||||||
this.data = data;
|
const updater = new StatsUpdater(this.data, ruleset);
|
||||||
this.updater.use(data);
|
updater.updateStats(playerGameResults);
|
||||||
}
|
|
||||||
|
|
||||||
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
|
||||||
this.updater.updateStats(playerGameResults, ruleset);
|
|
||||||
this.data.gamesPlayed += 1;
|
this.data.gamesPlayed += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {BlockDef, CellDef, RulesetSchema} from "../rulesets";
|
import {BlockDef, CellDef, RulesetSchema} from "../rulesets";
|
||||||
import ActiveRecord, {ActiveRecordId} from "./ActiveRecord";
|
import ActiveRecord, {ActiveRecordId} from "./ActiveRecord";
|
||||||
|
import {FieldType} from "../enums";
|
||||||
|
import {CellLocation} from "./ScoreCalculator";
|
||||||
|
|
||||||
export class Ruleset implements ActiveRecord {
|
export class Ruleset implements ActiveRecord {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -27,9 +29,13 @@ export class Ruleset implements ActiveRecord {
|
|||||||
return Object.assign({}, this.schema.blocks[blockId].cells);
|
return Object.assign({}, this.schema.blocks[blockId].cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSchemaJSON(): RulesetSchema {
|
getSchema(): RulesetSchema {
|
||||||
return Object.assign({}, this.schema);
|
return Object.assign({}, this.schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCellFieldTypeByLocation(cellLocation: CellLocation): FieldType {
|
||||||
|
return this.getCellsInBlock(cellLocation.blockId)[cellLocation.cellId].fieldType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Ruleset;
|
export default Ruleset;
|
||||||
@@ -1,49 +1,59 @@
|
|||||||
import ScoreCalculator, {ScoreCardJSONRepresentation} from "./ScoreCalculator";
|
import ScoreCalculator, { CellLocation, ScoreCardJSONRepresentation } from "./ScoreCalculator";
|
||||||
import {UpdateError} from "../errors";
|
import { UpdateError } from "../errors";
|
||||||
import {FieldType} from "../enums";
|
import { FieldType } from "../enums";
|
||||||
import {
|
import {
|
||||||
|
BaseCellStatsMongoData,
|
||||||
BaseStatsMongoData,
|
BaseStatsMongoData,
|
||||||
BestableFieldStatsMongoData,
|
BestableFieldStatsMongoData,
|
||||||
|
BlockStatsMongoData,
|
||||||
|
BonusBlockStatsMongoData,
|
||||||
BoolFieldStatsMongoData,
|
BoolFieldStatsMongoData,
|
||||||
CellStatsMongoData,
|
CellStatsMongoData,
|
||||||
|
defaultRulesetStatsMongoData,
|
||||||
OutcomeType,
|
OutcomeType,
|
||||||
PlayerGameResults,
|
PlayerGameResults,
|
||||||
RulesetStatsMongoData,
|
RulesetStatsMongoData,
|
||||||
TotalFieldStatsMongoData
|
TotalFieldStatsMongoData,
|
||||||
} from "./DefaultStatsMongoData";
|
} from "./DefaultStatsMongoData";
|
||||||
import Ruleset from "./Ruleset";
|
import Ruleset from "./Ruleset";
|
||||||
|
import { BonusBlockDef } from "../rulesets";
|
||||||
|
|
||||||
|
export type PlayerGameResultsWithOutcomes = PlayerGameResults & { outcome: OutcomeType };
|
||||||
|
|
||||||
class StatsUpdater {
|
class StatsUpdater {
|
||||||
private data?: BaseStatsMongoData;
|
private data: BaseStatsMongoData;
|
||||||
private validationRuleset?: Ruleset;
|
private validationRuleset: Ruleset;
|
||||||
private calculator?: ScoreCalculator;
|
private calculator: ScoreCalculator;
|
||||||
private currentStatsObject?: RulesetStatsMongoData;
|
private statsByRuleset: RulesetStatsMongoData;
|
||||||
constructor() {
|
constructor(data: BaseStatsMongoData, validationRuleset: Ruleset) {
|
||||||
|
this.data = data;
|
||||||
|
this.validationRuleset = validationRuleset;
|
||||||
|
this.calculator = new ScoreCalculator(validationRuleset);
|
||||||
|
this.statsByRuleset = this.getStatsByRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStatsByRuleset(): RulesetStatsMongoData {
|
||||||
|
if (!this.data.statsByRuleset[this.validationRuleset.getId().toString()]) {
|
||||||
|
this.data.statsByRuleset[
|
||||||
|
this.validationRuleset.getId().toString()
|
||||||
|
] = defaultRulesetStatsMongoData(this.validationRuleset.getSchema());
|
||||||
|
}
|
||||||
|
return this.data.statsByRuleset[this.validationRuleset.getId().toString()];
|
||||||
}
|
}
|
||||||
|
|
||||||
use(data: BaseStatsMongoData) {
|
use(data: BaseStatsMongoData) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStats(playerGameResults: PlayerGameResults & {outcome: OutcomeType}, ruleset: Ruleset): void {
|
updateStats(playerGameResults: PlayerGameResultsWithOutcomes): void {
|
||||||
if (this.data) {
|
for (const blockId in this.validationRuleset.getBlocks()) {
|
||||||
this.validationRuleset = ruleset;
|
this.updateBlockStats(blockId);
|
||||||
this.calculator = new ScoreCalculator(ruleset);
|
|
||||||
this.calculator.hydrateWithJSON(playerGameResults as ScoreCardJSONRepresentation);
|
|
||||||
this.currentStatsObject = this.data.statsByRuleset[ruleset.getId().toString()];
|
|
||||||
for (const blockId in ruleset.getBlocks()) {
|
|
||||||
this.updateBlockStats(blockId);
|
|
||||||
}
|
|
||||||
this.updateTotalFieldStats(this.currentStatsObject.grandTotal, this.calculator.getTotal());
|
|
||||||
this.currentStatsObject.wins += Number(playerGameResults.outcome === OutcomeType.win);
|
|
||||||
this.currentStatsObject.draws += Number(playerGameResults.outcome === OutcomeType.draw);
|
|
||||||
this.currentStatsObject.runnerUps += Number(playerGameResults.outcome === OutcomeType.runnerUp);
|
|
||||||
this.currentStatsObject.losses += Number(playerGameResults.outcome === OutcomeType.loss);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new UpdateError(`Cannot update without data! Call the use() method to hydrate the updater with data
|
|
||||||
to analyse.`);
|
|
||||||
}
|
}
|
||||||
|
this.updateTotalFieldStats(this.statsByRuleset.grandTotal, this.calculator.getTotal());
|
||||||
|
this.statsByRuleset.wins += Number(playerGameResults.outcome === OutcomeType.win);
|
||||||
|
this.statsByRuleset.draws += Number(playerGameResults.outcome === OutcomeType.draw);
|
||||||
|
this.statsByRuleset.runnerUps += Number(playerGameResults.outcome === OutcomeType.runnerUp);
|
||||||
|
this.statsByRuleset.losses += Number(playerGameResults.outcome === OutcomeType.loss);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTotalFieldStats(statsObject: TotalFieldStatsMongoData, total: number) {
|
private updateTotalFieldStats(statsObject: TotalFieldStatsMongoData, total: number) {
|
||||||
@@ -53,43 +63,55 @@ class StatsUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateBlockStats(blockId: string) {
|
private updateBlockStats(blockId: string) {
|
||||||
if (this.currentStatsObject) {
|
const blockStats = this.statsByRuleset.blockStats[blockId];
|
||||||
const blockStats = this.currentStatsObject.blockStats[blockId];
|
this.updateTotalFieldStats(blockStats.total, this.calculator.getBlockSubTotalById(blockId));
|
||||||
this.updateTotalFieldStats(blockStats.total, this.calculator!.getBlockSubTotalById(blockId));
|
if (this.isBonusBlockStats(blockStats)) {
|
||||||
if (this.calculator!.blockWithIdHasBonus(blockId)) {
|
blockStats.timesHadBonus += 1;
|
||||||
blockStats.timesHadBonus! += 1;
|
}
|
||||||
}
|
for (const cellId in this.validationRuleset.getBlocks()[blockId].cells) {
|
||||||
for (const cellId in this.validationRuleset!.getBlocks()[blockId].cells) {
|
this.updateCellStatsByLocation({ cellId, blockId });
|
||||||
this.updateCellStatsByIds({cellId, blockId});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateCellStatsByIds(ids: {cellId: string, blockId: string}) {
|
private updateCellStatsByLocation(location: CellLocation) {
|
||||||
const cellStats = this.getCellStatsByIds({...ids, rulesetId: this.validationRuleset!.getId().toString()});
|
const cellStats = this.getCellStatsByLocation(location);
|
||||||
const cellFieldType = this.validationRuleset?.getBlocks()[ids.blockId].cells[ids.cellId].fieldType;
|
const cellFieldType = this.validationRuleset.getCellFieldTypeByLocation(location);
|
||||||
const cellScore = this.calculator!.getCellScoreByLocation({...ids});
|
const cellScore = this.calculator.getCellScoreByLocation(location);
|
||||||
cellStats.runningTotal += cellScore;
|
cellStats.runningTotal += cellScore;
|
||||||
if (cellScore > 0 && cellFieldType === FieldType.bool) {
|
if (cellScore > 0 && cellFieldType === FieldType.bool) {
|
||||||
(cellStats as BoolFieldStatsMongoData).total += 1;
|
(cellStats as BoolFieldStatsMongoData).total += 1;
|
||||||
}
|
} else if (this.isBestableCell(cellStats, cellFieldType)) {
|
||||||
else if (cellFieldType === FieldType.multiplier || FieldType.superkadi || FieldType.number) {
|
if (cellStats.best < cellScore) {
|
||||||
const bestableStats = (cellStats as BestableFieldStatsMongoData);
|
cellStats.best = cellScore;
|
||||||
if (bestableStats.best < cellScore) {
|
} else if (cellStats.worst > cellScore) {
|
||||||
bestableStats.best = cellScore;
|
cellStats.worst = cellScore;
|
||||||
}
|
|
||||||
else if (bestableStats.worst > cellScore) {
|
|
||||||
bestableStats.worst = cellScore
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.calculator!.cellAtLocationIsStruck({...ids})) {
|
if (this.calculator.cellAtLocationIsStruck({ ...location })) {
|
||||||
cellStats.timesStruck += 1;
|
cellStats.timesStruck += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCellStatsByIds(ids: {cellId: string, blockId: string, rulesetId: string}): CellStatsMongoData {
|
private getCellStatsByLocation(location: CellLocation): CellStatsMongoData {
|
||||||
return this.data!.statsByRuleset[ids.rulesetId].blockStats[ids.blockId].cellStats[ids.cellId];
|
return this.statsByRuleset.blockStats[location.blockId].cellStats[location.cellId];
|
||||||
|
}
|
||||||
|
|
||||||
|
private isBonusBlockStats(
|
||||||
|
blockStats: BlockStatsMongoData
|
||||||
|
): blockStats is BonusBlockStatsMongoData {
|
||||||
|
return blockStats.hasOwnProperty("timesHadBonus");
|
||||||
|
}
|
||||||
|
|
||||||
|
private isBestableCell(
|
||||||
|
cellStats: BaseCellStatsMongoData,
|
||||||
|
fieldType: FieldType
|
||||||
|
): cellStats is BestableFieldStatsMongoData {
|
||||||
|
return (
|
||||||
|
fieldType === FieldType.multiplier ||
|
||||||
|
fieldType === FieldType.superkadi ||
|
||||||
|
fieldType === FieldType.number
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StatsUpdater;
|
export default StatsUpdater;
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ router.post("/games", requireAuthenticated, statsController.saveGame);
|
|||||||
//Stats
|
//Stats
|
||||||
router.get("/stats", requireAuthenticated, statsController.getStats);
|
router.get("/stats", requireAuthenticated, statsController.getStats);
|
||||||
router.get("/ruleset/:id", rulesetController.getRuleset);
|
router.get("/ruleset/:id", rulesetController.getRuleset);
|
||||||
|
router.post("/ruleset/", requireAuthenticated, rulesetController.addRuleset);
|
||||||
|
router.get("/rulesets/", requireAuthenticated, rulesetController.getAllRulesets);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
Reference in New Issue
Block a user