aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/states/play.ts
blob: a8a4727324a2cf03923e1902bd8076dccf153456 (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;
}