Skip to content

Commit 52c22a7

Browse files
authored
feat: add getGameProgression() (#130)
1 parent 18c0e7e commit 52c22a7

7 files changed

Lines changed: 294 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Click the function names to open their complete docs on the docs site.
9898
- [`getGame()`](https://api-docs.retroachievements.org/v1/get-game.html) - Get basic metadata about a game.
9999
- [`getGameExtended()`](https://api-docs.retroachievements.org/v1/get-game-extended.html) - Get extended metadata about a game.
100100
- [`getGameHashes()`](https://api-docs.retroachievements.org/v1/get-game-hashes.html) - Get a list of hashes linked to a game.
101+
- [`getGameProgression()`](https://api-docs.retroachievements.org/v1/get-game-progression.html) - Get information about the average time to unlock achievements in a game.
101102
- [`getAchievementCount()`](https://api-docs.retroachievements.org/v1/get-achievement-count.html) - Get the list of achievement IDs for a game.
102103
- [`getAchievementDistribution()`](https://api-docs.retroachievements.org/v1/get-achievement-distribution.html) - Get how many players have unlocked how many achievements for a game.
103104
- [`getGameRankAndScore()`](https://api-docs.retroachievements.org/v1/get-game-rank-and-score.html) - Get a list of either the latest masters or highest hardcore points earners for a game.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/* eslint-disable sonarjs/no-duplicate-string */
2+
3+
import { http, HttpResponse } from "msw";
4+
import { setupServer } from "msw/node";
5+
6+
import { apiBaseUrl } from "../utils/internal";
7+
import { buildAuthorization } from "../utils/public";
8+
import { getGameProgression } from "./getGameProgression";
9+
import type { GetGameProgressionResponse } from "./models";
10+
11+
const server = setupServer();
12+
13+
describe("Function: getGameProgression", () => {
14+
// MSW Setup
15+
beforeAll(() => server.listen());
16+
afterEach(() => server.resetHandlers());
17+
afterAll(() => server.close());
18+
19+
it("is defined #sanity", () => {
20+
// ASSERT
21+
expect(getGameProgression).toBeDefined();
22+
});
23+
24+
it("given a game ID, retrieves information about the average time to unlock achievements in a game", async () => {
25+
// ARRANGE
26+
const authorization = buildAuthorization({
27+
username: "mockUserName",
28+
webApiKey: "mockWebApiKey",
29+
});
30+
31+
const mockResponse: GetGameProgressionResponse = {
32+
ID: 228,
33+
Title: "Super Mario World",
34+
ConsoleID: 3,
35+
ConsoleName: "SNES/Super Famicom",
36+
ImageIcon: "/Images/112443.png",
37+
NumDistinctPlayers: 79_281,
38+
TimesUsedInBeatMedian: 4493,
39+
TimesUsedInHardcoreBeatMedian: 8249,
40+
MedianTimeToBeat: 17_878,
41+
MedianTimeToBeatHardcore: 19_224,
42+
TimesUsedInCompletionMedian: 155,
43+
TimesUsedInMasteryMedian: 1091,
44+
MedianTimeToComplete: 67_017,
45+
MedianTimeToMaster: 79_744,
46+
NumAchievements: 89,
47+
Achievements: [
48+
{
49+
ID: 342,
50+
Title: "Giddy Up!",
51+
Description: "Catch a ride with a friend",
52+
Points: 1,
53+
TrueRatio: 1,
54+
Type: null,
55+
BadgeName: "46580",
56+
NumAwarded: 75_168,
57+
NumAwardedHardcore: 37_024,
58+
TimesUsedInUnlockMedian: 63,
59+
TimesUsedInHardcoreUnlockMedian: 69,
60+
MedianTimeToUnlock: 274,
61+
MedianTimeToUnlockHardcore: 323,
62+
},
63+
],
64+
};
65+
66+
server.use(
67+
http.get(`${apiBaseUrl}/API_GetGameProgression.php`, () =>
68+
HttpResponse.json(mockResponse)
69+
)
70+
);
71+
72+
// ACT
73+
const response = await getGameProgression(authorization, {
74+
gameId: 104_370,
75+
hardcore: true,
76+
});
77+
78+
// ASSERT
79+
expect(response).toEqual({
80+
id: 228,
81+
title: "Super Mario World",
82+
consoleId: 3,
83+
consoleName: "SNES/Super Famicom",
84+
imageIcon: "/Images/112443.png",
85+
numDistinctPlayers: 79_281,
86+
timesUsedInBeatMedian: 4493,
87+
timesUsedInHardcoreBeatMedian: 8249,
88+
medianTimeToBeat: 17_878,
89+
medianTimeToBeatHardcore: 19_224,
90+
timesUsedInCompletionMedian: 155,
91+
timesUsedInMasteryMedian: 1091,
92+
medianTimeToComplete: 67_017,
93+
medianTimeToMaster: 79_744,
94+
numAchievements: 89,
95+
achievements: [
96+
{
97+
id: 342,
98+
title: "Giddy Up!",
99+
description: "Catch a ride with a friend",
100+
points: 1,
101+
trueRatio: 1,
102+
type: null,
103+
badgeName: "46580",
104+
numAwarded: 75_168,
105+
numAwardedHardcore: 37_024,
106+
timesUsedInUnlockMedian: 63,
107+
timesUsedInHardcoreUnlockMedian: 69,
108+
medianTimeToUnlock: 274,
109+
medianTimeToUnlockHardcore: 323,
110+
},
111+
],
112+
});
113+
});
114+
});

src/game/getGameProgression.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import type { ID } from "../utils/internal";
2+
import {
3+
apiBaseUrl,
4+
buildRequestUrl,
5+
call,
6+
serializeProperties,
7+
} from "../utils/internal";
8+
import type { AuthObject } from "../utils/public";
9+
import type { GameProgression, GetGameProgressionResponse } from "./models";
10+
11+
/**
12+
* A call to this function will retrieve information about the average time to unlock achievements in a game.
13+
*
14+
* @param authorization An object containing your username and webApiKey.
15+
* This can be constructed with `buildAuthorization()`.
16+
*
17+
* @param payload.gameId The unique game ID. If you are unsure, open the
18+
* game's page on the RetroAchievements.org website. For example, Dragster's
19+
* URL is https://retroachievements.org/game/14402. We can see from the
20+
* URL that the game ID is "14402".
21+
*
22+
* @param payload.hardcore Optional. If set to true, the player sampling
23+
* for median calculations will prefer players based on their hardcore
24+
* unlock count rather than their total unlock count.
25+
*
26+
* @example
27+
* ```
28+
* const game = await getGameProgression(
29+
* authorization,
30+
* { gameId: 14402, hardcore: true }
31+
* );
32+
* ```
33+
*
34+
* @returns An object containing information about the average time to unlock achievements in a game.
35+
* ```json
36+
* {
37+
* "id": 228,
38+
* "title": "Super Mario World",
39+
* "consoleId": 3,
40+
* "consoleName": "SNES/Super Famicom",
41+
* "imageIcon": "/Images/112443.png",
42+
* "numDistinctPlayers": 79281,
43+
* "timesUsedInBeatMedian": 4493,
44+
* "timesUsedInHardcoreBeatMedian": 8249,
45+
* "medianTimeToBeat": 17878,
46+
* "medianTimeToBeatHardcore": 19224,
47+
* "timesUsedInCompletionMedian": 155,
48+
* "timesUsedInMasteryMedian": 1091,
49+
* "medianTimeToComplete": 67017,
50+
* "medianTimeToMaster": 79744,
51+
* "numAchievements": 89,
52+
* "achievements": [
53+
* {
54+
* "id": 342,
55+
* "title": "Giddy Up!",
56+
* "description": "Catch a ride with a friend",
57+
* "points": 1,
58+
* "trueRatio": 1,
59+
* "type": null,
60+
* "badgeName": "46580",
61+
* "numAwarded": 75168,
62+
* "numAwardedHardcore": 37024,
63+
* "timesUsedInUnlockMedian": 63,
64+
* "timesUsedInHardcoreUnlockMedian": 69,
65+
* "medianTimeToUnlock": 274,
66+
* "medianTimeToUnlockHardcore": 323
67+
* },
68+
* {
69+
* "id": 341,
70+
* "title": "Unleash The Dragon",
71+
* "description": "Collect 5 Dragon Coins in a level",
72+
* "points": 2,
73+
* "trueRatio": 2,
74+
* "type": null,
75+
* "badgeName": "46591",
76+
* "numAwarded": 66647,
77+
* "numAwardedHardcore": 34051,
78+
* "timesUsedInUnlockMedian": 66,
79+
* "timesUsedInHardcoreUnlockMedian": 70,
80+
* "medianTimeToUnlock": 290,
81+
* "medianTimeToUnlockHardcore": 333
82+
* }
83+
* ]
84+
* }
85+
* ```
86+
*/
87+
export const getGameProgression = async (
88+
authorization: AuthObject,
89+
payload: {
90+
gameId: ID;
91+
hardcore?: boolean;
92+
}
93+
): Promise<GameProgression> => {
94+
const { gameId, hardcore } = payload;
95+
96+
const queryParams: Record<string, any> = { i: gameId };
97+
98+
if (hardcore !== undefined) {
99+
queryParams["h"] = hardcore === true ? 1 : 0;
100+
}
101+
102+
const url = buildRequestUrl(
103+
apiBaseUrl,
104+
"/API_GetGameProgression.php",
105+
authorization,
106+
queryParams
107+
);
108+
109+
const rawResponse = await call<GetGameProgressionResponse>({ url });
110+
111+
return serializeProperties(rawResponse);
112+
};

src/game/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./getAchievementDistribution";
33
export * from "./getGame";
44
export * from "./getGameExtended";
55
export * from "./getGameHashes";
6+
export * from "./getGameProgression";
67
export * from "./getGameRankAndScore";
78
export * from "./getGameRating";
89
export * from "./models";
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export interface GameProgression {
2+
id: number;
3+
title: string;
4+
consoleId: number;
5+
consoleName: string;
6+
imageIcon: string;
7+
numDistinctPlayers: number;
8+
timesUsedInBeatMedian: number;
9+
timesUsedInHardcoreBeatMedian: number;
10+
medianTimeToBeat: number | null;
11+
medianTimeToBeatHardcore: number | null;
12+
timesUsedInCompletionMedian: number;
13+
timesUsedInMasteryMedian: number;
14+
medianTimeToComplete: number | null;
15+
medianTimeToMaster: number | null;
16+
numAchievements: number;
17+
achievements: Array<{
18+
id: number;
19+
title: string;
20+
description: string;
21+
points: number;
22+
trueRatio: number;
23+
type: string | null;
24+
badgeName: string;
25+
numAwarded: number;
26+
numAwardedHardcore: number;
27+
timesUsedInUnlockMedian: number;
28+
timesUsedInHardcoreUnlockMedian: number;
29+
medianTimeToUnlock: number;
30+
medianTimeToUnlockHardcore: number;
31+
}>;
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export interface GetGameProgressionResponse {
2+
ID: number;
3+
Title: string;
4+
ConsoleID: number;
5+
ConsoleName: string;
6+
ImageIcon: string;
7+
NumDistinctPlayers: number;
8+
TimesUsedInBeatMedian: number;
9+
TimesUsedInHardcoreBeatMedian: number;
10+
MedianTimeToBeat: number | null;
11+
MedianTimeToBeatHardcore: number | null;
12+
TimesUsedInCompletionMedian: number;
13+
TimesUsedInMasteryMedian: number;
14+
MedianTimeToComplete: number | null;
15+
MedianTimeToMaster: number | null;
16+
NumAchievements: number;
17+
Achievements: Array<{
18+
ID: number;
19+
Title: string;
20+
Description: string;
21+
Points: number;
22+
TrueRatio: number;
23+
Type: string | null;
24+
BadgeName: string;
25+
NumAwarded: number;
26+
NumAwardedHardcore: number;
27+
TimesUsedInUnlockMedian: number;
28+
TimesUsedInHardcoreUnlockMedian: number;
29+
MedianTimeToUnlock: number;
30+
MedianTimeToUnlockHardcore: number;
31+
}>;
32+
}

src/game/models/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ export * from "./game-extended.model";
55
export * from "./game-extended-achievement-entity.model";
66
export * from "./game-extended-claim-entity.model";
77
export * from "./game-hashes.model";
8+
export * from "./game-progression.model";
89
export * from "./game-rank-and-score-entity.model";
910
export * from "./game-rating.model";
1011
export * from "./get-achievement-count-response.model";
1112
export * from "./get-achievement-distribution-response.model";
1213
export * from "./get-game-extended-response.model";
1314
export * from "./get-game-hashes-response.model";
15+
export * from "./get-game-progression-response.model";
1416
export * from "./get-game-rank-and-score-response.model";
1517
export * from "./get-game-rating-response.model";
1618
export * from "./get-game-response.model";

0 commit comments

Comments
 (0)