From 057f60f049ae1a4740bf1c8da3c662df0403b72d Mon Sep 17 00:00:00 2001 From: HF Date: Wed, 27 Dec 2023 15:43:34 +0100 Subject: [PATCH] add punishment for dominating country --- src/core/Ranks.js | 100 ++++++++++++++++++++++++++++++++-------- src/core/config.js | 3 ++ src/core/draw.js | 3 ++ src/data/redis/ranks.js | 58 +++++++++++++++++++++++ 4 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/core/Ranks.js b/src/core/Ranks.js index 70afa7a7..b11dbadd 100644 --- a/src/core/Ranks.js +++ b/src/core/Ranks.js @@ -11,6 +11,8 @@ import { storeOnlinUserAmount, getCountryDailyHistory, getCountryRanks, + getHourlyCountryStats, + storeHourlyCountryStats, getTopDailyHistory, storeHourlyPixelsPlaced, getHourlyPixelStats, @@ -20,30 +22,38 @@ import socketEvents from '../socket/socketEvents'; import logger from './logger'; import { MINUTE } from './constants'; +import { PUNISH_DOMINATOR } from './config'; import { DailyCron, HourlyCron } from '../utils/cron'; class Ranks { + ranks = { + // ranking today of users by pixels + dailyRanking: [], + // ranking of users by pixels + ranking: [], + // ranking today of countries by pixels + dailyCRanking: [], + // ranking hourly of countries by pixels + cHourlyStats: [], + // yesterdays ranking of users by pixels + prevTop: [], + // online user amount by hour + onlineStats: [], + // ranking of countries by day + cHistStats: [], + // ranking of users by day + histStats: { users: [], stats: [] }, + // pixels placed by hour + pHourlyStats: [], + // pixels placed by day + pDailyStats: [], + }; + + // if a country dominates, adjust its cooldown + #punishedCountry; + #punishmentFactor; + constructor() { - this.ranks = { - // ranking today of users by pixels - dailyRanking: [], - // ranking of users by pixels - ranking: [], - // ranking today of countries by pixels - dailyCRanking: [], - // yesterdays ranking of users by pixels - prevTop: [], - // online user amount by hour - onlineStats: [], - // ranking of countries by day - cHistStats: [], - // ranking of users by day - histStats: { users: [], stats: [] }, - // pixels placed by hour - pHourlyStats: [], - // pixels placed by day - pDailyStats: [], - }; /* * we go through socketEvents for sharding */ @@ -70,12 +80,59 @@ class Ranks { if (!newRanks) { return; } + if (newRanks.cHourlyStats) { + this.setPunishments(newRanks.cHourlyStats); + } this.ranks = { ...this.ranks, ...newRanks, }; } + setPunishments(cHourlyStats) { + if (!cHourlyStats.length || !PUNISH_DOMINATOR) { + return; + } + let outnumbered = 0; + const { cc: leadingCountry } = cHourlyStats[0]; + let { px: margin } = cHourlyStats[0]; + for (let i = 1; i < cHourlyStats.length; i += 1) { + margin -= cHourlyStats[i].px; + if (margin < 0) { + break; + } + outnumbered += 1; + } + /* + * if the leading country places more pixels + * than the fellowing 2+ countries after, + * 20% gets added to the cooldown for every following + * country, cailed at 200%; + */ + if (outnumbered > 2) { + this.#punishedCountry = leadingCountry; + let punishmentFactor = 1 + 0.2 * (outnumbered - 1); + if (punishmentFactor > 2) { + punishmentFactor = 2; + } + this.#punishmentFactor = punishmentFactor; + logger.info( + // eslint-disable-next-line max-len + `Punishment for dominating country ${leadingCountry} of ${punishmentFactor}`, + ); + return; + } + this.#punishedCountry = null; + this.#punishmentFactor = 1.0; + } + + getCountryCoolDownFactor(country) { + if (this.#punishedCountry === country) { + return this.#punishmentFactor; + } + return 1.0; + } + static async updateRanking() { // only main shard does it if (!socketEvents.amIImportant()) { @@ -107,10 +164,12 @@ class Ranks { const onlineStats = await getOnlineUserStats(); const cHistStats = await getCountryDailyHistory(); const pHourlyStats = await getHourlyPixelStats(); + const cHourlyStats = await getHourlyCountryStats(1, 100); const ret = { onlineStats, cHistStats, pHourlyStats, + cHourlyStats, }; if (socketEvents.amIImportant()) { // only main shard sends to others @@ -148,6 +207,7 @@ class Ranks { const amount = socketEvents.onlineCounter.total; await storeOnlinUserAmount(amount); await storeHourlyPixelsPlaced(); + await storeHourlyCountryStats(1, 100); await Ranks.hourlyUpdateRanking(); } diff --git a/src/core/config.js b/src/core/config.js index 7afa8dda..885f75eb 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -29,6 +29,9 @@ export const BACKUP_DIR = process.env.BACKUP_DIR || null; export const OUTGOING_ADDRESS = process.env.OUTGOING_ADDRESS || null; +// Punish when a country dominates +export const PUNISH_DOMINATOR = !!process.env.PUNISH_DOMINATOR; + // Proxycheck export const USE_PROXYCHECK = parseInt(process.env.USE_PROXYCHECK, 10) || false; export const { PROXYCHECK_KEY } = process.env; diff --git a/src/core/draw.js b/src/core/draw.js index ae2333fc..d8aca3e8 100644 --- a/src/core/draw.js +++ b/src/core/draw.js @@ -8,6 +8,7 @@ import { import logger, { pixelLogger } from './logger'; import allowPlace from '../data/redis/cooldown'; import socketEvents from '../socket/socketEvents'; +import rankings from './Ranks'; import { setPixelByOffset } from './setPixel'; import isIPAllowed from './isAllowed'; import canvases from './canvases'; @@ -127,6 +128,8 @@ export default async function drawByOffsets( } */ + factor *= rankings.getCountryCoolDownFactor(user.country); + factor *= 0.75; const bcd = canvas.bcd * factor; diff --git a/src/data/redis/ranks.js b/src/data/redis/ranks.js index 406ff58a..045a69ce 100644 --- a/src/data/redis/ranks.js +++ b/src/data/redis/ranks.js @@ -7,6 +7,9 @@ import { getDateKeyOfTs } from '../../core/utils'; export const RANKED_KEY = 'rank'; export const DAILY_RANKED_KEY = 'rankd'; export const DAILY_CRANKED_KEY = 'crankd'; +export const HOURLY_CRANKED_KEY = 'crankh'; +export const PREV_DAILY_CRANKED_KEY = 'pcrankd'; +export const PREV_DAILY_CRANKED_TS_KEY = 'pcrankdts'; export const PREV_DAY_TOP_KEY = 'prankd'; const DAY_STATS_RANKS_KEY = 'ds'; const CDAY_STATS_RANKS_KEY = 'cds'; @@ -100,6 +103,61 @@ export async function getCountryRanks(start, amount) { return ranks; } +/* + * get previous daily country ranking (one hour before cranks) + */ +export async function getHourlyCountryStats(start, amount) { + start -= 1; + amount -= 1; + let ranks = await client.zRangeWithScores( + HOURLY_CRANKED_KEY, start, start + amount, { + REV: true, + }); + ranks = ranks.map((r) => ({ + cc: r.value, + px: Number(r.score), + })); + return ranks; +} + +export async function storeHourlyCountryStats(start, amount) { + start -= 1; + amount -= 1; + const tsNow = Date.now(); + const prevTs = Number(await client.get(PREV_DAILY_CRANKED_TS_KEY)); + const prevData = await client.zRangeWithScores( + PREV_DAILY_CRANKED_KEY, start, start + amount, { + REV: true, + }); + + await client.copy(DAILY_CRANKED_KEY, PREV_DAILY_CRANKED_KEY, { + REAPLACE: true, + }); + await client.set(PREV_DAILY_CRANKED_TS_KEY, String(tsNow)); + await client.del(HOURLY_CRANKED_KEY); + + if (prevTs && prevTs > tsNow - 1000 * 3600 * 1.5) { + const curData = await client.zRangeWithScores( + DAILY_CRANKED_KEY, start, start + amount, { + REV: true, + }); + const prevRanks = new Map(); + prevData.forEach(({ value, score }) => prevRanks.set(value, score)); + const addArr = []; + curData.forEach(({ value: cc, score: curPx }) => { + const prevPx = prevData.get(cc) || 0; + const px = (curPx > prevPx) ? curPx - prevPx : curPx; + addArr.push({ + score: cc, + value: px, + }); + }); + if (addArr.length) { + await client.zAdd(HOURLY_CRANKED_KEY, addArr); + } + } +} + /* * get top 10 from previous day */