Skip to content

Commit

Permalink
Merge pull request #95 from Mickhat/oli-heeeckers-new-system
Browse files Browse the repository at this point in the history
change the 1337 game to use heeeckers early voting system
  • Loading branch information
oglimmer committed Mar 14, 2024
2 parents 1ea0117 + eb9adf1 commit 122810b
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 3 deletions.
92 changes: 92 additions & 0 deletions src/commands/elite-early-bird.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

import { CommandInteraction, EmbedBuilder, SlashCommandBuilder } from 'discord.js'
import LogManager from '../logger/logger'
import { EliteGameDataStorage } from '../service/eliteGameDataStorage'
import { getTodayAsDate } from '../service/promotionService'
import { calcValidFrom, parse } from 'src/service/eliteEarlyBirdService'

export default {
data: new SlashCommandBuilder()
.setName('1337-early-bird')
.setDescription('Plays the 1337 game as an early bird.')
.addStringOption((opt) => opt.setName('time').setDescription('The time (HH:mm:ss.sss) you would have /1337`ed').setRequired(true)),
async execute(interaction: CommandInteraction) {
const logger = LogManager.getInstance().logger('EliteEarlyBirdCommand')
if (!interaction.isRepliable()) {
logger.logSync('ERROR', 'Gegebene interaction kann nicht beantwortet werden.')
return
}
const berlinDate = getTodayAsDate()

const userId = interaction.member?.user.id ?? '<ERROR>'
const time = interaction.options.get('time', true).value
if (!time) {
throw new Error('No time provided')
}

const rows = await EliteGameDataStorage.instance().loadGamePlayForUser(userId, berlinDate)
try {
if (rows.length > 0) {
if (rows[0].player === '\uFFFF') {
// the player '\uFFFF' indicates that the game has been played for this day
await interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Too late!')
.setDescription(
'Es ist nach 13:37. Morgen kannst du wieder mitmachen.'
)
.setTimestamp()],
ephemeral: true
})
} else {
await interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('That`s not going to happen!')
.setDescription(
'Du hast heute bereits deinen Tipp abgegeben. Warte auf das Ergebnis.'
)
.setTimestamp()],
ephemeral: true
})
}
} else {
const playTime = parse(time)
if (!playTime) {
await interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Invalid time!')
.setDescription(
'Die Zeit muss im Format `HH:mm:ss.sss` sein.'
)
.setTimestamp()],
ephemeral: true
})
return
}
const validFrom = calcValidFrom(playTime)
await EliteGameDataStorage.instance().writeGamePlayEarlyBird(
userId, // discord-id user
berlinDate, // german format date of play e.g. 17.06.2024
playTime, // submission time (the guess) in millis since epoc e.g. 1710419768333 for 13.03.2024 23:05
validFrom // valid starting time in millis since epoc e.g. 1710419768333 for 13.03.2024 23:05
)
await interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x0099ff)
.setTitle('Got it early bird!')
.setDescription(
'Dein Tipp wurde registriert. Warte auf das Ergebnis.'
)
.setTimestamp()],
ephemeral: true
})
}
} catch (err) {
console.log(err)
logger.logSync('ERROR', `Reply to elite command konnte nicht gesendet werden. ${JSON.stringify(err)}`)
}
}
}
92 changes: 92 additions & 0 deletions src/service/eliteEarlyBirdService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { expect, test } from '@jest/globals'
import { parse } from './eliteEarlyBirdService'

test('full time - at 13:37', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('13:37:37.137')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 37 * 60 * 1000 + 37 * 1000 + 137)
}
})

test('full time - early morning', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('2:37:37.137')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(2 * 60 * 60 * 1000 + 37 * 60 * 1000 + 37 * 1000 + 137)
}
})

test('full time - early morning - single digits', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('2:02:02.1')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(2 * 60 * 60 * 1000 + 2 * 60 * 1000 + 2 * 1000 + 100)
}
})

test('time without millis - early morning - single digits', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('2:02:02')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(2 * 60 * 60 * 1000 + 2 * 60 * 1000 + 2 * 1000)
}
})

test('time without hour - single digits', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('2:02')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 2 * 60 * 1000 + 2 * 1000)
}
})

test('time just in seconcs', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('37')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 37 * 60 * 1000 + 37 * 1000)
}
})

test('time just in seconcs and millis(3 digit pending 0)', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('3.370')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 37 * 60 * 1000 + 3 * 1000 + 370)
}
})

test('time just in seconcs and millis(2 digits)', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('3.37')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 37 * 60 * 1000 + 3 * 1000 + 370)
}
})

test('time just in seconcs and millis(3 digits, leading 0)', async () => {
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
const result = parse('3.037')
expect(result).toBeDefined()
if (result) {
expect(result - midnight).toBe(13 * 60 * 60 * 1000 + 37 * 60 * 1000 + 3 * 1000 + 37)
}
})
92 changes: 92 additions & 0 deletions src/service/eliteEarlyBirdService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

