aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/states/play.ts
blob: 22d338c52414f621cdc98f5cd9ca81e460f34aa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { atom } from "jotai";
import type { components } from "../api/schema";
import type { SupportedLanguage } from "../types/SupportedLanguage";

const gameStartedAtAtom = atom<number | null>(null);
export const setGameStartedAtAtom = atom(null, (_, set, value: number | null) =>
	set(gameStartedAtAtom, value),
);

export type GameStateKind =
	| "loading"
	| "waiting"
	| "starting"
	| "gaming"
	| "finished";
type ExecutionStatus = components["schemas"]["ExecutionStatus"];
type LatestGameState = components["schemas"]["LatestGameState"];

export const gameStateKindAtom = atom<GameStateKind>((get) => {
	const now = get(currentTimestampAtom);
	if (!now) {
		return "loading";
	}
	const startedAt = get(gameStartedAtAtom);
	if (!startedAt) {
		return "waiting";
	}
	const durationSeconds = get(durationSecondsAtom);
	const finishedAt = startedAt + durationSeconds;
	if (now < startedAt) {
		return "starting";
	} else if (now < finishedAt) {
		return "gaming";
	} else {
		return "finished";
	}
});

const currentTimestampAtom = atom<number | null>(null);
export const setCurrentTimestampAtom = atom(null, (_, set) =>
	set(currentTimestampAtom, Math.floor(Date.now() / 1000)),
);

const durationSecondsAtom = atom<number>(0);
export const setDurationSecondsAtom = atom(null, (_, set, value: number) =>
	set(durationSecondsAtom, value),
);

export const startingLeftTimeSecondsAtom = atom<number | null>((get) => {
	const startedAt = get(gameStartedAtAtom);
	if (startedAt === null) {
		return null;
	}
	const currentTimestamp = get(currentTimestampAtom);
	if (currentTimestamp === null) {
		return null;
	}
	return Math.max(0, startedAt - currentTimestamp);
});

export const gamingLeftTimeSecondsAtom = atom<number | null>((get) => {
	const startedAt = get(gameStartedAtAtom);
	if (startedAt === null) {
		return null;
	}
	const durationSeconds = get(durationSecondsAtom);
	const finishedAt = startedAt + durationSeconds;
	const currentTimestamp = get(currentTimestampAtom);
	if (currentTimestamp === null) {
		return null;
	}
	return Math.min(durationSeconds, Math.max(0, finishedAt - currentTimestamp));
});

const rawStatusAtom = atom<ExecutionStatus>("none");
const rawScoreAtom = atom<number | null>(null);
export const statusAtom = atom<ExecutionStatus>((get) => {
	const isSubmittingCode = get(isSubmittingCodeAtom);
	if (isSubmittingCode) {
		return "running";
	} else {
		return get(rawStatusAtom);
	}
});
export const scoreAtom = atom<number | null>((get) => {
	return get(rawScoreAtom);
});

const isSubmittingCodeAtom = atom(false);
export const handleSubmitCodePreAtom = atom(null, (_, set) => {
	set(isSubmittingCodeAtom, true);
});
export const handleSubmitCodePostAtom = atom(null, (_, set) => {
	set(isSubmittingCodeAtom, false);
});

export const setLatestGameStateAtom = atom(
	null,
	(_, set, value: LatestGameState) => {
		set(rawStatusAtom, value.status);
		set(rawScoreAtom, value.score);
	},
);

function cleanCode(code: string, language: SupportedLanguage) {
	if (language === "php") {
		return code
			.replace(/\s+/g, "")
			.replace(/^<\?php/, "")
			.replace(/^<\?/, "")
			.replace(/\?>$/, "");
	} else {
		return code.replace(/\s+/g, "");
	}
}

export function calcCodeSize(
	code: string,
	language: SupportedLanguage,
): number {
	const trimmed = cleanCode(code, language);
	const utf8Encoded = new TextEncoder().encode(trimmed);
	return utf8Encoded.length;
}