Skip to content

Commit

Permalink
feat: show previous leaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
wolimst committed May 28, 2024
1 parent b73d27b commit 53dce66
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 24 deletions.
66 changes: 58 additions & 8 deletions src/components/wordle/Statistics.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@
import LinkButton from '@/components/ui/core/LinkButton.svelte'
import Modal from '@/components/ui/core/Modal.svelte'
import Select, { type Option } from '@/components/ui/core/Select.svelte'
import ArrowLeftIcon from '@/components/ui/icons/ArrowLeft.svelte'
import ArrowRightIcon from '@/components/ui/icons/ArrowRight.svelte'
import ArrowRightCircleIcon from '@/components/ui/icons/ArrowRightCircle.svelte'
import ClockIcon from '@/components/ui/icons/Clock.svelte'
import RefreshIcon from '@/components/ui/icons/Refresh.svelte'
import ShareIcon from '@/components/ui/icons/Share.svelte'
import SpinnerIcon from '@/components/ui/icons/Spinner.svelte'
import StatisticsIcon from '@/components/ui/icons/Statistics.svelte'
import { GAMES, GAME_MODES, N_GUESSES, WORDLE_NAMES } from '@/constants'
import {
GAMES,
GAME_MODES,
N_GUESSES,
RETENTION_PERIOD_DAY,
WORDLE_NAMES,
} from '@/constants'
import { time } from '@/lib/utils'
import * as Wordle from '@/lib/wordle'
import { isInGamePage, refreshIfAlreadyInPage } from '@/routes/page'
Expand Down Expand Up @@ -81,6 +89,7 @@
let nextGameCountdownMillis = 0
let countdownIntervalId: number
let leaderboardDate = time.getCurrentKSTDate()
let refreshingLeaderboard = false
function countdownByOneSecond() {
Expand All @@ -90,6 +99,14 @@
}
}
function decreaseLeaderboardDate() {
leaderboardDate = new Date(leaderboardDate.getTime() - time.DAY_MS)
}
function increaseLeaderboardDate() {
leaderboardDate = new Date(leaderboardDate.getTime() + time.DAY_MS)
}
function updateStatistics() {
stats = statistics.getStats(
Wordle.getGameTypeString(
Expand Down Expand Up @@ -238,7 +255,9 @@
<div
class="tw-w-full tw-mt-4 tw-px-2 tw-inline-flex tw-flex-col tw-items-center"
>
<div class="tw-font-medium">✅추측 횟수 분포</div>
<div class="tw-font-medium">
<span class="tw-mr-0.5">✅</span><span>추측 횟수 분포</span>
</div>
{#each { length: N_GUESSES[gameType.nWordles][gameType.answerLength] - gameType.nWordles + 1 } as _, i}
{@const n = i + gameType.nWordles}
{@const guess = stats.guesses[n] || 0}
Expand All @@ -265,25 +284,56 @@
{@const configId = Wordle.generateConfigId(
gameMode.id,
gameType.nWordles,
gameType.answerLength
gameType.answerLength,
leaderboardDate
)}
<div
class="tw-w-full tw-relative tw-inline-flex tw-items-center tw-justify-center"
class="tw-w-full tw-relative tw-inline-flex tw-items-center tw-justify-center tw-gap-1.5"
>
<div class="tw-font-medium tw-m-auto">✨오늘의 기록</div>
<ClickButton
on:click={decreaseLeaderboardDate}
disabled={time.getCurrentKSTDate().getTime() -
leaderboardDate.getTime() >=
RETENTION_PERIOD_DAY * time.DAY_MS}
>
<ArrowLeftIcon width={16} />
</ClickButton>
<div class="tw-font-medium">
<span class="tw-mr-0.5">✨</span><span>
{#if leaderboardDate.getTime() !== time
.getCurrentKSTDate()
.getTime()}{`${leaderboardDate.getMonth() + 1}월${leaderboardDate.getDate()}일`}{:else}오늘{/if}의
기록</span
>
</div>
<ClickButton
on:click={increaseLeaderboardDate}
disabled={leaderboardDate.getTime() ===
time.getCurrentKSTDate().getTime()}
>
<ArrowRightIcon width={16} />
</ClickButton>
<div
class="tw-absolute tw-right-0 tw-inline-flex tw-items-center tw-gap-1"
>
{#if refreshingLeaderboard}
<SpinnerIcon width={18} />
<SpinnerIcon width={16} />
{/if}
<ClickButton on:click={refreshLeaderboard}>
<RefreshIcon width={18} />
<RefreshIcon width={16} />
</ClickButton>
</div>
</div>
{#if !$leaderboard.data[configId] || $leaderboard.data[configId].length === 0}
<div class="tw-text-sm tw-mt-1">첫 도전자가 되어보세요!</div>
<div class="tw-text-sm tw-mt-1">
{#if leaderboardDate.getTime() === time
.getCurrentKSTDate()
.getTime()}
첫 도전자가 되어보세요!
{:else}
앗, 플레이 기록이 없어요
{/if}
</div>
{:else}
<div class="leaderboard tw-w-full tw-mt-1 tw-text-sm tw-gap-1">
{#each $leaderboard.data[configId] as item, i}
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export const WAIT_DURATION_TO_SHOW_STATS_MS = 1500

export const DOM_ID_GAME_CONTAINER = 'game-container'

export const RETENTION_PERIOD_DAY = 15

export const WORKER_URL_GET_LEADERBOARD =
'https://api.handle.wolim.net/leaderboard'
export const WORKER_URL_POST_RESULT = 'https://api.handle.wolim.net/result'
22 changes: 16 additions & 6 deletions src/lib/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const KST_OFFSET_HOURS = -9
const SECOND_MS = 1_000
const MINUTE_MS = 60_000
const HOUR_MS = 3_600_000
export const KST_OFFSET_HOURS = -9
export const SECOND_MS = 1_000
export const MINUTE_MS = SECOND_MS * 60
export const HOUR_MS = MINUTE_MS * 60
export const DAY_MS = HOUR_MS * 24

export function getMillisecondsToMidnightInKST(): number {
const midnight = new Date().setUTCHours(24 + KST_OFFSET_HOURS, 0, 0, 0)
Expand All @@ -19,8 +20,17 @@ export function millisecondsToHHMMSS(milliseconds: number): string {
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
}

export function getShortDateStringInKST(): string {
return new Date()
export function getCurrentKSTDate(): Date {
return new Date(
new Date().toLocaleDateString('ko', {
timeZone: 'Asia/Seoul',
}) + ' GMT+0900'
)
}

export function getShortDateStringInKST(date?: Date): string {
const _date = date || new Date()
return _date
.toLocaleDateString('ko', {
timeZone: 'Asia/Seoul',
year: '2-digit',
Expand Down
20 changes: 20 additions & 0 deletions src/lib/wordle/game.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,11 @@ describe('tests for generateConfigId()', () => {
const id1 = generateConfigId('daily', nWordles, answerLength)
const id2 = generateConfigId('daily', nWordles, answerLength)
expect(id1).toStrictEqual(id2)

const date = new Date()
const id3 = generateConfigId('daily', nWordles, answerLength, date)
const id4 = generateConfigId('daily', nWordles, answerLength, date)
expect(id3).toStrictEqual(id4)
})

it('should return different ids if the date is changed', () => {
Expand All @@ -505,6 +510,21 @@ describe('tests for generateConfigId()', () => {
vi.useFakeTimers().setSystemTime(new Date().setUTCHours(24))
const id2 = generateConfigId('daily', nWordles, answerLength)
expect(id1).not.toStrictEqual(id2)

const date1 = new Date()
date1.setUTCHours(0)
const date2 = new Date()
date2.setUTCHours(24)
const id3 = generateConfigId('daily', nWordles, answerLength, date1)
const id4 = generateConfigId('daily', nWordles, answerLength, date2)
expect(id3).not.toStrictEqual(id4)
})

it('should return same ids when the date is not changed', () => {
vi.useFakeTimers().setSystemTime(new Date().setUTCHours(0))
const id1 = generateConfigId('daily', nWordles, answerLength)
const id2 = generateConfigId('daily', nWordles, answerLength)
expect(id1).toStrictEqual(id2)
})
})

Expand Down
7 changes: 4 additions & 3 deletions src/lib/wordle/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,15 @@ export function getGameTypeString(
export function generateConfigId(
mode: GameMode,
nWordles: number,
answerLength: number
answerLength: number,
date?: Date
): string {
const gameType = getGameTypeString(mode, nWordles, answerLength)

switch (mode) {
case 'daily': {
const date = time.getShortDateStringInKST()
return `${gameType}-${date}`
const dateString = time.getShortDateStringInKST(date)
return `${gameType}-${dateString}`
}
case 'free': {
const n = Math.floor(Math.random() * 2 ** 32)
Expand Down
8 changes: 3 additions & 5 deletions src/stores/wordle/savedata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PRODUCTION } from '@/constants'
import { PRODUCTION, RETENTION_PERIOD_DAY } from '@/constants'
import { time } from '@/lib/utils'
import * as Wordle from '@/lib/wordle'
import { base64, plaintext } from '@/stores/encoder'
import { persistentStore } from '@/stores/localStore'
Expand All @@ -25,9 +26,6 @@ export const savedata = {
loadByConfigId: loadByConfigId,
}

const DAY_MS = 24 * 60 * 60 * 1000
const RETENTION_PERIOD_DAY = 15

function convert(data: Wordle.GameData): Wordle.GameSaveData {
const { guesses, wordleData, ...savedata } = data
return {
Expand Down Expand Up @@ -116,7 +114,7 @@ function removeOldData(storage: SaveStorage): SaveStorage {
? new Date(data.metadata?.lastUpdatedDateISOString)
: getKSTDate(data.config)
const dayDiff = Math.round(
Math.abs(now.getTime() - gameDate.getTime()) / DAY_MS
Math.abs(now.getTime() - gameDate.getTime()) / time.DAY_MS
)
return Number.isSafeInteger(dayDiff) && dayDiff < RETENTION_PERIOD_DAY
})
Expand Down
4 changes: 2 additions & 2 deletions worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Router, cors, error } from 'itty-router'
import { z } from 'zod'

const LEADERBOARD_KEY = 'leaderboard'
const LEADERBOARD_ITEM_MAX_LENGTH = 3
const RETENTION_PERIOD_DAY = 7
const LEADERBOARD_ITEM_MAX_LENGTH = 5
const RETENTION_PERIOD_DAY = 15
const DAY_MS = 24 * 60 * 60 * 1000

function validate(obj: unknown): obj is LeaderboardItem {
Expand Down

0 comments on commit 53dce66

Please sign in to comment.