aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml13
-rw-r--r--src/components/Chart/index.tsx139
-rw-r--r--src/components/Game/index.tsx28
-rw-r--r--src/components/Result/index.tsx17
-rw-r--r--src/index.tsx9
6 files changed, 202 insertions, 5 deletions
diff --git a/package.json b/package.json
index 71b5f82..7b07c3e 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"lodash": "^4.18.1",
"prop-types": "^15.8.1",
"react": "^19.2.7",
+ "react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.7",
"react-icons": "^5.6.0",
"react-is": "^19.2.7",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f2e4a61..828f20b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -52,6 +52,9 @@ dependencies:
react:
specifier: ^19.2.7
version: 19.2.7
+ react-chartjs-2:
+ specifier: ^5.3.1
+ version: 5.3.1(chart.js@4.5.1)(react@19.2.7)
react-dom:
specifier: ^19.2.7
version: 19.2.7(react@19.2.7)
@@ -2596,6 +2599,16 @@ packages:
unpipe: 1.0.0
dev: false
+ /react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.7):
+ resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==}
+ peerDependencies:
+ chart.js: ^4.1.1
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ dependencies:
+ chart.js: 4.5.1
+ react: 19.2.7
+ dev: false
+
/react-dom@19.2.7(react@19.2.7):
resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==}
peerDependencies:
diff --git a/src/components/Chart/index.tsx b/src/components/Chart/index.tsx
new file mode 100644
index 0000000..6aac91e
--- /dev/null
+++ b/src/components/Chart/index.tsx
@@ -0,0 +1,139 @@
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Tooltip,
+ Legend,
+} from "chart.js";
+import type { ChartOptions } from "chart.js";
+import { Bar } from "react-chartjs-2";
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Tooltip,
+ Legend
+);
+
+interface Props {
+ currentTry: number;
+ didGuess: boolean;
+ sessionDate: string;
+}
+
+interface HistoricalPlayData {
+ sessionDates?: string[];
+ guesses?: number[];
+ didGuess?: boolean[];
+}
+
+type GuessCount = 1 | 2 | 3 | 4 | 5 | 6;
+type Distribution = Record<GuessCount, number> & { NS: number };
+
+function getHistoricalPlayData(): HistoricalPlayData {
+ try {
+ return JSON.parse(localStorage.getItem("historicalPlayData") || "{}");
+ } catch {
+ return {};
+ }
+}
+
+function addResultToDistribution(
+ distribution: Distribution,
+ guessCount: number,
+ didGuess: boolean
+) {
+ if (!didGuess) {
+ distribution.NS += 1;
+ return;
+ }
+
+ if (guessCount >= 1 && guessCount <= 6) {
+ distribution[guessCount as GuessCount] += 1;
+ }
+}
+
+export default function GuessDistributionChart({
+ currentTry,
+ didGuess,
+ sessionDate,
+}: Props) {
+ const historicalPlayData = getHistoricalPlayData();
+
+ const distribution: Distribution = {
+ 1: 0,
+ 2: 0,
+ 3: 0,
+ 4: 0,
+ 5: 0,
+ 6: 0,
+ NS: 0,
+ };
+
+ if (historicalPlayData.guesses && historicalPlayData.didGuess) {
+ historicalPlayData.guesses.forEach((guessCount, index) => {
+ addResultToDistribution(
+ distribution,
+ guessCount,
+ historicalPlayData.didGuess?.[index] ?? false
+ );
+ });
+ }
+
+ if (!historicalPlayData.sessionDates?.includes(sessionDate)) {
+ addResultToDistribution(distribution, currentTry, didGuess);
+ }
+
+ const data = {
+ labels: ["1", "2", "3", "4", "5", "6", "NS"],
+ datasets: [
+ {
+ data: [
+ distribution[1],
+ distribution[2],
+ distribution[3],
+ distribution[4],
+ distribution[5],
+ distribution[6],
+ distribution.NS,
+ ],
+ backgroundColor: "#6aaa64",
+ borderRadius: 4,
+ },
+ ],
+ };
+
+ const options: ChartOptions<"bar"> = {
+ responsive: true,
+ plugins: {
+ legend: {
+ display: false,
+ },
+ tooltip: {
+ enabled: true,
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false,
+ },
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ precision: 0,
+ },
+ },
+ },
+ };
+
+ return (
+ <div style={{ maxWidth: 500, width: "100%", marginBottom: "20px" }}>
+ <h3>Statistics</h3>
+ <Bar data={data} options={options} />
+ </div>
+ );
+}
diff --git a/src/components/Game/index.tsx b/src/components/Game/index.tsx
index 59f9289..657b2af 100644
--- a/src/components/Game/index.tsx
+++ b/src/components/Game/index.tsx
@@ -47,13 +47,36 @@ export function Game({
!!dailyDate &&
!hasFinishedResponseDaily &&
new Date(getUtcDate()) > new Date(dailyDate);
+ const sessionDate = dailyDate ?? getUtcDate();
React.useEffect(() => {
if (mode !== "daily") return;
if (!hasFinishedCurrentRound) return;
+ localStorage.setItem("recentFinishedPlay", sessionDate);
- localStorage.setItem("recentFinishedPlay", dailyDate ?? getUtcDate());
- }, [mode, hasFinishedCurrentRound, dailyDate]);
+ const historicalPlayData = localStorage.getItem("historicalPlayData");
+ if (historicalPlayData === null) {
+ localStorage.setItem(
+ "historicalPlayData",
+ JSON.stringify({
+ sessionDates: [sessionDate],
+ guesses: [currentTry],
+ didGuess: [didGuess],
+ })
+ );
+ } else {
+ const parsedData = JSON.parse(historicalPlayData);
+ if (parsedData.sessionDates.includes(sessionDate)) return;
+ localStorage.setItem(
+ "historicalPlayData",
+ JSON.stringify({
+ sessionDates: [...parsedData.sessionDates, sessionDate],
+ guesses: [...parsedData.guesses, currentTry],
+ didGuess: [...parsedData.didGuess, didGuess],
+ })
+ );
+ }
+ }, [mode, hasFinishedCurrentRound, sessionDate, currentTry, didGuess]);
if (isBlocked) {
return <h1>Daily MIXX is not available yet. Check back soon!</h1>;
@@ -67,6 +90,7 @@ export function Game({
todaysSolution={todaysSolution}
guesses={guesses}
mode={mode}
+ sessionDate={sessionDate}
onPlayAgain={onPlayAgain}
/>
);
diff --git a/src/components/Result/index.tsx b/src/components/Result/index.tsx
index 4ce7f35..c74e4f8 100644
--- a/src/components/Result/index.tsx
+++ b/src/components/Result/index.tsx
@@ -9,6 +9,7 @@ import { MiniYouTubePlayer } from "../MiniYouTubePlayer";
import * as Styled from "./index.styled";
import { theme } from "../../constants";
+import GuessDistributionChart from '../Chart';
interface SolutionProps {
didGuess: boolean;
@@ -89,6 +90,7 @@ interface Props {
todaysSolution: Song;
guesses: GuessType[];
mode?: "daily" | "unlimited";
+ sessionDate: string;
onPlayAgain?: () => void;
}
@@ -98,6 +100,7 @@ export function Result({
guesses,
currentTry,
mode = "daily",
+ sessionDate,
onPlayAgain,
}: Props) {
const now = new Date();
@@ -126,6 +129,13 @@ export function Result({
currentTry={currentTry}
isUnlimited={isUnlimited}
/>
+ {!isUnlimited && (
+ <GuessDistributionChart
+ currentTry={currentTry}
+ didGuess={didGuess}
+ sessionDate={sessionDate}
+ />
+ )}
{!isUnlimited && <ShareButton guesses={guesses} variant="green" />}
@@ -152,6 +162,13 @@ export function Result({
currentTry={currentTry}
isUnlimited={isUnlimited}
/>
+ {!isUnlimited && (
+ <GuessDistributionChart
+ currentTry={currentTry}
+ didGuess={didGuess}
+ sessionDate={sessionDate}
+ />
+ )}
{!isUnlimited && <ShareButton guesses={guesses} variant="red" />}
diff --git a/src/index.tsx b/src/index.tsx
index e1b6a22..c4a2bfc 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,15 +1,18 @@
import React from "react";
-import ReactDOM from "react-dom";
+import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import { theme } from "./constants";
import "./index.css";
import App from "./app";
-ReactDOM.render(
+const root = ReactDOM.createRoot(
+ document.getElementById("root") as HTMLElement
+);
+
+root.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</React.StrictMode>,
- document.getElementById("root")
);
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage