diff --git a/cjsmin/src/chess.ts b/cjsmin/src/chess.ts index c19e8c1..a4e585e 100644 --- a/cjsmin/src/chess.ts +++ b/cjsmin/src/chess.ts @@ -53,6 +53,26 @@ export type PrettyMove = { uas: UASymbol; }; +export type PromotablePiece = 'q' | 'r' | 'b' | 'n'; + +export type Pawns = + | 'pa' + | 'pb' + | 'pc' + | 'pd' + | 'pe' + | 'pf' + | 'pg' + | 'ph' + | 'PA' + | 'PB' + | 'PC' + | 'PD' + | 'PE' + | 'PF' + | 'PG' + | 'PH'; + export type UASymbol = | 'RA' | 'NB' diff --git a/src/metrics/moves.ts b/src/metrics/moves.ts index 3177c04..6059f1f 100644 --- a/src/metrics/moves.ts +++ b/src/metrics/moves.ts @@ -1,5 +1,6 @@ import { Chess } from '../../cjsmin/src/chess'; import { FileReaderGame } from '../types'; + export async function getGameWithMostMoves(games: FileReaderGame[]) { console.time('Task 5: getGameWithMostMoves'); let maxNumMoves = 0; diff --git a/src/metrics/promotions.ts b/src/metrics/promotions.ts index 73fec04..ccb8fd4 100644 --- a/src/metrics/promotions.ts +++ b/src/metrics/promotions.ts @@ -1,54 +1,57 @@ -import { Chess } from '../../cjsmin/src/chess'; -import { FileReaderGame } from '../types'; - -export async function getPiecePromotionInfo(games: FileReaderGame[]) { - console.time('Task 7: getPiecePromotionInfo'); - let ambigPiecePromotedToMap = {}; - let promotingPieceMap = {}; - - for (const game of games) { - const chess = new Chess(); - chess.loadPgn(game.moves); - const chessHistory = chess.history(); +import { + Pawns, + Piece, + PrettyMove, + PromotablePiece, +} from '../../cjsmin/src/chess'; +import { Metric } from './metric'; + +export class PromotionMetric implements Metric { + //uas pawns mapped to count of promotions to each piece type + //prettier-ignore + promotionMap: { + [key in Pawns]: { + [key in PromotablePiece]: number; + } + }; - for (const moveInfo of chessHistory) { - if (moveInfo.originalString.includes('=')) { - // REGEX to only capture the piece type - const piecePromotedTo = moveInfo.originalString - .split('=')[1] - .match(/[a-zA-Z]/)[0]; + constructor() { + this.clear(); + } - const promotingPiece = moveInfo.uas; + clear(): void { + let promotionMap = {}; + //prettier-ignore + for (const pawn of ["pa", "pb", "pc", "pd", "pe", "pf", "pg", "ph", 'PA', 'PB', 'PC', 'PD', 'PE', 'PF', 'PG', 'PH']) { + promotionMap[pawn] = { q: 0, r: 0, b: 0, n: 0 }; + } + this.promotionMap = promotionMap as any; + } + processGame(game: { move: PrettyMove; board: Piece[] }[], gameLink?: string) { + for (const { move } of game) { + // TODO: we can use flags instead of includes('=) + if (move.originalString.includes('=')) { // update ambigPiecePromotedToMap - if (!ambigPiecePromotedToMap[piecePromotedTo]) { - ambigPiecePromotedToMap[piecePromotedTo] = 0; - } - ambigPiecePromotedToMap[piecePromotedTo]++; - - // update promotingPieceMap - if (!promotingPieceMap[promotingPiece]) { - promotingPieceMap[promotingPiece] = 0; - } - promotingPieceMap[promotingPiece]++; + this.promotionMap[move.uas][move.promotion]++; } } } - // promotions facts - console.log('PROMOTIONS FACTS:'); - console.log( - 'How often a piece is promoted to different ambiguous piece types:' - ), - console.table(ambigPiecePromotedToMap); - console.log('How often unambiguous piece is promoted:'), - console.table(promotingPieceMap); - console.log('=============================================================='); - console.log('\n'); - - console.timeEnd('Task 7: getPiecePromotionInfo'); - return { - ambigPiecePromotedToMap, - promotingPieceMap, - }; + aggregate() {} + + logResults(): void { + // promotions facts + console.log('PROMOTIONS FACTS:'); + console.log( + 'How often a piece is promoted to different ambiguous piece types:' + ), + console.table(this.promotionMap); + console.log('How often unambiguous piece is promoted:'), + console.table(this.promotionMap); + console.log( + '==============================================================' + ); + console.log('\n'); + } } diff --git a/tests/metrics.test.ts b/tests/metrics.test.ts index 944e11f..e6d6ab5 100644 --- a/tests/metrics.test.ts +++ b/tests/metrics.test.ts @@ -7,6 +7,7 @@ import { } from '../src/metrics/captures'; import { MoveDistanceMetric } from '../src/metrics/distances'; import { getGameWithMostMoves } from '../src/metrics/moves'; +import { PromotionMetric } from '../src/metrics/promotions'; // convert PGN string to GameHistoryObject export function pgnToGameHistory(pgn: string) { @@ -331,7 +332,6 @@ describe('All Tests', () => { kdrMetric.processGame(Array.from(cjsmin.historyGenerator(game[0].moves))); - console.log(kdrMetric.KDAssistsMap['ng'].kills); expect(kdrMetric.KDAssistsMap['ng'].kills).toEqual(3); }); }); @@ -351,4 +351,110 @@ describe('All Tests', () => { expect(result.maxNumMoves).toEqual(24); }); }); + + describe('PromotionMetric', () => { + const promotionMetric = new PromotionMetric(); + + afterEach(() => { + promotionMetric.clear(); + }); + + it('should update the promotion map when a promotion occurs', () => { + const moves = [ + { + move: { + originalString: 'e8=Q', + color: 'b', + from: 'e7', + to: 'e8', + piece: 'p', + flags: 'p', + promotion: 'q', + uas: 'pe', + }, + board: [], + }, + { + move: { + originalString: 'a2=a1', + color: 'w', + from: 'a2', + to: 'a1', + piece: 'p', + flags: 'p', + promotion: 'q', + uas: 'PA', + }, + }, + { + move: { + originalString: 'b7=b8', + color: 'b', + from: 'b7', + to: 'b8', + piece: 'p', + flags: 'p', + promotion: 'r', + uas: 'pb', + }, + }, + ].map((entry) => { + return { + move: entry.move as any, // cast to match type checks in the processGame handler + board: [], + }; + }); + + promotionMetric.processGame(moves); + + expect(promotionMetric.promotionMap.pe.q).toEqual(1); + expect(promotionMetric.promotionMap.PA.q).toEqual(1); + expect(promotionMetric.promotionMap.pb.r).toEqual(1); + }); + + it('should not update the promotion map when a promotion does not occur', () => { + const moves = [ + { + move: { + originalString: 'e4', + color: 'w', + from: 'e2', + to: 'e4', + piece: 'p', + flags: 'b', + uas: 'PE', + }, + }, + { + move: { + originalString: 'e5', + color: 'b', + from: 'e7', + to: 'e5', + piece: 'p', + flags: 'n', + uas: 'pe', + }, + }, + ].map((entry) => { + return { + move: entry.move as any, // cast to match type checks in the processGame handler + board: [], + }; + }); + + promotionMetric.processGame(moves); + let promoTotal = 0; + + for (const k of Object.keys(promotionMetric.promotionMap)) { + for (const promoCount of Object.values( + promotionMetric.promotionMap[k] + )) { + promoTotal += promoCount as number; + } + } + + expect(promoTotal).toEqual(0); + }); + }); });