export function parse(dateToParse: string | number | boolean): undefined | number {
if (typeof dateToParse !== 'string') {
return undefined
}
const now = new Date()
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime()
if (/^\d{1,2}:\d{1,2}:\d{1,2}\.\d{1,3}$/g.test(dateToParse)) {
const splitByColon = dateToParse.split(':')
if (splitByColon.length !== 3) {
return undefined
}
const hours = parseInt(splitByColon[0])
const minutes = parseInt(splitByColon[1])
const seconds = parseFloat(splitByColon[2])
if (hours < 0 || hours > 23) {
return undefined
}
if (minutes < 0 || minutes > 59) {
return undefined
}
if (seconds < 0 || seconds >= 60) {
return undefined
}
return midnight + (seconds * 1000) + (minutes * 60 * 1000) + (hours * 60 * 60 * 1000)
}
if (/^\d{1,2}:\d{1,2}:\d{1,2}$/g.test(dateToParse)) {
const splitByColon = dateToParse.split(':')
if (splitByColon.length !== 3) {
return undefined
}
const hours = parseInt(splitByColon[0])
const minutes = parseInt(splitByColon[1])
const seconds = parseInt(splitByColon[2])
if (hours < 0 || hours > 23) {
return undefined
}
if (minutes < 0 || minutes > 59) {
return undefined
}
if (seconds < 0 || seconds > 59) {
return undefined
}
return midnight + (seconds * 1000) + (minutes * 60 * 1000) + (hours * 60 * 60 * 1000)
}
if (/^\d{1,2}:\d{1,2}$/g.test(dateToParse)) {
const splitByColon = dateToParse.split(':')
if (splitByColon.length !== 2) {
return undefined
}
const minutes = parseInt(splitByColon[0])
const seconds = parseInt(splitByColon[1])
if (minutes < 0 || minutes > 59) {
return undefined
}
if (seconds < 0 || seconds > 59) {
return undefined
}
return midnight + (seconds * 1000) + (minutes * 60 * 1000) + (13 * 60 * 60 * 1000)
}
if (/^\d{1,2}\.\d{1,3}$/g.test(dateToParse)) {
const seconds = parseFloat(dateToParse)
if (seconds < 0 || seconds >= 60) {
return undefined
}
return midnight + (seconds * 1000) + (37 * 60 * 1000) + (13 * 60 * 60 * 1000)
}
if (/^\d{1,2}$/g.test(dateToParse)) {
const seconds = parseInt(dateToParse)
if (seconds < 0 || seconds > 59) {
return undefined
}
return midnight + seconds * 1000 + (37 * 60 * 1000) + (13 * 60 * 60 * 1000)
}
return undefined
}

export function calcValidFrom(playTime: number): number {
const now = new Date()
let offset = 999
// rules: Mitternacht tippt sind es 65s, wenn man um 8 Uhr tippt 30s, um 12 Uhr -> 7s, 13 Uhr -> 3s, ab 13:37 dann 0s
if (now.getHours() >= 0 && now.getHours() <= 7) {
offset = 65
} else if (now.getHours() >= 8 && now.getHours() <= 11) {
offset = 30
} else if (now.getHours() === 12) {
offset = 7
} else if (now.getHours() === 13) {
offset = 3
}
return playTime + offset * 1000
}
24 changes: 21 additions & 3 deletions src/service/eliteGameDataStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export class EliteGameDataStorage {
register_date TEXT NOT NULL,
play_timestamp INTEGER NOT NULL
)`)
const rowST = await this.db.getAsyncT<{ count: number }>(`SELECT 1 as count FROM sqlite_master WHERE type='table' AND name='elite_game' AND sql LIKE '%submission_time%';`, [])
if (rowST?.count !== 1) {
await this.db.runAsync(`ALTER TABLE elite_game ADD COLUMN submission_time INTEGER NOT NULL DEFAULT 0`)
await this.db.runAsync(`ALTER TABLE elite_game ADD COLUMN valid_starting_at INTEGER NOT NULL DEFAULT 0`)
}
await this.db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game_winner (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
player TEXT NOT NULL,
Expand Down Expand Up @@ -84,7 +89,15 @@ export class EliteGameDataStorage {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
await this.db.runAsync(`INSERT INTO elite_game(register_date, player, play_timestamp) VALUES(?,?,?)`, [berlinDate, userId, Date.now()])
const now = Date.now()
await this.db.runAsync(`INSERT INTO elite_game(register_date, player, play_timestamp, submission_time, valid_starting_at) VALUES(?,?,?,?,0)`, [berlinDate, userId, now, now])
}

public async writeGamePlayEarlyBird(userId: string, berlinDate: string, tippTime: number, validFrom: number): Promise<void> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
await this.db.runAsync(`INSERT INTO elite_game(register_date, player, play_timestamp, submission_time, valid_starting_at) VALUES(?,?,?,?,?)`, [berlinDate, userId, tippTime, Date.now(), validFrom])
}

public async loadGamePlayForUser(userId: string, berlinDate: string): Promise<EliteGame[]> {
Expand All @@ -98,7 +111,8 @@ export class EliteGameDataStorage {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<EliteGame>(`select * from elite_game where register_date = ? and player != "\uFFFF" order by play_timestamp desc`, [berlinDate])
const now = Date.now()
return await this.db.allAsyncT<EliteGame>(`select * from elite_game where register_date = ? and valid_starting_at <= ? and player != "\uFFFF" order by play_timestamp desc`, [berlinDate, now])
}

public async writeGameWinner(userId: string, berlinDate: string, playerTimestamp: number): Promise<void> {
Expand Down Expand Up @@ -132,6 +146,10 @@ export class EliteGameDataStorage {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<{ userId: string }>(`select player as userId from elite_game where register_date = (select register_date from elite_game where identifier = (select max(identifier) from elite_game where player = "\uFFFF")) and player != "\uFFFF" order by play_timestamp desc`, [])
return await this.db.allAsyncT<{ userId: string }>(`select player as userId from elite_game where register_date = ` +
`(select register_date from elite_game where identifier = (select max(identifier) from elite_game where player = "\uFFFF")) ` +
`and player != "\uFFFF" and valid_starting_at <= ` +
`(select play_timestamp from elite_game where identifier = (select max(identifier) from elite_game where player = "\uFFFF")) ` +
`order by play_timestamp desc`, [])
}
}

0 comments on commit 122810b

Please sign in to comment.