Skip to content

Commit

Permalink
feat: switch back to combos/conversions by player being combo'd
Browse files Browse the repository at this point in the history
  • Loading branch information
vinceau committed May 12, 2021
1 parent d1b34c0 commit 50f5d5c
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 74 deletions.
1 change: 0 additions & 1 deletion src/stats/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class ActionsComputer implements StatComputer<ActionCountsType[]> {
this.playerPermutations.forEach((indices) => {
const playerCounts: ActionCountsType = {
playerIndex: indices.playerIndex,
opponentIndex: indices.opponentIndex,
wavedashCount: 0,
wavelandCount: 0,
airDodgeCount: 0,
Expand Down
5 changes: 3 additions & 2 deletions src/stats/combos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ function handleComboCompute(
let comboStarted = false;
if (!state.combo) {
state.combo = {
playerIndex: indices.playerIndex,
opponentIndex: indices.opponentIndex,
playerIndex: indices.opponentIndex,
startFrame: currentFrameNumber,
endFrame: null,
startPercent: prevOpponentFrame ? prevOpponentFrame.percent ?? 0 : 0,
currentPercent: opponentFrame.percent ?? 0,
endPercent: null,
moves: [],
didKill: false,
lastHitBy: indices.playerIndex,
};

combos.push(state.combo);
Expand All @@ -149,6 +149,7 @@ function handleComboCompute(
// prevents counting multiple hits from the same move such as fox's drill
if (state.lastHitAnimation === null) {
state.move = {
playerIndex: indices.playerIndex,
frame: currentFrameNumber,
moveId: playerFrame.lastAttackLanded!,
hitCount: 0,
Expand Down
20 changes: 12 additions & 8 deletions src/stats/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,33 @@ export interface DamageType {
endPercent?: number | null;
}

export interface StockType extends PlayerIndexedType, DurationType, DamageType {
export interface StockType extends DurationType, DamageType {
playerIndex: number;
count: number;
deathAnimation?: number | null;
}

export interface MoveLandedType {
playerIndex: number;
frame: number;
moveId: number;
hitCount: number;
damage: number;
}

export interface ConversionType extends PlayerIndexedType, DurationType, DamageType {
export interface ComboType extends DurationType, DamageType {
playerIndex: number;
moves: MoveLandedType[];
openingType: string;
didKill: boolean;
lastHitBy: number | null;
}

export interface ComboType extends PlayerIndexedType, DurationType, DamageType {
moves: MoveLandedType[];
didKill: boolean;
export interface ConversionType extends ComboType {
openingType: string;
}

export interface ActionCountsType extends PlayerIndexedType {
export interface ActionCountsType {
playerIndex: number;
wavedashCount: number;
wavelandCount: number;
airDodgeCount: number;
Expand Down Expand Up @@ -90,7 +93,8 @@ export interface InputCountsType {
total: number;
}

export interface OverallType extends PlayerIndexedType {
export interface OverallType {
playerIndex: number;
inputCounts: InputCountsType;
conversionCount: number;
totalDamage: number;
Expand Down
10 changes: 7 additions & 3 deletions src/stats/conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ export class ConversionComputer extends EventEmitter implements StatComputer<Con
}

// If not trade, check the opponent endFrame
const oppEndFrame = this.metadata.lastEndFrameByOppIdx[conversion.opponentIndex];
const lastMove = _.last(conversion.moves);
const oppEndFrame = this.metadata.lastEndFrameByOppIdx[
lastMove ? lastMove.playerIndex : conversion.playerIndex
];
const isCounterAttack = oppEndFrame && oppEndFrame > conversion.startFrame;
conversion.openingType = isCounterAttack ? "counter-attack" : "neutral-win";
});
Expand Down Expand Up @@ -164,8 +167,8 @@ function handleConversionCompute(
if (opntIsDamaged || opntIsGrabbed || opntIsCommandGrabbed) {
if (!state.conversion) {
state.conversion = {
playerIndex: indices.playerIndex,
opponentIndex: indices.opponentIndex,
playerIndex: indices.opponentIndex,
lastHitBy: indices.playerIndex,
startFrame: currentFrameNumber,
endFrame: null,
startPercent: prevOpponentFrame ? prevOpponentFrame.percent ?? 0 : 0,
Expand All @@ -184,6 +187,7 @@ function handleConversionCompute(
// prevents counting multiple hits from the same move such as fox's drill
if (state.lastHitAnimation === null) {
state.move = {
playerIndex: indices.playerIndex,
frame: currentFrameNumber,
moveId: playerFrame.lastAttackLanded!,
hitCount: 0,
Expand Down
106 changes: 66 additions & 40 deletions src/stats/overall.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import _ from "lodash";

import { GameStartType } from "../types";
import {
ConversionType,
getSinglesPlayerPermutationsFromSettings,
InputCountsType,
OverallType,
RatioType,
StockType,
} from "./common";
import { ConversionType, InputCountsType, OverallType, RatioType, StockType } from "./common";
import { PlayerInput } from "./inputs";

interface ConversionsByPlayerByOpening {
Expand All @@ -25,18 +18,17 @@ export function generateOverallStats(
playableFrameCount: number,
): OverallType[] {
const inputsByPlayer = _.keyBy(inputs, "playerIndex");
const stocksByPlayer = _.groupBy(stocks, "playerIndex");
const conversionsByPlayer = _.groupBy(conversions, "playerIndex");
const originalConversions = conversions;
const conversionsByPlayer = _.groupBy(conversions, (conv) => conv.moves[0]?.playerIndex);
const conversionsByPlayerByOpening: ConversionsByPlayerByOpening = _.mapValues(conversionsByPlayer, (conversions) =>
_.groupBy(conversions, "openingType"),
);

const gameMinutes = playableFrameCount / 3600;

const playerIndices = getSinglesPlayerPermutationsFromSettings(settings);
const overall = playerIndices.map((indices) => {
const playerIndex = indices.playerIndex;
const opponentIndex = indices.opponentIndex;
const overall = settings.players.map((player) => {
const playerIndex = player.playerIndex;

const playerInputs = _.get(inputsByPlayer, playerIndex) || {};
const inputCounts: InputCountsType = {
buttons: _.get(playerInputs, "buttonInputCount"),
Expand All @@ -45,21 +37,49 @@ export function generateOverallStats(
joystick: _.get(playerInputs, "joystickInputCount"),
total: _.get(playerInputs, "inputCount"),
};

const conversions = _.get(conversionsByPlayer, playerIndex) || [];
const successfulConversions = conversions.filter((conversion) => conversion.moves.length > 1);
const opponentStocks = _.get(stocksByPlayer, opponentIndex) || [];
const opponentEndedStocks = _.filter(opponentStocks, "endFrame");

const conversionCount = conversions.length;
const successfulConversionCount = successfulConversions.length;
const totalDamage =
_.sumBy(conversions, (conversion) => conversion.moves.reduce((total, move) => total + move.damage, 0)) || 0;
const killCount = opponentEndedStocks.length;
// const conversions = _.get(conversionsByPlayer, playerIndex) || [];
// const successfulConversions = conversions.filter((conversion) => conversion.moves.length > 1);
let conversionCount = 0;
let successfulConversionCount = 0;

const opponentIndices = settings.players
.filter((opp) => {
// We want players which aren't ourselves
if (opp.playerIndex === playerIndex) {
return false;
}

// Make sure they're not on our team either
return !settings.isTeams || opp.teamId !== player.teamId;
})
.map((opp) => opp.playerIndex);

let totalDamage = 0;
let killCount = 0;

// These are the conversions that we did on our opponents
originalConversions
// Filter down to conversions of our opponent
.filter((conversion) => conversion.playerIndex !== playerIndex)
.forEach((conversion) => {
conversionCount++;

// We killed the opponent
if (conversion.didKill && conversion.lastHitBy === playerIndex) {
killCount += 1;
}
if (conversion.moves.length > 1 && conversion.moves[0].playerIndex === playerIndex) {
successfulConversionCount++;
}
conversion.moves.forEach((move) => {
if (move.playerIndex === playerIndex) {
totalDamage += move.damage;
}
});
});

return {
playerIndex: playerIndex,
opponentIndex: opponentIndex,
inputCounts: inputCounts,
conversionCount: conversionCount,
totalDamage: totalDamage,
Expand All @@ -70,9 +90,9 @@ export function generateOverallStats(
digitalInputsPerMinute: getRatio(inputCounts.buttons, gameMinutes),
openingsPerKill: getRatio(conversionCount, killCount),
damagePerOpening: getRatio(totalDamage, conversionCount),
neutralWinRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndex, "neutral-win"),
counterHitRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndex, "counter-attack"),
beneficialTradeRatio: getBeneficialTradeRatio(conversionsByPlayerByOpening, playerIndex, opponentIndex),
neutralWinRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices, "neutral-win"),
counterHitRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices, "counter-attack"),
beneficialTradeRatio: getBeneficialTradeRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices),
};
});

Expand All @@ -90,23 +110,27 @@ function getRatio(count: number, total: number): RatioType {
function getOpeningRatio(
conversionsByPlayerByOpening: ConversionsByPlayerByOpening,
playerIndex: number,
opponentIndex: number,
opponentIndices: number[],
type: string,
): RatioType {
const openings = _.get(conversionsByPlayerByOpening, [playerIndex, type]) || [];

const opponentOpenings = _.get(conversionsByPlayerByOpening, [opponentIndex, type]) || [];
const opponentOpenings = _.flatten(
opponentIndices.map((opponentIndex) => _.get(conversionsByPlayerByOpening, [opponentIndex, type]) || []),
);

return getRatio(openings.length, openings.length + opponentOpenings.length);
}

function getBeneficialTradeRatio(
conversionsByPlayerByOpening: ConversionsByPlayerByOpening,
playerIndex: number,
opponentIndex: number,
opponentIndices: number[],
): RatioType {
const playerTrades = _.get(conversionsByPlayerByOpening, [playerIndex, "trade"]) || [];
const opponentTrades = _.get(conversionsByPlayerByOpening, [opponentIndex, "trade"]) || [];
const opponentTrades = _.flatten(
opponentIndices.map((opponentIndex) => _.get(conversionsByPlayerByOpening, [opponentIndex, "trade"]) || []),
);

const benefitsPlayer = [];

Expand All @@ -115,13 +139,15 @@ function getBeneficialTradeRatio(
zippedTrades.forEach((conversionPair) => {
const playerConversion = _.first(conversionPair);
const opponentConversion = _.last(conversionPair);
const playerDamage = playerConversion!.currentPercent - playerConversion!.startPercent;
const opponentDamage = opponentConversion!.currentPercent - opponentConversion!.startPercent;

if (playerConversion!.didKill && !opponentConversion!.didKill) {
benefitsPlayer.push(playerConversion);
} else if (playerDamage > opponentDamage) {
benefitsPlayer.push(playerConversion);
if (playerConversion && opponentConversion) {
const playerDamage = playerConversion.currentPercent - playerConversion.startPercent;
const opponentDamage = opponentConversion.currentPercent - opponentConversion.startPercent;

if (playerConversion!.didKill && !opponentConversion!.didKill) {
benefitsPlayer.push(playerConversion);
} else if (playerDamage > opponentDamage) {
benefitsPlayer.push(playerConversion);
}
}
});

Expand Down
1 change: 0 additions & 1 deletion src/stats/stocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ function handleStockCompute(

state.stock = {
playerIndex: indices.playerIndex,
opponentIndex: indices.opponentIndex,
startFrame: currentFrameNumber,
endFrame: null,
startPercent: 0,
Expand Down
14 changes: 7 additions & 7 deletions test/conversion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe("when calculating conversions", () => {
const puff = stats.overall[0];
let totalDamagePuffDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === puff.playerIndex) {
if (conversion.lastHitBy === puff.playerIndex) {
totalDamagePuffDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -22,7 +22,7 @@ describe("when calculating conversions", () => {
const bowser = stats.overall[0];
let totalDamageBowserDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === bowser.playerIndex) {
if (conversion.lastHitBy === bowser.playerIndex) {
totalDamageBowserDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -39,7 +39,7 @@ describe("when calculating conversions", () => {
const falcon = stats.overall[0];
let totalDamageFalconDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === falcon.playerIndex) {
if (conversion.lastHitBy === falcon.playerIndex) {
totalDamageFalconDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -54,7 +54,7 @@ describe("when calculating conversions", () => {
const ganon = stats.overall[0];
let totalDamageGanonDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === ganon.playerIndex) {
if (conversion.lastHitBy === ganon.playerIndex) {
totalDamageGanonDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -69,7 +69,7 @@ describe("when calculating conversions", () => {
const kirby = stats.overall[0];
let totalDamageKirbyDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === kirby.playerIndex) {
if (conversion.lastHitBy === kirby.playerIndex) {
totalDamageKirbyDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -84,7 +84,7 @@ describe("when calculating conversions", () => {
const yoshi = stats.overall[0];
let totalDamageYoshiDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === yoshi.playerIndex) {
if (conversion.lastHitBy === yoshi.playerIndex) {
totalDamageYoshiDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -99,7 +99,7 @@ describe("when calculating conversions", () => {
const mewTwo = stats.overall[0];
let totalDamageMewTwoDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === mewTwo.playerIndex) {
if (conversion.lastHitBy === mewTwo.playerIndex) {
totalDamageMewTwoDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand Down
20 changes: 8 additions & 12 deletions test/stats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe("when calculating stats", () => {
const yl = stats.overall[1];
let totalDamagePuffDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === puff.playerIndex) {
if (conversion.lastHitBy === puff.playerIndex) {
totalDamagePuffDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand All @@ -87,15 +87,11 @@ describe("when calculating stats", () => {
let totalDamagePichuDealt = 0;
let icsDamageDealt = 0;
stats.conversions.forEach((conversion) => {
switch (conversion.playerIndex) {
case pichu.playerIndex: {
totalDamagePichuDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
break;
}
case ics.playerIndex: {
icsDamageDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
break;
}
if (conversion.playerIndex === pichu.playerIndex) {
icsDamageDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
if (conversion.playerIndex === ics.playerIndex) {
totalDamagePichuDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
// Pichu should have done at least 32% damage
Expand All @@ -117,10 +113,10 @@ describe("when calculating stats", () => {
let totalDamageNessDealt = 0;
let totalDamageFoxDealt = 0;
stats.conversions.forEach((conversion) => {
if (conversion.playerIndex === ness.playerIndex) {
if (conversion.lastHitBy === ness.playerIndex) {
totalDamageNessDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
if (conversion.playerIndex === fox.playerIndex) {
if (conversion.lastHitBy === fox.playerIndex) {
totalDamageFoxDealt += conversion.moves.reduce((total, move) => total + move.damage, 0);
}
});
Expand Down

0 comments on commit 50f5d5c

Please sign in to comment.