Skip to content

Commit

Permalink
consolidate metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
EllAchE committed Nov 5, 2023
1 parent 3eb7a5e commit b69d822
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 202 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ jobs:
- name: Install Dependencies
run: npm install

- name: Run tests on diffed files
- name: Run tests
run: npm run test
30 changes: 8 additions & 22 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { Piece, PrettyMove } from '../cjsmin/src/chess';
import { gameChunks } from './fileReader';
import { KDRatioMetric } from './metrics/captures';
import {
AverageDistanceMetric,
getMoveDistanceSetOfGames,
} from './metrics/distances';
import { MoveDistanceMetric } from './metrics/distances';
import { getGameWithMostMoves, getPieceLevelMoveInfo } from './metrics/moves';
import { getPiecePromotionInfo } from './metrics/promotions';
import { FileReaderGame } from './types';
Expand Down Expand Up @@ -34,33 +30,23 @@ export async function main(path: string) {
}
console.timeEnd('Task 1: FileReader');

const { gameCount, totalDistanceMap } = await getMoveDistanceSetOfGames(
games
);

getGameWithMostMoves(games);
getPieceLevelMoveInfo(games);
getPiecePromotionInfo(games);

console.log(`Total number of games analyzed: ${gameCount}`);
console.log('\n');

console.log('==============================================================');
console.log('\n');

console.timeEnd('Total Execution Time');
console.log('\n');
}

