Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Stats v2 #5

Open
wants to merge 17 commits into
base: main
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
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ COPY package*.json ./
RUN npm install

COPY . .
COPY credentials.localdev.yaml credentials.yaml

CMD [ "node", "run.js" ]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ docker-compose up server redis

In order to use the server along with the [BYOnline session server](https://github.com/Backyard-Sports-Online/session), you'll have to build a session server image and tag it `session-main:latest`. Then, you can launch all three components (server, redis, and session server) with:
```
docker-compose up
docker-compose up server redis session-main
```
20 changes: 0 additions & 20 deletions credentials.localdev.yaml

This file was deleted.

20 changes: 0 additions & 20 deletions credentials.template.yaml

This file was deleted.

40 changes: 30 additions & 10 deletions database/Redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const createLogger = require('logging').default;
const ioredis = require("ioredis")
const Areas = require('../global/Areas.js');
const Stats = require('../global/Stats.js');

class Redis {
constructor(config) {
Expand Down Expand Up @@ -45,18 +46,10 @@ class Redis {
this.logger.warn(`User ${userId} not found in Redis!`);
return {};
}
let stats;
if (database == this) {
stats = (game == 'football' ? response['f_stats'] : response['b_stats']);
} else {
stats = response['stats'];
}
return {
'id': Number(userId),
'user': response['user'],
'icon': Number(response['icon']),
'stats': stats
.split(',').map(Number),
'game': response['game'],
'area': Number(response['area']),
'inGame': Number(response['inGame']),
Expand Down Expand Up @@ -99,8 +92,6 @@ class Redis {
user = {
'user': username,
'icon': 0,
'f_stats': Array(42).fill(0),
'b_stats': Array(29).fill(0),
}
this.addUser(userId, user, game);
user['id'] = userId;
Expand Down Expand Up @@ -130,6 +121,7 @@ class Redis {
'opponent': 0
});
}
await this.removeOngoingResults(client.userId, client.game);
}

async setIcon(userId, icon) {
Expand Down Expand Up @@ -209,6 +201,34 @@ class Redis {
games: Math.floor(gamesPlaying)
});
}

async setStats(userId, game, stats) {
this.logger.warn("Stats not supported with Redis DB!");
}

async getStats(userId, game) {
return Stats.DefaultStats[game];
}

async setOngoingResults(userId, game, ongoingResults) {
await this.redis.hmset(`byonline:ongoingResults:${game}:${userId}`, ongoingResults);
}

async getOngoingResults(userId, game) {
const ongoingResults = await this.redis.hgetall(`byonline:ongoingResults:${game}:${userId}`);
return ongoingResults;
}

async hasOngoingResults(userId, game) {
return await this.redis.exists(`byonline:ongoingResults:${game}:${userId}`);
}

async removeOngoingResults(userId, game) {
const resultsKey = `byonline:ongoingResults:${game}:${userId}`;
if (await this.redis.exists(resultsKey)) {
await this.redis.del(resultsKey);
}
}
}

module.exports = Redis;
31 changes: 31 additions & 0 deletions database/WebAPI.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";
const createLogger = require('logging').default;
const bent = require('bent');
const Stats = require('../global/Stats.js');

