Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix lint warnings and expand lint coverage to tests #48

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"test": "jest --verbose",
"coverage": "yarn run test -- --coverage",
"postcoverage": "open-cli coverage/lcov-report/index.html",
"lint": "eslint \"src/**/*.ts\"",
"lint": "eslint \"src/**/*.ts\" \"test/*.ts\"",
"lint:fix": "yarn run lint --fix",
"clean": "rimraf dist",
"prebuild": "yarn run clean",
Expand Down
40 changes: 17 additions & 23 deletions src/stats/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,6 @@ function didStartLedgegrab(currentAnimation: State, previousAnimation: State): b

function handleActionCompute(state: PlayerActionState, indices: PlayerIndexedType, frame: FrameEntryType): void {
const playerFrame = frame.players[indices.playerIndex].post;
const incrementCount = (field: string, condition: boolean): void => {
if (!condition) {
return;
}

// FIXME: ActionsCountsType should be a map of actions -> number, instead of accessing the field via string
(state.playerCounts as any)[field] += 1;
};

// Manage animation state
state.animations.push(playerFrame.actionStateId);
Expand All @@ -114,23 +106,25 @@ function handleActionCompute(state: PlayerActionState, indices: PlayerIndexedTyp
const prevAnimation = last3Frames[last3Frames.length - 2];

// Increment counts based on conditions
const didDashDance = _.isEqual(last3Frames, dashDanceAnimations);
incrementCount("dashDanceCount", didDashDance);

const didRoll = didStartRoll(currentAnimation, prevAnimation);
incrementCount("rollCount", didRoll);

const didSpotDodge = didStartSpotDodge(currentAnimation, prevAnimation);
incrementCount("spotDodgeCount", didSpotDodge);

const didAirDodge = didStartAirDodge(currentAnimation, prevAnimation);
incrementCount("airDodgeCount", didAirDodge);

const didGrabLedge = didStartLedgegrab(currentAnimation, prevAnimation);
incrementCount("ledgegrabCount", didGrabLedge);
const playerCounts = state.playerCounts;
if (_.isEqual(last3Frames, dashDanceAnimations)) {
playerCounts.dashDanceCount++;
}
if (didStartRoll(currentAnimation, prevAnimation)) {
playerCounts.rollCount++;
}
if (didStartSpotDodge(currentAnimation, prevAnimation)) {
playerCounts.spotDodgeCount++;
}
if (didStartAirDodge(currentAnimation, prevAnimation)) {
playerCounts.airDodgeCount++;
}
if (didStartLedgegrab(currentAnimation, prevAnimation)) {
playerCounts.ledgegrabCount++;
}

// Handles wavedash detection (and waveland)
handleActionWavedash(state.playerCounts, state.animations);
handleActionWavedash(playerCounts, state.animations);
}

function handleActionWavedash(counts: ActionCountsType, animations: State[]): void {
Expand Down
26 changes: 16 additions & 10 deletions src/stats/combos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,19 @@ function handleComboCompute(
combos: ComboType[],
): void {
const playerFrame: PostFrameUpdateType = frame.players[indices.playerIndex].post;
// FIXME: use type PostFrameUpdateType instead of any
// This is because the default value {} should not be casted as a type of PostFrameUpdateType
const prevPlayerFrame: any = _.get(frames, [playerFrame.frame - 1, "players", indices.playerIndex, "post"], {});
const prevPlayerFrame: PostFrameUpdateType = _.get(frames, [
playerFrame.frame - 1,
"players",
indices.playerIndex,
"post",
]);
const opponentFrame: PostFrameUpdateType = frame.players[indices.opponentIndex].post;
// FIXME: use type PostFrameUpdateType instead of any
// This is because the default value {} should not be casted as a type of PostFrameUpdateType
const prevOpponentFrame: any = _.get(frames, [playerFrame.frame - 1, "players", indices.opponentIndex, "post"], {});
const prevOpponentFrame: PostFrameUpdateType = _.get(frames, [
playerFrame.frame - 1,
"players",
indices.opponentIndex,
"post",
]);

const opntIsDamaged = isDamaged(opponentFrame.actionStateId);
const opntIsGrabbed = isGrabbed(opponentFrame.actionStateId);
Expand All @@ -69,7 +75,7 @@ function handleComboCompute(
// null and null < null = false
const actionChangedSinceHit = playerFrame.actionStateId !== state.lastHitAnimation;
const actionCounter = playerFrame.actionStateCounter;
const prevActionCounter = prevPlayerFrame.actionStateCounter;
const prevActionCounter = _.get(prevPlayerFrame, "actionStateCounter");
const actionFrameCounterReset = actionCounter < prevActionCounter;
if (actionChangedSinceHit || actionFrameCounterReset) {
state.lastHitAnimation = null;
Expand All @@ -84,7 +90,7 @@ function handleComboCompute(
opponentIndex: indices.opponentIndex,
startFrame: playerFrame.frame,
endFrame: null,
startPercent: prevOpponentFrame.percent || 0,
startPercent: _.get(prevOpponentFrame, "percent", 0),
currentPercent: opponentFrame.percent || 0,
endPercent: null,
moves: [],
Expand Down Expand Up @@ -115,7 +121,7 @@ function handleComboCompute(

// Store previous frame animation to consider the case of a trade, the previous
// frame should always be the move that actually connected... I hope
state.lastHitAnimation = prevPlayerFrame.actionStateId;
state.lastHitAnimation = _.get(prevPlayerFrame, "actionStateId");
}
}

Expand Down Expand Up @@ -158,7 +164,7 @@ function handleComboCompute(
// If combo should terminate, mark the end states and add it to list
if (shouldTerminate) {
state.combo.endFrame = playerFrame.frame;
state.combo.endPercent = prevOpponentFrame.percent || 0;
state.combo.endPercent = _.get(prevOpponentFrame, "percent", 0);

state.combo = null;
state.move = null;
Expand Down
26 changes: 16 additions & 10 deletions src/stats/conversions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,19 @@ function handleConversionCompute(
conversions: ConversionType[],
): void {
const playerFrame: PostFrameUpdateType = frame.players[indices.playerIndex].post;
// FIXME: use type PostFrameUpdateType instead of any
// This is because the default value {} should not be casted as a type of PostFrameUpdateType
const prevPlayerFrame: any = _.get(frames, [playerFrame.frame - 1, "players", indices.playerIndex, "post"], {});
const prevPlayerFrame: PostFrameUpdateType = _.get(frames, [
playerFrame.frame - 1,
"players",
indices.playerIndex,
"post",
]);
const opponentFrame: PostFrameUpdateType = frame.players[indices.opponentIndex].post;
// FIXME: use type PostFrameUpdateType instead of any
// This is because the default value {} should not be casted as a type of PostFrameUpdateType
const prevOpponentFrame: any = _.get(frames, [playerFrame.frame - 1, "players", indices.opponentIndex, "post"], {});
const prevOpponentFrame: PostFrameUpdateType = _.get(frames, [
playerFrame.frame - 1,
"players",
indices.opponentIndex,
"post",
]);

const opntIsDamaged = isDamaged(opponentFrame.actionStateId);
const opntIsGrabbed = isGrabbed(opponentFrame.actionStateId);
Expand All @@ -116,7 +122,7 @@ function handleConversionCompute(
// null and null < null = false
const actionChangedSinceHit = playerFrame.actionStateId !== state.lastHitAnimation;
const actionCounter = playerFrame.actionStateCounter;
const prevActionCounter = prevPlayerFrame.actionStateCounter;
const prevActionCounter = _.get(prevPlayerFrame, "actionStateCounter");
const actionFrameCounterReset = actionCounter < prevActionCounter;
if (actionChangedSinceHit || actionFrameCounterReset) {
state.lastHitAnimation = null;
Expand All @@ -131,7 +137,7 @@ function handleConversionCompute(
opponentIndex: indices.opponentIndex,
startFrame: playerFrame.frame,
endFrame: null,
startPercent: prevOpponentFrame.percent || 0,
startPercent: _.get(prevOpponentFrame, "percent", 0),
currentPercent: opponentFrame.percent || 0,
endPercent: null,
moves: [],
Expand Down Expand Up @@ -163,7 +169,7 @@ function handleConversionCompute(

// Store previous frame animation to consider the case of a trade, the previous
// frame should always be the move that actually connected... I hope
state.lastHitAnimation = prevPlayerFrame.actionStateId;
state.lastHitAnimation = _.get(prevPlayerFrame, "actionStateId");
}
}

Expand Down Expand Up @@ -211,7 +217,7 @@ function handleConversionCompute(
// If conversion should terminate, mark the end states and add it to list
if (shouldTerminate) {
state.conversion.endFrame = playerFrame.frame;
state.conversion.endPercent = prevOpponentFrame.percent || 0;
state.conversion.endPercent = _.get(prevOpponentFrame, "percent", 0);

state.conversion = null;
state.move = null;
Expand Down
10 changes: 7 additions & 3 deletions src/stats/stocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import _ from "lodash";

import { isDead, didLoseStock, PlayerIndexedType, StockType } from "./common";
import { FrameEntryType, FramesType } from "../types";
import { FrameEntryType, FramesType, PostFrameUpdateType } from "../types";
import { StatComputer } from "./stats";

interface StockState {
Expand Down Expand Up @@ -44,8 +44,12 @@ function handleStockCompute(
stocks: StockType[],
): void {
const playerFrame = frame.players[indices.playerIndex].post;
// FIXME: use PostFrameUpdateType instead of any
const prevPlayerFrame: any = _.get(frames, [playerFrame.frame - 1, "players", indices.playerIndex, "post"], {});
const prevPlayerFrame: PostFrameUpdateType = _.get(frames, [
playerFrame.frame - 1,
"players",
indices.playerIndex,
"post",
]);

// If there is currently no active stock, wait until the player is no longer spawning.
// Once the player is no longer spawning, start the stock
Expand Down
18 changes: 12 additions & 6 deletions src/utils/slpFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ const DEFAULT_NICKNAME = "unknown";
export interface SlpFileMetadata {
startTime: Moment;
lastFrame: number;
players: any;
players: {
[playerIndex: number]: {
characterUsage: {
[internalCharacterId: number]: number;
};
};
};
consoleNickname?: string;
}

Expand Down Expand Up @@ -111,17 +117,17 @@ export class SlpFile extends Writable {
this.metadata.lastFrame = frame;

// Update character usage
const prevPlayer = get(this.metadata, ["players", `${playerIndex}`]) || {};
const characterUsage = prevPlayer.characterUsage || {};
const curCharFrames = characterUsage[internalCharacterId] || 0;
const characterUsage: {
[internalCharacterId: number]: number;
} = get(this.metadata, ["players", playerIndex, "characterUsage"]);
const curCharFrames = get(characterUsage, internalCharacterId, 0);
const player = {
...prevPlayer,
characterUsage: {
...characterUsage,
[internalCharacterId]: curCharFrames + 1,
},
};
this.metadata.players[`${playerIndex}`] = player;
this.metadata.players[playerIndex] = player;
break;
}
}
Expand Down
45 changes: 34 additions & 11 deletions test/filewriter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import fs from "fs";
import { openSlpFile, SlpInputSource } from "../src/utils/slpReader";
import { SlpFileWriter } from "../src";
import { SlpFileWriter, SlippiGame } from "../src";
import { Writable } from "stream";

// On my machine, >100 is required to give the slpFile.ts "finish" callback time to execute.
// I thought a 'yield' 0 ms setTimout would allow the callback to execute, but that's not the case.
const TIMEOUT_MS = 1000;

describe("when ending SlpFileWriter", () => {
it("should write data length to file", async () => {
const testFilePath = "slp/finalizedFrame.slp";
Expand All @@ -14,24 +18,44 @@ describe("when ending SlpFileWriter", () => {

const testFd = fs.openSync(testFilePath, "r");
const newPos = pipeMessageSizes(testFd, dataPos, slpFileWriter);

const newFilename = slpFileWriter.getCurrentFilename();
const buffer = Buffer.alloc(4);

pipeAllEvents(testFd, newPos, dataPos + dataLength, slpFileWriter, slpFile.messageSizes);
await new Promise((resolve) => {
// On my machine, >100 is required to give the slpFile.ts "finish" callback time to execute.
// I thought a 'yield' 0 ms setTimout would allow the callback to execute, but that's not the case.
const timeoutMs = 1000;

await new Promise((resolve): void => {
setTimeout(() => {
const writtenDataLength = openSlpFile({ source: SlpInputSource.FILE, filePath: newFilename }).rawDataLength;
fs.unlinkSync(newFilename);

expect(writtenDataLength).toBe(dataLength);

resolve();
}, timeoutMs);
}, TIMEOUT_MS);
});
});

it("should track and write player data to metadata in file", async () => {
const testFilePath = "slp/finalizedFrame.slp";

const slpFileWriter = new SlpFileWriter();
const slpFile = openSlpFile({ source: SlpInputSource.FILE, filePath: testFilePath });
const dataPos = slpFile.rawDataPosition;

const testFd = fs.openSync(testFilePath, "r");
const newPos = pipeMessageSizes(testFd, dataPos, slpFileWriter);
const newFilename = slpFileWriter.getCurrentFilename();

pipeAllEvents(testFd, newPos, dataPos + slpFile.rawDataLength, slpFileWriter, slpFile.messageSizes);
await new Promise((resolve): void => {
setTimeout(() => {
const players = new SlippiGame(newFilename).getMetadata().players;
fs.unlinkSync(newFilename);

expect(Object.keys(players).length).toBe(2);
expect(players[0].characters).toEqual({ 0: 17558 });
expect(players[1].characters).toEqual({ 1: 17558 });

resolve();
}, TIMEOUT_MS);
});
});
});
Expand Down Expand Up @@ -59,13 +83,12 @@ const pipeAllEvents = function (
messageSizes: {
[command: number]: number;
},
) {
): void {
let pos = start;
while (pos < end) {
const commandByteBuffer = new Uint8Array(1);
fs.readSync(fd, commandByteBuffer, 0, 1, pos);
const length = messageSizes[commandByteBuffer[0]] + 1;
const commandByte = commandByteBuffer[0];

const buffer = new Uint8Array(length);
fs.readSync(fd, buffer, 0, length, pos);
Expand Down
16 changes: 13 additions & 3 deletions test/game.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from "lodash";
// import path from 'path';
import fs from "fs";
import { SlippiGame } from "../src";
import { FrameEntryType, FramesType, GameEndType, GameStartType, MetadataType, SlippiGame, StatsType } from "../src";

it("should correctly return game settings", () => {
const game = new SlippiGame("slp/sheik_vs_ics_yoshis.slp");
Expand Down Expand Up @@ -34,6 +34,10 @@ it("should correctly return stats", () => {
expect(stats.actionCounts[0].wavedashCount).toBe(16);
expect(stats.actionCounts[0].wavelandCount).toBe(1);
expect(stats.actionCounts[0].airDodgeCount).toBe(3);
expect(stats.actionCounts[0].dashDanceCount).toBe(39);
expect(stats.actionCounts[0].spotDodgeCount).toBe(0);
expect(stats.actionCounts[0].ledgegrabCount).toBe(1);
expect(stats.actionCounts[0].rollCount).toBe(0);

// Test overall
expect(stats.overall[0].inputCounts.total).toBe(494);
Expand Down Expand Up @@ -82,7 +86,6 @@ it("should be able to read netplay names and codes", () => {

it("should be able to read console nickname", () => {
const game = new SlippiGame("slp/realtimeTest.slp");
const metadata = game.getMetadata().consoleNick;
expect(game.getMetadata().consoleNick).toBe("Day 1");
});

Expand Down Expand Up @@ -119,7 +122,14 @@ it("should support realtime parsing", () => {
let data,
copyPos = 0;

const getData = () => ({
const getData = (): {
settings: GameStartType;
frames: FramesType;
metadata: MetadataType;
gameEnd: GameEndType | null;
stats: StatsType;
latestFrame: FrameEntryType | null;
} => ({
settings: game.getSettings(),
frames: game.getFrames(),
metadata: game.getMetadata(),
Expand Down
4 changes: 2 additions & 2 deletions test/realtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ describe("when reading finalised frames from SlpParser", () => {
});
});

const pipeFileContents = async (filename: string, destination: Writable, options?: any): Promise<void> => {
const pipeFileContents = async (filename: string, destination: Writable): Promise<void> => {
return new Promise((resolve): void => {
const readStream = fs.createReadStream(filename);
readStream.on("open", () => {
readStream.pipe(destination, options);
readStream.pipe(destination);
});
readStream.on("close", () => {
resolve();
Expand Down