/**
* Metric functions will ingest a single game at a time
* @param metricFunctions
*/
function gameIterator(
metricFunctions: ((game: { move: PrettyMove; board: Piece[] }[]) => void)[]
) {
function gameIterator() {
// Logic to get link to the game, which should be passed in processGame
// let site = game.metadata
// .find((item) => item.startsWith('[Site "'))
// ?.replace('[Site "', '')
// .replace('"]', '');
const kdrm = new KDRatioMetric();
const adm = new AverageDistanceMetric();
const adm = new MoveDistanceMetric();
}

if (require.main === module) {
Expand Down
14 changes: 7 additions & 7 deletions src/metrics/captures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,21 @@ export class KDRatioMetric implements Metric {
aggregate() {
const KDRatios = createUAPMap(0);
// calculate the KD ratios of each piece
for (const piece of Object.keys(this.KDAssistsMap)) {
const kills = this.KDAssistsMap[piece].kills;
const deaths = this.KDAssistsMap[piece].deaths || 0;
for (const uas of Object.keys(this.KDAssistsMap)) {
const kills = this.KDAssistsMap[uas].kills;
const deaths = this.KDAssistsMap[uas].deaths || 0;
if (deaths !== 0) {
KDRatios[piece] = kills / deaths;
KDRatios[uas] = kills / deaths;
}
}

// find the piece with the highest KD ratio
let maxKDRatio = 0;
let pieceWithHighestKDRatio = null;

for (const piece of Object.keys(KDRatios)) {
if (KDRatios[piece] > maxKDRatio) {
maxKDRatio = pieceWithHighestKDRatio = KDRatios[piece];
for (const uas of Object.keys(KDRatios)) {
if (KDRatios[uas] > maxKDRatio) {
maxKDRatio = pieceWithHighestKDRatio = KDRatios[uas];
}
}

Expand Down
225 changes: 54 additions & 171 deletions src/metrics/distances.ts
Original file line number Diff line number Diff line change
@@ -1,208 +1,83 @@
import { Chess, Piece, PrettyMove, UASymbol } from '../../cjsmin/src/chess';
import { FileReaderGame, UAPMap } from '../types';
import { Piece, PrettyMove, UASymbol } from '../../cjsmin/src/chess';
import { UAPMap } from '../types';
import { createUAPMap } from '../utils';
import { Metric } from './metric';

// take a start and end board position and return the distances moved
export async function getMoveDistanceSingleGame(game: FileReaderGame) {
const chess = new Chess();
const moveGenerator = chess.historyGenerator(game.moves);

// create an object to track distance value for each piece
const distanceMap: { [key: string]: number } = {};

// Initialize variables to keep track of the maximum distance and the piece
let maxDistance = -1;
let maxDistancePiece: UASymbol;
let singleGameDistanceTotal = 0;

// evaluate each move, update the correct unambiguous piece's distance
for (let moveInfo of moveGenerator) {
const { move, board } = moveInfo;

if (!distanceMap[move.uas]) {
distanceMap[move.uas] = 0;
}

const fromMove = moveInfo.move.from;
const toMove = moveInfo.move.to;

let distance = 0;

// Check if the move is a castling move
if (moveInfo.move.flags === 'k' || moveInfo.move.flags === 'q') {
let movingKing = 'k';
let movingRook, rookDistance;

if (moveInfo.move.flags === 'k') {
rookDistance = 2;
movingRook = 'rh';
} else {
rookDistance = 3;
movingRook = 'ra';
}

if (moveInfo.move.color === 'w') {
movingRook = movingRook.toUpperCase();
movingKing = movingKing.toUpperCase();
}

distanceMap[movingKing] += 2;
distanceMap[movingRook] += rookDistance;
singleGameDistanceTotal += 2;
singleGameDistanceTotal += rookDistance;
} else {
// Calculate the file (column) distance by subtracting ASCII values
const fileDist = Math.abs(fromMove.charCodeAt(0) - toMove.charCodeAt(0));
// Calculate the rank (row) distance by subtracting numeric values
const rankDist = Math.abs(Number(fromMove[1]) - Number(toMove[1]));
// The distance moved is the maximum of fileDist and rankDist
distance = Math.max(fileDist, rankDist);

distanceMap[move.uas] += distance;
singleGameDistanceTotal += distance;
}

if (distanceMap[move.uas] > maxDistance) {
maxDistance = distanceMap[move.uas];
maxDistancePiece = move.uas;
}
}

return {
maxDistancePiece,
maxDistance,
distanceMap,
singleGameDistanceTotal,
};
}

// returns the piece that moved the furthest, the game it moved the furthest in, the distance it moved, and the number of games analyzed in the set
export async function getMoveDistanceSetOfGames(games: FileReaderGame[]) {
console.time('Task 2: getMoveDistanceSetOfGames');
let maxDistance = 0;
let pieceThatMovedTheFurthest = null;
let totalDistanceMap: { [key: string]: number } = {};
let gameWithFurthestPiece = null;
let siteWithFurthestPiece = null;
let lastGame;
let furthestCollectiveDistance = 0;
let gameLinkWithFurthestCollectiveDistance = null;

let gameCount = 0;
for await (const game of games) {
// progress tracker
gameCount++;

const {
maxDistancePiece,
maxDistance: distance,
distanceMap,
singleGameDistanceTotal,
} = await getMoveDistanceSingleGame(game);

if (distance > maxDistance) {
maxDistance = distance;
pieceThatMovedTheFurthest = maxDistancePiece;
gameWithFurthestPiece = game;
let site = game.metadata
.find((item) => item.startsWith('[Site "'))
?.replace('[Site "', '')
.replace('"]', '');
siteWithFurthestPiece = site;
}

if (singleGameDistanceTotal > furthestCollectiveDistance) {
furthestCollectiveDistance = singleGameDistanceTotal;
gameLinkWithFurthestCollectiveDistance = game.metadata
.find((item) => item.startsWith('[Site "'))
?.replace('[Site "', '')
.replace('"]', '');
}

for (const piece of Object.keys(distanceMap)) {
if (!totalDistanceMap[piece]) {
totalDistanceMap[piece] = 0;
}
totalDistanceMap[piece] += distanceMap[piece];
}

lastGame = game;
}

console.timeEnd('Task 2: getMoveDistanceSetOfGames');

// distance facts
console.log('DISTANCE FACTS:');
console.log('==============================================================');
console.log('\n');

// Facts
console.log(`Piece that moved the furthest: ${pieceThatMovedTheFurthest}`);
console.log(
`Game in which that piece (${pieceThatMovedTheFurthest}) moved the furthest: ${siteWithFurthestPiece}`
);
console.log(`Distance that piece moved in the game: ${maxDistance}`);
console.log(
`Game with the furthest collective distance moved: ${gameLinkWithFurthestCollectiveDistance}`
);
console.log(
`Collective distance moved in that game: ${furthestCollectiveDistance}`
);

return {
pieceThatMovedTheFurthest,
maxDistance,
gameCount,
gameWithFurthestPiece,
siteWithFurthestPiece,
totalDistanceMap,
lastGame,
furthestCollectiveDistance,
gameLinkWithFurthestCollectiveDistance,
};
}

export class AverageDistanceMetric implements Metric {
distanceMap: UAPMap<{ totalDistance: number }>;
export class MoveDistanceMetric implements Metric {
distanceMap: UAPMap<{ total: number; maxSingleGame: number }>;
pieceWithHighestAvg: UASymbol;
pieceWithLowestAvg: UASymbol;
maxAvgDistance: number;
minAvgDistance: number;
totalDistance: number;
maxSingleGameTotal: number;
singleGameMaxPiece: {
uas: UASymbol;
distance: number;
link: string;
};

constructor() {
this.distanceMap = createUAPMap({ totalDistance: 0 });
this.distanceMap = createUAPMap({
total: 0,
maxSingleGame: 0,
});
this.maxAvgDistance = 0;
this.minAvgDistance = Infinity; // Set high so first will overwrite
}

aggregate(gameCount: number) {
for (const piece of Object.keys(this.distanceMap)) {
const avgDistance = this.distanceMap[piece].totalDistance / gameCount;
for (const uas of Object.keys(this.distanceMap)) {
const avgDistance = this.distanceMap[uas].totalDistance / gameCount;
if (avgDistance > this.maxAvgDistance) {
this.maxAvgDistance = avgDistance;
this.pieceWithHighestAvg = piece as UASymbol;
this.pieceWithHighestAvg = uas as UASymbol;
}
if (avgDistance < this.minAvgDistance) {
this.minAvgDistance = avgDistance;
this.pieceWithLowestAvg = piece as UASymbol;
this.pieceWithLowestAvg = uas as UASymbol;
}
}
}

logResults(): void {
console.timeEnd('Task 2: getMoveDistanceSetOfGames');

// distance facts
console.log('DISTANCE FACTS:');
console.log(
'==============================================================\n'
);
console.log(
`Piece with highest avg distance for games analyzed: ${this.pieceWithHighestAvg}`
);
console.log(
`That piece's (${this.pieceWithHighestAvg}'s) average distance moved per game: ${this.maxAvgDistance}`
);

// Facts
console.log(
`Piece that moved the furthest: ${this.singleGameMaxPiece.uas}`
);
// Game link support will come later
// console.log(
// `Game in which that piece (${this.maxSingleGameDistancePiece}) moved the furthest: ${siteWithFurthestPiece}`
// );
console.log(
`Distance that piece moved in the game: ${this.singleGameMaxPiece.distance}`
);
// console.log(
// `Game with the furthest collective distance moved: ${gameLinkWithFurthestCollectiveDistance}`
// );
console.log(
`Collective distance moved in that game: ${this.maxSingleGameTotal}`
);
}

processGame(game: { move: PrettyMove; board: Piece[] }[]) {
// Initialize variables to keep track of the maximum distance and the piece
// evaluate each move, update the correct unambiguous piece's distance
const singleGameMap = createUAPMap(0);

for (const { move } of game) {
// Check if the move is a castling move
if (move.flags === 'k' || move.flags === 'q') {
Expand Down Expand Up @@ -237,9 +112,17 @@ export class AverageDistanceMetric implements Metric {
// The distance moved is the maximum of fileDist and rankDist
const distance = Math.max(fileDist, rankDist);

this.distanceMap[move.uas].totalDistance += distance;
singleGameMap[move.uas] += distance;
this.totalDistance += distance;
}
}

// add the single game aggregates to the state object
for (const uas of Object.keys(singleGameMap)) {
this.distanceMap[uas].totalDistance += singleGameMap[uas];
if (singleGameMap[uas] > this.distanceMap[uas].maxSingleGameDistance) {
this.distanceMap[uas].maxSingleGameDistance = singleGameMap[uas];
}
}
}
}
5 changes: 4 additions & 1 deletion src/metrics/metric.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Piece, PrettyMove } from '../../cjsmin/src/chess';

export interface Metric {
processGame(game: { move: PrettyMove; board: Piece[] }[]): any;
processGame(
game: { move: PrettyMove; board: Piece[] }[],
gameLink?: string
): any;

logResults?(): void;

Expand Down

0 comments on commit b69d822

Please sign in to comment.