class WebAPI {
constructor(config) {
Expand Down Expand Up @@ -43,6 +44,36 @@ class WebAPI {
// Set the icon in the Redis cache.
redis.setIcon(userId, icon);
}

async setStats(userId, game, stats) {
const response = await this.post('/set_stats', {
token: this.token,
userId: userId,
game: game,
stats: stats,
})

if (response.error) {
this.logger.error("Failed to set stats!", { response });
return;
}
}

async getStats(userId, game) {
const response = await this.get('/get_stats', {
token: this.token,
userId: userId,
game: game,
})
let stats;
if (response['stats']) {
stats = response['stats'];
} else {
await this.setStats(userId, game, Stats.DefaultStats[game]);
stats = Stats.DefaultStats[game];
}
return stats;
}
}

module.exports = WebAPI;
27 changes: 25 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,41 @@ version: "3.8"
services:
server:
build: ./
links:
- redis
ports:
- "9130:9130"
environment:
- DATABASE=${DATABASE:-redis}
- WEB_ENDPOINT=${WEB_ENDPOINT:-http://site:3000/api}
- WEB_TOKEN=${WEB_TOKEN:-}
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- DISCORD_CLIENT
- DISCORD_CHANNEL
- DISCORD_TOKEN

redis:
image: redis
expose:
- 6379
ports:
- "6379:6379"

# This image must be built from the BYOnline session code (https://github.com/Backyard-Sports-Online/session)
session-main:
image: session-main:latest
ports:
- "9130:9130/udp"

site:
image: site:latest
ports:
- "3000:3000"
command:
- npm
- run
- compose

mongo:
image: mongo
ports:
- "27017:27017"
148 changes: 148 additions & 0 deletions global/Stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use strict";

const StatsFormatters = {
// These take a statistics object from redis or the site/Mongo
// and return an array in the order that the game expects
"baseball": (stats) => {
const numericStats = Object.fromEntries(
Object.entries(stats).map(([k, stat]) => [k, Number(stat)])
);
const statsArray = [
numericStats.wins,
numericStats.losses,
numericStats.disconnects,
numericStats.streak,
0,
0,
numericStats.games,
numericStats.atBats,
numericStats.hits,
0,
numericStats.singles,
numericStats.doubles,
numericStats.triples,
numericStats.homeRuns,
0,
numericStats.steals,
numericStats.strikeouts,
numericStats.walks,
0,
0,
0,
0,
0,
0,
0,
0,
0,
numericStats.longestHomeRun,
0
];
return statsArray;
},
// TODO: Football
"football": (stats) => {
return Array(42).fill(0)
}
};

const ResultsMappers = {
// These take an array that the game sends to `/game_results`
// and return an ongoing results object that can be stored in redis
"baseball": (resultsFields, resultsSide) => {
const ongoingResults = {
winning: resultsFields[0],
runs: resultsFields[1],
atBats: resultsFields[2],
hits: resultsFields[3],
errors: resultsFields[4],
longestHomeRun: resultsFields[5],
singles: resultsFields[6],
doubles: resultsFields[7],
triples: resultsFields[8],
steals: resultsFields[9],
strikeouts: resultsFields[10],
walks: resultsFields[11],
disconnect: resultsFields[12],
completedInnings: resultsFields[13],
side: resultsSide
};
return ongoingResults;
},
// TODO: Football
"football": (resultsFields, resultsSide) => {
return {"not_yet_supported": 1}
}
};

const Aggregators = {
// These combine a completed game's stats with that user's existing stats
// so that the user's stats can be updated to include that game
"baseball": (finalResults, stats) => {
const homeRuns = finalResults.hits - (finalResults.singles + finalResults.doubles + finalResults.triples);
// Update all of these counting stats and longest home run regardless of whether the user disconnected
stats.games += 1;
stats.atBats += finalResults.atBats;
stats.hits += finalResults.hits;
stats.singles += finalResults.singles;
stats.doubles += finalResults.doubles;
stats.triples += finalResults.triples;
stats.homeRuns += homeRuns;
stats.steals += finalResults.steals;
stats.strikeouts += finalResults.strikeouts;
stats.walks += finalResults.walks;
stats.longestHomeRun = Math.max(stats.longestHomeRun, finalResults.longestHomeRun);
// If the user disconnected, increment those. If not, update wins, losses, and streak as normal
if (finalResults.disconnect == 1) {
stats.disconnects += finalResults.disconnect;
} else {
stats.wins += finalResults.winning; // Increment user's wins
stats.losses += (1 - finalResults.winning); // Increment user's losses
const winSign = finalResults.winning * 2 - 1;
if (Math.sign(stats.streak) == winSign) {
// If user is continuing their streak, increment/decrement it.
stats.streak += winSign;
} else {
// If user is starting a new streak.
stats.streak = winSign;
}
// TODO (maybe): Wins in last 10 games and margin (what is that exactly?)
}
return stats;
},
// TODO: Football
"football": (finalResults, stats) => {
return {not_yet_supported: 1}
}
}

const DefaultStats = {
"baseball": {
wins: 0,
losses: 0,
disconnects: 0,
streak: 0,
games: 0,
atBats: 0,
hits: 0,
singles: 0,
doubles: 0,
triples: 0,
homeRuns: 0,
steals: 0,
strikeouts: 0,
walks: 0,
longestHomeRun: 0
},
// TODO: Football
"football": {
not_yet_supported: 1
}
};

module.exports = {
StatsFormatters: StatsFormatters,
ResultsMappers: ResultsMappers,
Aggregators: Aggregators,
DefaultStats: DefaultStats
};
Loading