Show "Replay" indicator in GroupCard

This commit is contained in:
Kalle
2022-03-03 17:39:01 +02:00
parent 8ff5e07db0
commit 10468ff8e0
7 changed files with 207 additions and 11 deletions

View File

@@ -74,7 +74,10 @@ export function GroupCard({
{group.teamMMR.value}
</div>
)}
{group.MMRRelation && <MMRRelation relation={group.MMRRelation} />}
{group.MMRRelation && !group.replay && (
<MMRRelation relation={group.MMRRelation} />
)}
{group.replay && <div className="play__card__replay">Replay</div>}
<input type="hidden" name="targetGroupId" value={group.id} />
{action === "UNITE_GROUPS" && (
<input

View File

@@ -4,6 +4,7 @@ import { BIT_HIGHER_MMR_LIMIT } from "~/constants";
import {
calculateDifference,
groupsToWinningAndLosingPlayerIds,
isMatchReplay,
scoresAreIdentical,
uniteGroupInfo,
UniteGroupInfoArg,
@@ -15,6 +16,7 @@ const GroupsToWinningAndLosingPlayerIds = suite(
"groupsToWinningAndLosingPlayerIds()"
);
const CalculateDifference = suite("calculateDifference()");
const IsMatchReplay = suite("isMatchReplay()");
const SMALL_GROUP: UniteGroupInfoArg = { id: "small", memberCount: 1 };
const BIG_GROUP: UniteGroupInfoArg = { id: "big", memberCount: 3 };
@@ -110,7 +112,7 @@ CalculateDifference("Higher/lower", () => {
assert.equal(calculateDifference({ ourMMR: 10_000, theirMMR: 0 }), "LOWER");
});
CalculateDifference.only("A bit higher/lower", () => {
CalculateDifference("A bit higher/lower", () => {
assert.equal(
calculateDifference({ ourMMR: 0, theirMMR: BIT_HIGHER_MMR_LIMIT }),
"BIT_HIGHER"
@@ -121,7 +123,67 @@ CalculateDifference.only("A bit higher/lower", () => {
);
});
const user = { id: "a" };
const createMembers = (input: string[]) => input.map((v) => ({ memberId: v }));
IsMatchReplay("Detects replays", () => {
assert.ok(
isMatchReplay({
user,
recentMatch: {
groups: [
{ members: createMembers(["a", "b", "c", "d"]) },
{ members: createMembers(["e", "f", "g", "h"]) },
],
},
group: { members: createMembers(["e", "f", "g", "h"]) },
})
);
assert.ok(
isMatchReplay({
user,
recentMatch: {
groups: [
{ members: createMembers(["e", "f", "g", "h"]) },
{ members: createMembers(["a", "b", "c", "d"]) },
],
},
group: { members: createMembers(["e", "f", "g", "1"]) },
})
);
});
IsMatchReplay("Detects not replays", () => {
assert.not.ok(
isMatchReplay({
user,
recentMatch: {
groups: [
{ members: createMembers(["a", "b", "c", "d"]) },
{ members: createMembers(["e", "f", "g", "h"]) },
],
},
group: { members: createMembers(["1", "2", "3", "4"]) },
})
);
assert.not.ok(
isMatchReplay({
user,
recentMatch: {
groups: [
{ members: createMembers(["a", "b", "c", "d"]) },
{ members: createMembers(["e", "f", "g", "h"]) },
],
},
group: { members: createMembers(["e", "f", "3", "4"]) },
})
);
});
UniteGroupInfo.run();
ScoresAreIdentical.run();
GroupsToWinningAndLosingPlayerIds.run();
CalculateDifference.run();
IsMatchReplay.run();

View File

@@ -6,6 +6,7 @@ import {
LFG_GROUP_INACTIVE_MINUTES,
} from "~/constants";
import * as LFGGroup from "~/models/LFGGroup.server";
import * as LFGMatch from "~/models/LFGMatch.server";
import { PlayFrontPageLoader } from "~/routes/play/index";
import {
LookingLoaderData,
@@ -132,6 +133,8 @@ export function otherGroupsForResponse({
lookingForMatch,
ownGroup,
useRelativeSkillLevel,
recentMatch,
user,
}: {
groups: LFGGroup.FindLookingAndOwnActive;
likes: {
@@ -141,6 +144,8 @@ export function otherGroupsForResponse({
lookingForMatch: boolean;
ownGroup: Unpacked<LFGGroup.FindLookingAndOwnActive>;
useRelativeSkillLevel: boolean;
recentMatch: LFGMatch.RecentOfUser;
user: { id: string };
}) {
return (
groups
@@ -179,6 +184,7 @@ export function otherGroupsForResponse({
};
}),
ranked: ranked(),
replay: isMatchReplay({ user, group, recentMatch }),
MMRRelation:
ownGroup.ranked &&
group.ranked &&
@@ -220,6 +226,34 @@ export function otherGroupsForResponse({
);
}
export function isMatchReplay({
recentMatch,
user,
group,
}: {
recentMatch: { groups: { members: { memberId: string }[] }[] } | null;
user: { id: string };
group: { members: { memberId: string }[] };
}): boolean {
if (!recentMatch) return false;
const opponentGroupOfRecent = recentMatch.groups.find((g) =>
g.members.every((m) => m.memberId !== user.id)
);
invariant(
opponentGroupOfRecent,
"Unexpected opponentGroupOfRecent undefined"
);
const memberIdsOfGroup = new Set(group.members.map((m) => m.memberId));
let sameCount = 0;
for (const { memberId } of opponentGroupOfRecent.members) {
if (memberIdsOfGroup.has(memberId)) sameCount++;
}
return sameCount > 2;
}
export function filterExpiredGroups(group: { lastActionAt: Date }) {
const { EXPIRED: expiredDate } = groupExpiredDates();

View File

@@ -64,6 +64,42 @@ export function findByUserId({ userId }: { userId: string }) {
});
}
export type RecentOfUser = Prisma.PromiseReturnType<typeof recentOfUser>;
export function recentOfUser(userId: string) {
const twoHoursAgo = () => {
const result = new Date();
result.setHours(result.getHours() - 2);
return result;
};
return db.lfgGroupMatch.findFirst({
where: {
groups: {
some: {
members: {
some: {
memberId: userId,
},
},
},
},
createdAt: {
gte: twoHoursAgo(),
},
},
orderBy: {
createdAt: "desc",
},
include: {
groups: {
include: {
members: true,
},
},
},
});
}
export async function reportScore({
UNSAFE_matchId,
UNSAFE_winnerGroupIds,

View File

@@ -26,6 +26,7 @@ import {
import { canUniteWithGroup, isGroupAdmin } from "~/core/play/validators";
import { usePolling } from "~/hooks/common";
import * as LFGGroup from "~/models/LFGGroup.server";
import * as LFGMatch from "~/models/LFGMatch.server";
import styles from "~/styles/play-looking.css";
import {
makeTitle,
@@ -219,6 +220,7 @@ export type LookingLoaderDataGroup = {
};
MMRRelation?: "LOWER" | "BIT_LOWER" | "CLOSE" | "BIT_HIGHER" | "HIGHER";
ranked?: boolean;
replay?: boolean;
};
export interface LookingLoaderData {
@@ -233,7 +235,10 @@ export interface LookingLoaderData {
export const loader: LoaderFunction = async ({ context }) => {
const user = requireUser(context);
const { groups, ownGroup } = await LFGGroup.findLookingAndOwnActive(user.id);
const [{ groups, ownGroup }, recentMatch] = await Promise.all([
LFGGroup.findLookingAndOwnActive(user.id),
LFGMatch.recentOfUser(user.id),
]);
if (!ownGroup) return redirect("/play");
if (ownGroup.status === "MATCH") {
@@ -283,6 +288,8 @@ export const loader: LoaderFunction = async ({ context }) => {
isCaptain: isGroupAdmin({ group: ownGroup, user }),
lastActionAtTimestamp: ownGroup.lastActionAt.getTime(),
...otherGroupsForResponse({
recentMatch,
user,
useRelativeSkillLevel: USE_RELATIVE_SKILL_LEVEL,
groups: groupsOfType,
lookingForMatch,

View File

@@ -116,6 +116,12 @@
width: 1rem;
}
.play__card__replay {
color: var(--theme-info);
font-size: var(--fonts-sm);
font-weight: var(--semi-bold);
}
.play__card__member-power {
display: flex;
margin-top: -4px;

View File

@@ -80,6 +80,7 @@ export async function seed(variation?: SeedVariations) {
}
if (variation === "looking" || variation === "looking-match") {
await ownGroup(variation === "looking-match", remainingUserIdsForGroups);
await lfgMatches();
}
async function adminUser() {
@@ -405,19 +406,32 @@ export async function seed(variation?: SeedVariations) {
}
for (let i = 0; i < 24; i++) {
const members = {
createMany: {
data: new Array(4).fill(null).map((_, i) => ({
memberId: userIdsStack.shift()!,
captain: i === 0,
})),
},
};
if (i === 0) {
await prisma.lfgGroup.create({
data: {
id: `quad-${i + 1}-past`,
status: "INACTIVE",
type: "VERSUS",
ranked: true,
members,
},
});
}
await prisma.lfgGroup.create({
data: {
id: `quad-${i + 1}`,
status: "LOOKING",
type: "VERSUS",
ranked: i < 12,
members: {
createMany: {
data: new Array(4).fill(null).map((_, i) => ({
memberId: userIdsStack.shift()!,
captain: i === 0,
})),
},
},
members,
},
});
}
@@ -442,6 +456,20 @@ export async function seed(variation?: SeedVariations) {
}
}
await prisma.lfgGroup.create({
data: {
id: "own-group-past",
status: "INACTIVE",
type: "VERSUS",
ranked: true,
members: {
createMany: {
data: members,
},
},
},
});
return prisma.lfgGroup.create({
data: {
status: "LOOKING",
@@ -463,6 +491,26 @@ export async function seed(variation?: SeedVariations) {
});
}
async function lfgMatches() {
return prisma.lfgGroupMatch.create({
data: {
groups: {
connect: [{ id: "quad-1-past" }, { id: "own-group-past" }],
},
stages: {
createMany: {
data: [
{ order: 1, stageId: 1, winnerGroupId: "own-group-past" },
{ order: 2, stageId: 40, winnerGroupId: "own-group-past" },
{ order: 3, stageId: 60, winnerGroupId: "own-group-past" },
{ order: 4, stageId: 101, winnerGroupId: "own-group-past" },
],
},
},
},
});
}
async function tournamentRoundsCreate() {
const stages = await prisma.stage.findMany({});
await createTournamentRounds({