Skip to content

Commit 1934da3

Browse files
committed
ic
1 parent ea6a59d commit 1934da3

File tree

12 files changed

+693
-54
lines changed

12 files changed

+693
-54
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The application is implemented in [Remix](https://remix.run/docs) with a [Postgr
1010

1111
```sh
1212
docker run --name postgres16 -e POSTGRES_DB=krokelo -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=666 -p 5432:5432 -d postgres:16
13+
docker run --restart always --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/your_password -d neo4j:5.23.0
1314
```
1415

1516
### Create a .env file with required environment variables

app/neo4j-driver.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import neo4j from 'neo4j-driver';
2+
3+
const neo4jDriver = neo4j.driver(
4+
process.env.NEO4J_URL || 'bolt://localhost:7687',
5+
neo4j.auth.basic(
6+
process.env.NEO4J_USERNAME || 'neo4j',
7+
process.env.NEO4J_PASSWORD || 'neo4j'
8+
),
9+
{ disableLosslessIntegers: true }
10+
);
11+
12+
export { neo4jDriver };

app/routes/duel-stats.tsx

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { useFetcher } from '@remix-run/react';
2-
import { getPlayers } from '~/services/player-service';
3-
import {
4-
getRecent1v1Matches,
5-
revertLatest1v1Match,
6-
} from '~/services/match-service';
2+
import { getPlayers as getPlayersNeo4j } from '~/services/player-service-neo4j';
3+
import { revertLatest1v1Match } from '~/services/match-service';
4+
import { getRecent1v1Matches as getRecent1v1MatchesNeo4j } from '~/services/match-service-neo4j';
75
import { PageContainerStyling } from './team-duel';
86
import { typedjson, useTypedLoaderData } from 'remix-typedjson';
97
import { BASE_ELO } from '~/utils/constants';
108

119
export const loader = async () => {
12-
const players = await getPlayers();
13-
const recent1v1Matches = await getRecent1v1Matches(5);
10+
const playersNeo4j = await getPlayersNeo4j();
11+
const recent1v1MatchesNeo4j = await getRecent1v1MatchesNeo4j(5);
1412

15-
return typedjson({ players, recent1v1Matches });
13+
return typedjson({
14+
playersNeo4j,
15+
recent1v1MatchesNeo4j,
16+
});
1617
};
1718

1819
export const action = async () => {
@@ -34,9 +35,10 @@ const getRowHighlightClass = (idx: number, matchDate: Date) =>
3435

3536
export default function Index() {
3637
const fetcher = useFetcher();
37-
const { players, recent1v1Matches } = useTypedLoaderData<typeof loader>();
38+
const { playersNeo4j, recent1v1MatchesNeo4j } =
39+
useTypedLoaderData<typeof loader>();
3840

39-
const rankedPlayersSortedOnELODesc = [...players]
41+
const rankedPlayersSortedOnELODesc = [...playersNeo4j]
4042
.filter((a) => a.currentELO !== BASE_ELO)
4143
.sort((p1, p2) => p2.currentELO - p1.currentELO);
4244

@@ -57,13 +59,13 @@ export default function Index() {
5759
</tr>
5860
</thead>
5961
<tbody>
60-
{recent1v1Matches.map((match, idx) => (
62+
{recent1v1MatchesNeo4j.map((match, idx) => (
6163
<tr
6264
key={match.id}
63-
className={`border-b dark:border-gray-600 ${getRowHighlightClass(idx, match.date)}`}
65+
className={`border-b dark:border-gray-600 ${getRowHighlightClass(idx, match.createdDatetime)}`}
6466
>
6567
<td className="py-2 dark:text-white">
66-
{match.date.toLocaleString('no-NO', {
68+
{match.createdDatetime.toLocaleString('no-NO', {
6769
day: '2-digit',
6870
month: 'short',
6971
hour: '2-digit',
@@ -91,7 +93,7 @@ export default function Index() {
9193
</td>
9294
<td>
9395
{isLatestMatch(idx) &&
94-
isMatchLessThan5MinutesOld(match.date) && (
96+
isMatchLessThan5MinutesOld(match.createdDatetime) && (
9597
<button
9698
onClick={() => fetcher.submit({}, { method: 'post' })}
9799
className="mx-4 rounded bg-blue-600 px-2 py-1 font-bold text-white hover:bg-blue-700 dark:bg-purple-600 dark:hover:bg-purple-800"
@@ -124,9 +126,7 @@ export default function Index() {
124126
<tbody>
125127
{rankedPlayersSortedOnELODesc
126128
.filter(
127-
(a) =>
128-
a.matchesAsWinner.length + a.matchesAsLoser.length > 4 &&
129-
!a.inactive
129+
(a) => a.nbMatchesWon + a.nbMatchesLost > 4 && !a.inactive
130130
)
131131
.map((player) => (
132132
<tr
@@ -137,11 +137,11 @@ export default function Index() {
137137
{player.name}
138138
</td>
139139
<td className="py-2 dark:text-white">
140-
{`${player.matchesAsWinner.length}` +
140+
{`${player.nbMatchesWon}` +
141141
(player.winStreak > 0 ? ` (${player.winStreak})` : '')}
142142
</td>
143143
<td className="py-2 dark:text-white">
144-
{player.matchesAsLoser.length}
144+
{player.nbMatchesLost}
145145
</td>
146146
<td className="py-2 dark:text-white">{player.currentELO}</td>
147147
</tr>
@@ -162,9 +162,7 @@ export default function Index() {
162162
</tr>
163163
{rankedPlayersSortedOnELODesc
164164
.filter(
165-
(a) =>
166-
a.matchesAsWinner.length + a.matchesAsLoser.length <= 4 &&
167-
!a.inactive
165+
(a) => a.nbMatchesWon + a.nbMatchesLost <= 4 && !a.inactive
168166
)
169167
.map((player) => (
170168
<tr
@@ -175,11 +173,11 @@ export default function Index() {
175173
{player.name}
176174
</td>
177175
<td className="py-2 dark:text-white">
178-
{`${player.matchesAsWinner.length}` +
176+
{`${player.nbMatchesWon}` +
179177
(player.winStreak > 0 ? ` (${player.winStreak})` : '')}
180178
</td>
181179
<td className="py-2 dark:text-white">
182-
{player.matchesAsLoser.length}
180+
{player.nbMatchesLost}
183181
</td>
184182
<td></td>
185183
</tr>
@@ -210,10 +208,10 @@ export default function Index() {
210208
{player.name}
211209
</td>
212210
<td className="py-2 dark:text-white">
213-
{`${player.matchesAsWinner.length}`}
211+
{`${player.nbMatchesWon}`}
214212
</td>
215213
<td className="py-2 dark:text-white">
216-
{player.matchesAsLoser.length}
214+
{player.nbMatchesLost}
217215
</td>
218216
<td className="py-2 dark:text-white">{player.currentELO}</td>
219217
</tr>

app/routes/duel.tsx

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
calculateNewELOs,
1414
logIndividualELO,
1515
} from '../services/player-service';
16-
import { record1v1Match } from '../services/match-service';
16+
import { findOrCreatePlayer } from '../services/player-service-neo4j';
17+
import { record1v1Match } from '../services/match-service-neo4j';
1718
import { PageContainerStyling } from './team-duel';
1819
import CreatableSelect from 'react-select/creatable';
1920
import { createFilter } from 'react-select';
@@ -70,36 +71,15 @@ export const action = async ({ request }: ActionFunctionArgs) => {
7071
}
7172

7273
try {
73-
const player1 =
74-
(await findPlayerByName(player1Name)) ||
75-
(await createPlayer(player1Name));
74+
const player1 = await findOrCreatePlayer(player1Name);
75+
const player2 = await findOrCreatePlayer(player2Name);
7676

77-
const player2 =
78-
(await findPlayerByName(player2Name)) ||
79-
(await createPlayer(player2Name));
80-
81-
const player1IsWinner =
82-
player1Name.trim().toLowerCase() === winner.trim().toLowerCase();
83-
84-
const { newELOPlayer1, newELOPlayer2 } = calculateNewELOs(
85-
player1.currentELO,
86-
player2.currentELO,
87-
player1IsWinner
88-
);
77+
const player1IsWinner = player1Name === winner;
8978

9079
const winnerId = player1IsWinner ? player1.id : player2.id;
9180
const loserId = player1IsWinner ? player2.id : player1.id;
9281

93-
const match = await record1v1Match(
94-
winnerId,
95-
loserId,
96-
player1IsWinner ? newELOPlayer1 : newELOPlayer2,
97-
player1IsWinner ? newELOPlayer2 : newELOPlayer1
98-
);
99-
await updatePlayerELO(player1.id, newELOPlayer1);
100-
await logIndividualELO(player1.id, newELOPlayer1, match.id); // Log the new ELO for player 1
101-
await updatePlayerELO(player2.id, newELOPlayer2);
102-
await logIndividualELO(player2.id, newELOPlayer2, match.id); // Log the new ELO for player 2
82+
const match = await record1v1Match(winnerId, loserId);
10383
} catch (err) {
10484
console.error(err);
10585
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { neo4jDriver } from '~/neo4j-driver';
2+
3+
interface Recent1v1Match {
4+
id: number;
5+
createdDatetime: Date;
6+
winner: Recent1v1MatchPlayer;
7+
loser: Recent1v1MatchPlayer;
8+
}
9+
10+
interface Recent1v1MatchPlayer {
11+
id: number;
12+
name: string;
13+
eloAfterMatch: number;
14+
eloBeforeMatch: number;
15+
eloDifference: number;
16+
}
17+
18+
export const getRecent1v1Matches = async (limit: number = 5) => {
19+
const neo4jSession = neo4jDriver.session();
20+
21+
try {
22+
const res = await neo4jSession.executeRead((tx) =>
23+
tx.run(
24+
`
25+
MATCH (winner:Player)-[:WON]->(match:Match)<-[:LOST]-(loser:Player)
26+
WITH match, winner, loser
27+
ORDER BY match.createdDatetime DESC
28+
LIMIT toInteger($limit)
29+
MATCH (winner)-[:HAS_CURRENT_ELO|PREVIOUS_ELO*0..]->(winnerEloAfterMatch:EloRating)-[:AFTER]->(match)
30+
WITH match, winner, winnerEloAfterMatch, loser
31+
MATCH (winnerEloAfterMatch)-[:PREVIOUS_ELO]->(winnerEloBeforeMatch:EloRating)
32+
WITH match, winner, winnerEloAfterMatch, winnerEloBeforeMatch, loser
33+
MATCH (loser)-[:HAS_CURRENT_ELO|PREVIOUS_ELO*0..]->(loserEloAfterMatch:EloRating)-[:AFTER]->(match)
34+
WITH match, winner, winnerEloAfterMatch, winnerEloBeforeMatch, loser, loserEloAfterMatch
35+
MATCH (loserEloAfterMatch)-[:PREVIOUS_ELO]->(loserEloBeforeMatch:EloRating)
36+
WITH match, winner, winnerEloAfterMatch, winnerEloBeforeMatch, loser, loserEloAfterMatch, loserEloBeforeMatch
37+
RETURN {
38+
id: match.id,
39+
createdDatetime: match.createdDatetime,
40+
winner: {
41+
id: winner.id,
42+
name: winner.name,
43+
eloAfterMatch: winnerEloAfterMatch.rating,
44+
eloBeforeMatch: winnerEloBeforeMatch.rating,
45+
eloDifference: winnerEloAfterMatch.rating - winnerEloBeforeMatch.rating
46+
},
47+
loser: {
48+
id: loser.id,
49+
name: loser.name,
50+
eloAfterMatch: loserEloAfterMatch.rating,
51+
eloBeforeMatch: loserEloBeforeMatch.rating,
52+
eloDifference: loserEloAfterMatch.rating - loserEloBeforeMatch.rating
53+
}
54+
} as match
55+
`,
56+
{ limit }
57+
)
58+
);
59+
60+
const matches: Recent1v1Match[] = res.records.map((row) => ({
61+
...row.get('match'),
62+
createdDatetime: row.get('match').createdDatetime.toStandardDate(),
63+
}));
64+
return matches;
65+
} finally {
66+
// Close the Session
67+
await neo4jSession.close();
68+
}
69+
};
70+
71+
export const record1v1Match = async (winnerId: string, loserId: string) => {
72+
const neo4jSession = neo4jDriver.session();
73+
74+
try {
75+
await neo4jSession.executeWrite((tx) =>
76+
tx.run(
77+
`
78+
MATCH (winner:Player { id: $winnerId }), (loser:Player { id: $loserId })
79+
CREATE (match:Match {
80+
id: apoc.create.uuidBase64(),
81+
createdDatetime: datetime.transaction()
82+
})
83+
CREATE (winner)-[:WON]->(match)<-[:LOST]-(loser)
84+
WITH match, winner, loser
85+
OPTIONAL MATCH (winner)-[:HAS_CURRENT_ELO]->(winnerEloBeforeMatch:EloRating)
86+
WITH match, winner, winnerEloBeforeMatch, loser
87+
OPTIONAL MATCH (loser)-[:HAS_CURRENT_ELO]->(loserEloBeforeMatch:EloRating)
88+
WITH match, winner, winnerEloBeforeMatch, loser, loserEloBeforeMatch
89+
90+
`,
91+
{ winnerId, loserId }
92+
)
93+
);
94+
} finally {
95+
// Close the Session
96+
await neo4jSession.close();
97+
}
98+
};
99+
100+
export const revertLatest1v1Match = async () => {
101+
const neo4jSession = neo4jDriver.session();
102+
103+
try {
104+
await neo4jSession.executeWrite((tx) =>
105+
tx.run(
106+
`
107+
MATCH (latestMatch:Match)
108+
WHERE EXISTS { (latestMatch)<-[:WON]-(:Player) }
109+
WITH latestMatch
110+
ORDER BY latestMatch.createdDatetime DESC
111+
LIMIT 1
112+
MATCH (latestMatch)<-[:AFTER]-(eloAfterMatch:EloRating)<-[:PREVIOUS_ELO]-(eloBeforeMatch:EloRating)
113+
WITH latestMatch, eloAfterMatch, eloBeforeMatch
114+
MATCH (player:Player)-[:HAS_CURRENT_ELO]->(eloAfterMatch)
115+
WITH latestMatch, player, eloAfterMatch, eloBeforeMatch
116+
CREATE (player)-[:HAS_CURRENT_ELO]->(eloBeforeMatch)
117+
DETACH DELETE eloAfterMatch
118+
DETACH DELETE latestMatch
119+
`
120+
)
121+
);
122+
} finally {
123+
// Close the Session
124+
await neo4jSession.close();
125+
}
126+
};

0 commit comments

Comments
 (0)