From 71a6130b655444bdce557a1055a06d2a140c7e63 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Tue, 9 Apr 2024 10:56:20 +0300 Subject: [PATCH 01/12] removing console.logs --- web/src/components/atbat/TokenCard.tsx | 2 -- web/src/components/playing/DetailedStat.tsx | 4 ---- web/src/components/playing/MainStat.tsx | 3 --- 3 files changed, 9 deletions(-) diff --git a/web/src/components/atbat/TokenCard.tsx b/web/src/components/atbat/TokenCard.tsx index 1d21908a..621183bb 100644 --- a/web/src/components/atbat/TokenCard.tsx +++ b/web/src/components/atbat/TokenCard.tsx @@ -18,7 +18,6 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = } const API_URL = "https://api.fullcount.xyz/stats"; const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - console.log(stat.data); return stat.data; }, { @@ -34,7 +33,6 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = } const API_URL = "https://api.fullcount.xyz/stats"; const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - console.log(stat.data); return stat.data; }, { diff --git a/web/src/components/playing/DetailedStat.tsx b/web/src/components/playing/DetailedStat.tsx index 45c69eea..c24e5057 100644 --- a/web/src/components/playing/DetailedStat.tsx +++ b/web/src/components/playing/DetailedStat.tsx @@ -68,10 +68,6 @@ const DataRow = ({ label, data }: { label: string; data: string }) => { }; const DetailedStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { - useEffect(() => { - console.log(stats); - }, []); - return (
season statistics
diff --git a/web/src/components/playing/MainStat.tsx b/web/src/components/playing/MainStat.tsx index 6dbb8683..016c4b92 100644 --- a/web/src/components/playing/MainStat.tsx +++ b/web/src/components/playing/MainStat.tsx @@ -26,9 +26,6 @@ const pitcherRecord = (stats: PlayerStats): string => { }; const MainStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { - useEffect(() => { - console.log(stats, isPitcher); - }, []); return ( <> {isPitcher && stats.points_data?.pitching_data && ( From 1702837ec2bdcaf2d304f596e63a6fb91908338e Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Tue, 9 Apr 2024 11:54:15 +0300 Subject: [PATCH 02/12] pass token to Outcome instead of Context's --- web/src/components/atbat/AtBatView.tsx | 1 + web/src/components/atbat/Outcome2.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/components/atbat/AtBatView.tsx b/web/src/components/atbat/AtBatView.tsx index 4d184ab4..e50d2133 100644 --- a/web/src/components/atbat/AtBatView.tsx +++ b/web/src/components/atbat/AtBatView.tsx @@ -313,6 +313,7 @@ const AtBatView: React.FC = () => { {atBatState.data?.atBat && ( { const { selectedToken } = useGameContext(); useEffect(() => { @@ -49,12 +51,12 @@ const Outcome2 = ({
{/*
*/}
- {atBat && atBat.outcome !== 0 && selectedToken && ( + {atBat && atBat.outcome !== 0 && forToken && (
Date: Tue, 9 Apr 2024 12:30:46 +0300 Subject: [PATCH 03/12] API for onboarding atbat --- web/src/components/atbat/OnboardingAPI.ts | 170 ++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 web/src/components/atbat/OnboardingAPI.ts diff --git a/web/src/components/atbat/OnboardingAPI.ts b/web/src/components/atbat/OnboardingAPI.ts new file mode 100644 index 00000000..3884e179 --- /dev/null +++ b/web/src/components/atbat/OnboardingAPI.ts @@ -0,0 +1,170 @@ +import { AtBatStatus, BatterReveal, PitcherReveal, SessionStatus } from "../../types"; + +export const selectedToken = { + address: "0xf40c0961A9CC5c037B92D2cb48167F7f62Dd7cD0", + id: "7", + image: "https://static.fullcount.xyz/Beer_League_Ballers/p2.png", + isStaked: true, + stakedSessionID: 0, + tokenProgress: 3, + name: "newbie", +}; + +export const dummy = { + address: "0xf40c0961A9CC5c037B92D2cb48167F7f62Dd7cD0", + id: "1", + image: "https://static.fullcount.xyz/Beer_League_Ballers/p1.png", + isStaked: true, + stakedSessionID: 0, + tokenProgress: 3, + name: "mumintrl", +}; + +const getPitchOutcome = (pitcherReveal: PitcherReveal, batterReveal: BatterReveal) => { + const isStrikeZone = + Number(pitcherReveal.horizontal) > 0 && + Number(pitcherReveal.horizontal) < 4 && + Number(pitcherReveal.vertical) > 0 && + Number(pitcherReveal.vertical) < 4; + if (batterReveal.kind === "2") { + return isStrikeZone ? 0 : 1; + } + if ( + pitcherReveal.horizontal === batterReveal.horizontal && + pitcherReveal.vertical === batterReveal.vertical + ) { + return 6; + } + return 0; +}; + +export const getAtBAtOutcome = (pitches: SessionStatus[]) => { + const balls = pitches.filter((p) => p.outcome === 1).length; + const strikes = pitches.filter((p) => p.outcome === 0 && p.progress === 5).length; + let outcome = 0; + if (pitches[pitches.length - 1].outcome === 6) { + outcome = 6; + } + if (balls === 4) { + outcome = 2; + } + if (strikes === 3) { + outcome = 1; + } + return { balls, strikes, outcome }; +}; + +const emptyPitch = { + progress: 3, + outcome: 0, + sessionID: 0, + didPitcherCommit: true, + didBatterCommit: false, + didPitcherReveal: true, + didBatterReveal: false, + pitcherReveal: { nonce: "0", speed: "0", vertical: "0", horizontal: "0" }, + batterReveal: { + nonce: "0", + kind: "0", + vertical: "0", + horizontal: "0", + }, + phaseStartTimestamp: String(Math.floor(Date.now() / 1000)), +}; + +export const initialAtBatState = { + pitcher: dummy, + batter: selectedToken, + balls: 0, + strikes: 0, + outcome: 0, + id: 0, + pitches: [{ ...emptyPitch }], + numberOfSessions: 1, +}; + +type Cell = [number, number]; + +function findDistantCell(cell: Cell): Cell { + const [x, y] = cell; + const distantX = x <= 2 ? 4 : 0; + const distantY = y <= 2 ? 4 : 0; + return [distantX, distantY]; +} + +const getPitch = (swing: BatterReveal, pitchNumber: number) => { + if (pitchNumber < 2) { + if (swing.kind === "2") { + return { + nonce: "0", + speed: "0", + vertical: "2", + horizontal: "2", + }; + } + const [horizontal, vertical] = findDistantCell([ + Number(swing.horizontal), + Number(swing.vertical), + ]); + return { + nonce: "0", + speed: "0", + vertical: String(vertical), + horizontal: String(horizontal), + }; + } else { + if (swing.kind === "2") { + return { + nonce: "0", + speed: "0", + vertical: String(pitchNumber - 2), + horizontal: "0", + }; + } + return { + nonce: "0", + speed: "1", + vertical: swing.vertical, + horizontal: swing.horizontal, + }; + } +}; + +export const getAtBat = (swings: BatterReveal[], currentState: AtBatStatus) => { + const newPitches = currentState.pitches.map((p, idx) => { + if (!p.didBatterReveal && swings[idx]) { + const batterReveal = swings[idx]; + const pitcherReveal = getPitch(swings[idx], idx); + return { + progress: 5, + outcome: getPitchOutcome(pitcherReveal, batterReveal), + didBatterReveal: true, + didPitcherReveal: true, + didPitcherCommit: true, + didBatterCommit: true, + batterReveal, + pitcherReveal, + phaseStartTimestamp: "0", + sessionID: 0, + }; + } + return p; + }); + + const { balls, strikes, outcome } = getAtBAtOutcome(newPitches); + if (newPitches.length === swings.length && outcome === 0) { + newPitches.push({ + ...emptyPitch, + phaseStartTimestamp: String(Math.floor(Date.now() / 1000)), + }); + } + const numberOfSessions = newPitches.length; + return { + ...currentState, + pitches: newPitches, + numberOfSessions, + balls, + strikes, + outcome, + }; +}; From 428e3dd1b8369d56cf6b90b3ea3a343f081d7cb0 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Tue, 9 Apr 2024 12:31:20 +0300 Subject: [PATCH 04/12] option for open pitch history by default --- web/src/components/atbat/Score.tsx | 12 ++++++++++-- web/src/components/atbat/ScoreForDesktop.tsx | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/web/src/components/atbat/Score.tsx b/web/src/components/atbat/Score.tsx index 05e9534a..34c0a677 100644 --- a/web/src/components/atbat/Score.tsx +++ b/web/src/components/atbat/Score.tsx @@ -8,9 +8,17 @@ import ChevronUp from "../icons/ChevronUp"; import ChevronDown from "../icons/ChevronDown"; import PitchHistory from "./PitchHistory"; -const Score = ({ atBat, pitch }: { atBat: AtBatStatus; pitch: SessionStatus }) => { +const Score = ({ + atBat, + pitch, + openHistory = false, +}: { + atBat: AtBatStatus; + pitch: SessionStatus; + openHistory?: boolean; +}) => { const { secondsPerPhase } = useGameContext(); - const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [isHistoryOpen, setIsHistoryOpen] = useState(openHistory); return (
diff --git a/web/src/components/atbat/ScoreForDesktop.tsx b/web/src/components/atbat/ScoreForDesktop.tsx index 7583faf4..c3a0326c 100644 --- a/web/src/components/atbat/ScoreForDesktop.tsx +++ b/web/src/components/atbat/ScoreForDesktop.tsx @@ -8,9 +8,17 @@ import ChevronUpLarge from "../icons/ChevronUpLarge"; import ChevronDownLarge from "../icons/ChevronDownLarge"; import DotsCounterLarge from "../sessions/DotsCounterLarge"; -const ScoreForDesktop = ({ atBat, pitch }: { atBat: AtBatStatus; pitch: SessionStatus }) => { +const ScoreForDesktop = ({ + atBat, + pitch, + openHistory = false, +}: { + atBat: AtBatStatus; + pitch: SessionStatus; + openHistory?: boolean; +}) => { const { secondsPerPhase } = useGameContext(); - const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [isHistoryOpen, setIsHistoryOpen] = useState(openHistory); return (
From 18fd67b990a2f610a75e16caf1b7e0b7a5dccf6a Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Tue, 9 Apr 2024 12:32:28 +0300 Subject: [PATCH 05/12] export delay func --- web/src/tokenInterfaces/FullcountPlayerAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/tokenInterfaces/FullcountPlayerAPI.ts b/web/src/tokenInterfaces/FullcountPlayerAPI.ts index 022049cc..ebf378ff 100644 --- a/web/src/tokenInterfaces/FullcountPlayerAPI.ts +++ b/web/src/tokenInterfaces/FullcountPlayerAPI.ts @@ -121,7 +121,7 @@ export async function startSessionFullcountPlayer({ return { sessionID: data.session_id, sign: "0x" + data.signature }; } -const delay = (delayInms: number) => { +export const delay = (delayInms: number) => { return new Promise((resolve) => setTimeout(resolve, delayInms)); }; From 42445ca7d562a7a7f81f506358367015c4552fd3 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Tue, 9 Apr 2024 12:32:51 +0300 Subject: [PATCH 06/12] onboarding atBat --- web/pages/atbat/index.tsx | 12 ++ web/src/components/atbat/AtBatView2.tsx | 163 +++++++++++++++++ .../components/atbat/BatterViewMobile2.tsx | 64 +++++++ web/src/components/playing/PlayerView2.tsx | 168 ++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 web/pages/atbat/index.tsx create mode 100644 web/src/components/atbat/AtBatView2.tsx create mode 100644 web/src/components/atbat/BatterViewMobile2.tsx create mode 100644 web/src/components/playing/PlayerView2.tsx diff --git a/web/pages/atbat/index.tsx b/web/pages/atbat/index.tsx new file mode 100644 index 00000000..6837821c --- /dev/null +++ b/web/pages/atbat/index.tsx @@ -0,0 +1,12 @@ +import Layout from "../../src/components/layout/layout"; +import AtBatView2 from "../../src/components/atbat/AtBatView2"; + +const AtBat = () => { + return ( + + + + ); +}; + +export default AtBat; diff --git a/web/src/components/atbat/AtBatView2.tsx b/web/src/components/atbat/AtBatView2.tsx new file mode 100644 index 00000000..bdc60328 --- /dev/null +++ b/web/src/components/atbat/AtBatView2.tsx @@ -0,0 +1,163 @@ +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import styles from "./AtBatView.module.css"; +import Score from "./Score"; +import AtBatFooter from "./AtBatFooter"; +import PitcherViewMobile from "../playing/PitcherViewMobile"; +import { AtBatStatus, BatterReveal, OwnedToken, Token } from "../../types"; +import { FULLCOUNT_ASSETS_PATH } from "../../constants"; +import { Image, useMediaQuery } from "@chakra-ui/react"; +import Outcome2, { sessionOutcomeType } from "./Outcome2"; +import TokenCard from "./TokenCard"; +import ScoreForDesktop from "./ScoreForDesktop"; +import BatterViewMobile2 from "./BatterViewMobile2"; +import { getAtBat, initialAtBatState, selectedToken } from "./OnboardingAPI"; +import { outcomeType, sessionOutcomes } from "./AtBatView"; + +const AtBatView2: React.FC = () => { + const router = useRouter(); + const [showPitchOutcome, setShowPitchOutcome] = useState(false); + const [currentSessionIdx, setCurrentSessionIdx] = useState(0); + const [isBigView] = useMediaQuery("(min-width: 1024px)"); + const [swings, setSwings] = useState([]); + const [atBat, setAtBat] = useState(initialAtBatState); + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + const [windowHeight, setWindowHeight] = useState(undefined); + + const updateHeight = () => { + setWindowHeight(window.innerHeight); + }; + + useEffect(() => { + window.addEventListener("resize", updateHeight); + return () => window.removeEventListener("resize", updateHeight); + }, []); + + const isSameToken = (a: Token | undefined, b: Token | undefined) => { + if (!a || !b) return false; + return a.id === b.id && a.address === b.address; + }; + + const handleSwing = (swing: BatterReveal) => { + const newSwings = [...swings, swing]; + const newAtBat = getAtBat(newSwings, atBat); + setTimeout(() => { + if (newAtBat.numberOfSessions - 1 !== currentSessionIdx) { + setCurrentSessionIdx(newAtBat.numberOfSessions - 1); + setShowPitchOutcome(true); + setTimeout(() => setShowPitchOutcome(false), 5000); + } + if (newAtBat.outcome !== 0) { + setShowPitchOutcome(true); + } + setSwings(newSwings); + setAtBat(newAtBat); + }, 5000); + }; + + return ( +
+ {""} + {atBat && showPitchOutcome && atBat.outcome !== 0 && atBat.pitches.length > 0 && ( +
router.push("/")}> + Go to home page +
+ )} + + {!isBigView && } + {isBigView && atBat.outcome === 0 && !showPitchOutcome && ( + + )} + {atBat && + showPitchOutcome && + atBat.pitches.length > 0 && + selectedToken && + atBat.outcome === 0 && ( +
+ {sessionOutcomes[atBat.pitches[atBat.numberOfSessions - 2].outcome]}! +
+ )} + {atBat.outcome !== 0 && selectedToken && ( +
+ {outcomeType([selectedToken], atBat) === "positive" ? "you win!" : "you lose!"} +
+ )} + {atBat.outcome === 0 && + !showPitchOutcome && + atBat.pitches[atBat.numberOfSessions - 1].progress !== 2 && + atBat.pitches[currentSessionIdx].progress !== 6 && ( +
+ {isBigView && atBat.pitcher && } + {selectedToken && isSameToken(selectedToken, atBat.pitcher) && ( + + )} + {selectedToken && isSameToken(selectedToken, atBat.batter) && ( + + )} + {isBigView && atBat.batter && } +
+ )} + {atBat && showPitchOutcome && atBat.pitches.length > 0 && ( + <> + {atBat && ( + + )} + + )} + {!showPitchOutcome && !isBigView && } +
+ ); +}; + +export default AtBatView2; diff --git a/web/src/components/atbat/BatterViewMobile2.tsx b/web/src/components/atbat/BatterViewMobile2.tsx new file mode 100644 index 00000000..221e9141 --- /dev/null +++ b/web/src/components/atbat/BatterViewMobile2.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from "react"; +import { useMutation, useQueryClient } from "react-query"; +import { SessionStatus } from "../playing/PlayView"; +import { BatterReveal, OwnedToken } from "../../types"; +import PlayerView2 from "../playing/PlayerView2"; +import { delay } from "../../tokenInterfaces/FullcountPlayerAPI"; + +const BatterViewMobile2 = ({ + sessionStatus, + token, + addSwing, +}: { + sessionStatus: SessionStatus; + token: OwnedToken; + addSwing: (arg0: BatterReveal) => void; +}) => { + const [isCommitted, setIsCommitted] = useState(false); + const [isRevealed, setIsRevealed] = useState(sessionStatus.didBatterReveal); + + const commitSwing = useMutation( + async ({ + sign, + commit, + }: { + sign?: string; + commit?: { nonce: string; vertical: number; horizontal: number; actionChoice: number }; + }) => { + await delay(4000); + if (commit) { + addSwing({ + nonce: commit.nonce, + vertical: String(commit.vertical), + horizontal: String(commit.horizontal), + kind: String(commit.actionChoice), + }); + } + }, + { + onSuccess: () => { + setIsCommitted(true); + }, + onError: (e: Error) => { + console.log("Commit failed." + e?.message); + }, + }, + ); + + useEffect(() => { + setIsRevealed(sessionStatus.didBatterReveal); + setIsCommitted(sessionStatus.didBatterCommit); + }, [sessionStatus]); + + return ( + + ); +}; + +export default BatterViewMobile2; diff --git a/web/src/components/playing/PlayerView2.tsx b/web/src/components/playing/PlayerView2.tsx new file mode 100644 index 00000000..78a5275d --- /dev/null +++ b/web/src/components/playing/PlayerView2.tsx @@ -0,0 +1,168 @@ +import { Flex, Image, Spinner, Text } from "@chakra-ui/react"; +import ActionTypeSelector from "./ActionTypeSelector"; +import GridComponent from "./GridComponent"; +import globalStyles from "../GlobalStyles.module.css"; +import styles from "./PlayView.module.css"; +import { getPitchDescription, getSwingDescription } from "../../utils/messages"; +import { getRowCol, SessionStatus } from "./PlayView"; +import React, { useState } from "react"; +import AnimatedMessage from "../AnimatedMessage"; +import { FULLCOUNT_ASSETS_PATH } from "../../constants"; + +const swingKinds = ["Contact", "Power", "Take"]; +const pitchSpeeds = ["Fast", "Slow"]; + +const PlayerView2 = ({ + sessionStatus, + isPitcher, + commitMutation, + isCommitted, + isRevealed, +}: { + sessionStatus: SessionStatus; + isPitcher: boolean; + commitMutation: any; + isCommitted: boolean; + isRevealed: boolean; +}) => { + const [actionChoice, setActionChoice] = useState(0); + const [gridIndex, setGridIndex] = useState(-1); + const [showTooltip, setShowTooltip] = useState(false); + + const typeChangeHandle = (value: number) => { + setActionChoice(value); + if (isPitcher) { + return; + } + if (value !== 2 && gridIndex === -1) { + setGridIndex(12); + } + if (value === 2) { + setGridIndex(-1); + } + }; + + const handleCommit = async () => { + if (gridIndex === -1) { + if (isPitcher || actionChoice !== 2) { + setShowTooltip(true); + setTimeout(() => { + setShowTooltip(false); + }, 3000); + return; + } + } + const vertical = gridIndex === -1 ? 0 : getRowCol(gridIndex)[0]; + const horizontal = gridIndex === -1 ? 0 : getRowCol(gridIndex)[1]; + const nonce = ""; + const commit = { + nonce, + actionChoice, + vertical, + horizontal, + }; + commitMutation.mutate({ sign: undefined, commit }); + }; + + const columnCenters = [1.955, 7.1, 13.48, 19.86, 25.01]; + const rowCenters = [1.955, 7.82, 15.64, 23.46, 29.33]; + + return ( + +
+ + {!isPitcher && actionChoice !== 2 && gridIndex !== -1 && ( + {"o"} + )} + {isPitcher && gridIndex !== -1 && ( + {"o"} + )} +
+ +
+ {!isCommitted && ( + + )} + {isCommitted && + ((!isPitcher && !sessionStatus.didPitcherCommit) || + (isPitcher && !sessionStatus.didBatterCommit)) && ( +
+ +
+ )} + {isRevealed && + ((!isPitcher && !sessionStatus.didPitcherReveal) || + (isPitcher && !sessionStatus.didBatterReveal)) && ( +
+ +
+ )} +
+
+ ); +}; + +export default PlayerView2; From 22fe89f7f1d0695c2d4faca617d04895aa4072e4 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Wed, 10 Apr 2024 16:29:11 +0300 Subject: [PATCH 07/12] Onboarding character form --- .../atbat/OnboardingCharacter.module.css | 43 ++++++++++ .../components/atbat/OnboardingCharacter.tsx | 80 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 web/src/components/atbat/OnboardingCharacter.module.css create mode 100644 web/src/components/atbat/OnboardingCharacter.tsx diff --git a/web/src/components/atbat/OnboardingCharacter.module.css b/web/src/components/atbat/OnboardingCharacter.module.css new file mode 100644 index 00000000..1979fea5 --- /dev/null +++ b/web/src/components/atbat/OnboardingCharacter.module.css @@ -0,0 +1,43 @@ +.container { + display: flex; + width: 100vw; + min-height: 100vh; + padding: 20px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; + /*max-width: 525px;*/ + position: absolute; + left: 50%; + z-index: 20; + transform: translateX(-50%); + background-color: #fcecd9; +} + +.input { + width: 245px; + place-self: center; +} + +.button, .inactiveButton { + display: flex; + padding: 4px 19px; + justify-content: center; + align-items: center; + gap: 10px; + border: 1px solid #262019; + background: #328449; + flex: 1 0 0; + color: #FFF; + text-align: center; + font-family: Bangers, cursive; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 1.2px; + min-height: 35.5px; + max-width: 245px; + +} \ No newline at end of file diff --git a/web/src/components/atbat/OnboardingCharacter.tsx b/web/src/components/atbat/OnboardingCharacter.tsx new file mode 100644 index 00000000..c15d1d17 --- /dev/null +++ b/web/src/components/atbat/OnboardingCharacter.tsx @@ -0,0 +1,80 @@ +import Image from "next/image"; + +import styles from "../tokens/CreateNewCharacter.module.css"; +import localStyles from "./OnboardingCharacter.module.css"; +import React, { useEffect, useState } from "react"; +import { blbImage } from "../../constants"; +const NUMBER_OF_IMAGES = 8; + +const images: number[] = []; +for (let i = 0; i < NUMBER_OF_IMAGES; i += 1) { + images.push(i); +} + +const OnboardingCharacter = ({ + onClose, + onChange, +}: { + onClose: () => void; + onChange: (name: string, image: string) => void; +}) => { + const [name, setName] = useState("Guest_0420"); + const [imageIndex, setImageIndex] = useState(7); + + useEffect(() => { + setName("Guest_0420"); + setImageIndex(7); + }, []); + + useEffect(() => { + onChange(name, blbImage(imageIndex)); + }, [imageIndex, name]); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + onClose(); + } + }; + return ( +
+
+ {""} +
+ {images.map((_, idx: number) => ( + {`img${idx}`} setImageIndex(idx)} + /> + ))} +
+
Choose an image.
+ setName(e.target.value)} + onKeyDown={handleKeyDown} + spellCheck={false} + className={localStyles.input} + /> +
+
+ +
+
+ ); +}; + +export default OnboardingCharacter; From aabd15260f7bfac15e0f4d07dcbc51a45ec534d2 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Wed, 10 Apr 2024 16:29:39 +0300 Subject: [PATCH 08/12] token with empty batter stat for onboarding --- web/src/components/atbat/OnboardingAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/atbat/OnboardingAPI.ts b/web/src/components/atbat/OnboardingAPI.ts index 3884e179..3d84ef14 100644 --- a/web/src/components/atbat/OnboardingAPI.ts +++ b/web/src/components/atbat/OnboardingAPI.ts @@ -2,7 +2,7 @@ import { AtBatStatus, BatterReveal, PitcherReveal, SessionStatus } from "../../t export const selectedToken = { address: "0xf40c0961A9CC5c037B92D2cb48167F7f62Dd7cD0", - id: "7", + id: "2", image: "https://static.fullcount.xyz/Beer_League_Ballers/p2.png", isStaked: true, stakedSessionID: 0, From 051c2cbbcf8d646653839a1da185d8053742b89e Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Wed, 10 Apr 2024 16:30:18 +0300 Subject: [PATCH 09/12] using onboarding character --- web/src/components/atbat/AtBatView2.tsx | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/web/src/components/atbat/AtBatView2.tsx b/web/src/components/atbat/AtBatView2.tsx index bdc60328..07a28e1a 100644 --- a/web/src/components/atbat/AtBatView2.tsx +++ b/web/src/components/atbat/AtBatView2.tsx @@ -5,7 +5,7 @@ import Score from "./Score"; import AtBatFooter from "./AtBatFooter"; import PitcherViewMobile from "../playing/PitcherViewMobile"; import { AtBatStatus, BatterReveal, OwnedToken, Token } from "../../types"; -import { FULLCOUNT_ASSETS_PATH } from "../../constants"; +import { blbImage, FULLCOUNT_ASSETS_PATH } from "../../constants"; import { Image, useMediaQuery } from "@chakra-ui/react"; import Outcome2, { sessionOutcomeType } from "./Outcome2"; import TokenCard from "./TokenCard"; @@ -13,6 +13,7 @@ import ScoreForDesktop from "./ScoreForDesktop"; import BatterViewMobile2 from "./BatterViewMobile2"; import { getAtBat, initialAtBatState, selectedToken } from "./OnboardingAPI"; import { outcomeType, sessionOutcomes } from "./AtBatView"; +import OnboardingCharacter from "./OnboardingCharacter"; const AtBatView2: React.FC = () => { const router = useRouter(); @@ -21,6 +22,9 @@ const AtBatView2: React.FC = () => { const [isBigView] = useMediaQuery("(min-width: 1024px)"); const [swings, setSwings] = useState([]); const [atBat, setAtBat] = useState(initialAtBatState); + const [isCharacterSelectOpen, setIsCharacterSelectOpen] = useState(true); + const [name, setName] = useState("Guest_0420"); + const [image, setImage] = useState(blbImage(7)); useEffect(() => { window.scrollTo(0, 0); @@ -32,6 +36,17 @@ const AtBatView2: React.FC = () => { setWindowHeight(window.innerHeight); }; + useEffect(() => { + const newAtBat = initialAtBatState; + setAtBat({ + ...newAtBat, + batter: { ...selectedToken, name, image }, + pitches: [ + { ...newAtBat.pitches[0], phaseStartTimestamp: String(Math.floor(Date.now() / 1000)) }, + ], + }); + }, [name, image, isCharacterSelectOpen]); + useEffect(() => { window.addEventListener("resize", updateHeight); return () => window.removeEventListener("resize", updateHeight); @@ -64,6 +79,15 @@ const AtBatView2: React.FC = () => { className={styles.container} style={{ maxHeight: windowHeight ? `${windowHeight}px` : "100vh" }} > + {isCharacterSelectOpen && ( + setIsCharacterSelectOpen(false)} + onChange={(name, image) => { + setName(name); + setImage(image); + }} + /> + )} Date: Wed, 10 Apr 2024 16:30:42 +0300 Subject: [PATCH 10/12] handling empty stat and stat errors --- web/src/components/atbat/TokenCard.tsx | 114 ++++++++++++------ web/src/components/playing/DetailedStat.tsx | 42 ++++++- web/src/components/playing/HeatMap.tsx | 30 +++-- .../components/playing/MainStat.module.css | 7 +- web/src/components/playing/MainStat.tsx | 35 +++++- 5 files changed, 170 insertions(+), 58 deletions(-) diff --git a/web/src/components/atbat/TokenCard.tsx b/web/src/components/atbat/TokenCard.tsx index 621183bb..0de6f5d4 100644 --- a/web/src/components/atbat/TokenCard.tsx +++ b/web/src/components/atbat/TokenCard.tsx @@ -17,11 +17,21 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = return; } const API_URL = "https://api.fullcount.xyz/stats"; - const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - return stat.data; + try { + const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); + return stat.data; + } catch (e) { + console.log({ token, e }); + return 0; + } }, { enabled: !!token && isPitcher, + retryDelay: (attemptIndex) => (attemptIndex < 1 ? 5000 : 10000), + retry: (failureCount) => { + return failureCount < 3; + }, + refetchInterval: 5000, }, ); @@ -32,11 +42,21 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = return; } const API_URL = "https://api.fullcount.xyz/stats"; - const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - return stat.data; + try { + const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); + return stat.data; + } catch (e) { + console.log({ token, e }); + return; + } }, { enabled: !!token && !isPitcher, + retryDelay: (attemptIndex) => (attemptIndex < 1 ? 5000 : 10000), + retry: (failureCount) => { + return failureCount < 3; + }, + refetchInterval: 5000, }, ); @@ -47,23 +67,33 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = return; } const API_URL = "https://api.fullcount.xyz/pitch_distribution"; - const res = await axios.get(`${API_URL}/${token.address}/${token.id}`); const counts = new Array(25).fill(0); - res.data.pitch_distribution.forEach((l: PitchLocation) => { - counts[l.pitch_vertical * 5 + l.pitch_horizontal] = - counts[l.pitch_vertical * 5 + l.pitch_horizontal] + l.count; - }); - const total = counts.reduce((acc, value) => acc + value); - const fast = res.data.pitch_distribution.reduce( - (acc: number, value: { pitch_speed: 0 | 1; count: number }) => - acc + (value.pitch_speed === 0 ? value.count : 0), - 0, - ); - const rates = counts.map((value) => value / total); - return { rates, counts, fast }; + try { + const res = await axios.get(`${API_URL}/${token.address}/${token.id}`); + res.data.pitch_distribution.forEach((l: PitchLocation) => { + counts[l.pitch_vertical * 5 + l.pitch_horizontal] = + counts[l.pitch_vertical * 5 + l.pitch_horizontal] + l.count; + }); + const total = counts.reduce((acc, value) => acc + value); + const fast = res.data.pitch_distribution.reduce( + (acc: number, value: { pitch_speed: 0 | 1; count: number }) => + acc + (value.pitch_speed === 0 ? value.count : 0), + 0, + ); + const rates = counts.map((value) => value / total); + return { rates, counts, fast }; + } catch (e) { + console.log({ token, e }); + return { counts, rates: counts, fast: 0 }; + } }, { enabled: !!token && isPitcher, + retryDelay: (attemptIndex) => (attemptIndex < 1 ? 5000 : 10000), + retry: (failureCount) => { + return failureCount < 3; + }, + refetchInterval: 5000, }, ); @@ -74,20 +104,30 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = return; } const API_URL = "https://api.fullcount.xyz/swing_distribution"; - const res = await axios.get(`${API_URL}/${token.address}/${token.id}`); const counts = new Array(25).fill(0); - let takes = 0; - res.data.swing_distribution.forEach((l: SwingLocation) => - l.swing_type === 2 - ? (takes += l.count) - : (counts[l.swing_vertical * 5 + l.swing_horizontal] = l.count), - ); - const total = counts.reduce((acc, value) => acc + value); - const rates = counts.map((value) => value / total); - return { rates, counts, takes }; + try { + const res = await axios.get(`${API_URL}/${token.address}/${token.id}`); + let takes = 0; + res.data.swing_distribution.forEach((l: SwingLocation) => + l.swing_type === 2 + ? (takes += l.count) + : (counts[l.swing_vertical * 5 + l.swing_horizontal] = l.count), + ); + const total = counts.reduce((acc, value) => acc + value); + const rates = counts.map((value) => value / total); + return { rates, counts, takes }; + } catch (e) { + console.log({ token, e }); + return { counts, rates: counts, takes: 0 }; + } }, { enabled: !!token && !isPitcher, + retryDelay: (attemptIndex) => (attemptIndex < 1 ? 5000 : 10000), + retry: (failureCount) => { + return failureCount < 3; + }, + refetchInterval: 5000, }, ); return ( @@ -102,24 +142,22 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) =
{token.name}
{token.id}
- {((isPitcher && pitcherStats.data) || (!isPitcher && batterStats.data)) && ( - - )} +
- {isPitcher && pitchDistributions.data && ( + {isPitcher && ( )} - {!isPitcher && swingDistributions.data && ( + {!isPitcher && ( )} diff --git a/web/src/components/playing/DetailedStat.tsx b/web/src/components/playing/DetailedStat.tsx index c24e5057..44167daa 100644 --- a/web/src/components/playing/DetailedStat.tsx +++ b/web/src/components/playing/DetailedStat.tsx @@ -1,6 +1,6 @@ import styles from "./DetailedStat.module.css"; import { PlayerStats } from "../../types"; -import { useEffect } from "react"; +import AnimatedMessage from "../AnimatedMessage"; const formatDecimal = (value: number) => { if (!value) { return ".000"; @@ -67,12 +67,20 @@ const DataRow = ({ label, data }: { label: string; data: string }) => { ); }; -const DetailedStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { +const DetailedStat = ({ + stats, + isPitcher, +}: { + stats: PlayerStats | undefined; + isPitcher: boolean; +}) => { return (
-
season statistics
+
+ {stats !== undefined ? "season statistics" : } +
- {isPitcher && stats.points_data.pitching_data && ( + {isPitcher && stats?.points_data.pitching_data && ( <> @@ -93,7 +101,7 @@ const DetailedStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boo /> )} - {!isPitcher && stats.points_data.batting_data && ( + {!isPitcher && stats?.points_data.batting_data && ( <> @@ -114,6 +122,30 @@ const DetailedStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boo )} + {isPitcher && !stats?.points_data.pitching_data && ( + <> + + + + + + + + + + )} + {!isPitcher && !stats?.points_data.batting_data && ( + <> + + + + + + + + + + )}
); diff --git a/web/src/components/playing/HeatMap.tsx b/web/src/components/playing/HeatMap.tsx index 4b4b1346..6d2c763f 100644 --- a/web/src/components/playing/HeatMap.tsx +++ b/web/src/components/playing/HeatMap.tsx @@ -3,6 +3,7 @@ import { Box, Flex, Grid, Text, Image } from "@chakra-ui/react"; import { valueToColor } from "../../utils/colors"; import styles from "./HeatMap.module.css"; import { FULLCOUNT_ASSETS } from "../../constants"; +import AnimatedMessage from "../AnimatedMessage"; const leftBorder = [6, 11, 16]; const topBorder = [6, 7, 8]; @@ -19,10 +20,10 @@ const HeatMap = ({ isPitcher, showStrikeZone = false, }: { - rates: number[]; - counts: number[]; - takes?: number; - fast?: number; + rates: number[] | undefined; + counts: number[] | undefined; + takes?: number | undefined; + fast?: number | undefined; showStrikeZone?: boolean; isPitcher: boolean; }) => { @@ -44,10 +45,10 @@ const HeatMap = ({ alignItems="center" justifyContent="center" cursor={"pointer"} - bg={valueToColor(rates[index], rates)} + bg={rates ? valueToColor(rates[index], rates) : valueToColor(0, [0])} onClick={() => setShowMode(showMode === 2 ? 0 : showMode + 1)} > - {showMode !== 0 && ( + {showMode !== 0 && rates && counts && ( {showMode === 2 ? (rates[index] * 100).toFixed(2) : counts[index]} @@ -58,11 +59,18 @@ const HeatMap = ({ return ( -
- {counts.reduce((acc, c) => acc + c)} - {isPitcher ? " pitches" : " swings"} - {isPitcher && fast ? ` (${fast} fast)` : !isPitcher && takes ? ` + ${takes} takes` : ""} -
+ {counts && ( +
+ {counts.reduce((acc, c) => acc + c)} + {isPitcher ? " pitches" : " swings"} + {isPitcher && fast ? ` (${fast} fast)` : !isPitcher && takes ? ` + ${takes} takes` : ""} +
+ )} + {!counts && ( +
+ +
+ )} {Array.from({ length: 25 }).map((_, i) => generateCell(i))} diff --git a/web/src/components/playing/MainStat.module.css b/web/src/components/playing/MainStat.module.css index c37fd8f6..39dfc63d 100644 --- a/web/src/components/playing/MainStat.module.css +++ b/web/src/components/playing/MainStat.module.css @@ -1,4 +1,4 @@ -.container { +.container, .emptyStatContainer { display: flex; padding: 10px 5px; justify-content: center; @@ -8,6 +8,11 @@ align-items: baseline; } +.emptyStatContainer { + justify-content: space-between; + padding: 10px 15px; +} + .divider { width: 0.5px; height: 12px; diff --git a/web/src/components/playing/MainStat.tsx b/web/src/components/playing/MainStat.tsx index 016c4b92..3566d1ff 100644 --- a/web/src/components/playing/MainStat.tsx +++ b/web/src/components/playing/MainStat.tsx @@ -1,7 +1,6 @@ import styles from "./MainStat.module.css"; import { PlayerStats } from "../../types"; import { Box, Flex, Text } from "@chakra-ui/react"; -import { useEffect } from "react"; const formatDecimal = (value: number) => { if (!value) { @@ -28,7 +27,7 @@ const pitcherRecord = (stats: PlayerStats): string => { const MainStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { return ( <> - {isPitcher && stats.points_data?.pitching_data && ( + {isPitcher && stats && stats.points_data?.pitching_data && ( {pitcherRecord(stats)} W-L @@ -45,7 +44,7 @@ const MainStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean WHIP )} - {!isPitcher && stats.points_data?.batting_data && ( + {!isPitcher && stats && stats.points_data?.batting_data && ( {formatDecimal(stats.points_data.batting_data.batting_average)} @@ -64,6 +63,36 @@ const MainStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean OPS )} + {isPitcher && (!stats || !stats.points_data.pitching_data) && ( + + - + W-L + + - + ERA + + - + SO + + - + WHIP + + )} + {!isPitcher && (!stats || !stats.points_data.batting_data) && ( + + - + AVG + + - + HR + + - + RBI + + - + OPS + + )} ); }; From 760154da5209bc56c80bba4f0ee8abe047c2a2ce Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Wed, 10 Apr 2024 17:03:11 +0300 Subject: [PATCH 11/12] storing onboarding choices in the context --- web/src/contexts/GameContext.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/contexts/GameContext.tsx b/web/src/contexts/GameContext.tsx index 73c75ec2..f25ae5fe 100644 --- a/web/src/contexts/GameContext.tsx +++ b/web/src/contexts/GameContext.tsx @@ -31,6 +31,8 @@ interface GameContextProps { selectedMode: number; selectedTokenIdx: number; joinedNotification: boolean; + onboardingName: string; + onboardingImageIdx: number; } interface GameContextType extends GameContextProps { @@ -74,6 +76,8 @@ export const GameContextProvider: FC = ({ children }) => { selectedMode: 1, selectedTokenIdx: 0, joinedNotification: false, + onboardingName: "", + onboardingImageIdx: 0, }); useEffect(() => { From fec1832e8ac991ea41feafc58903fa2d01afa925 Mon Sep 17 00:00:00 2001 From: Anton Mushnin Date: Wed, 10 Apr 2024 17:03:23 +0300 Subject: [PATCH 12/12] exit prompt --- web/src/components/atbat/AtBatView2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/atbat/AtBatView2.tsx b/web/src/components/atbat/AtBatView2.tsx index 07a28e1a..003fdeb5 100644 --- a/web/src/components/atbat/AtBatView2.tsx +++ b/web/src/components/atbat/AtBatView2.tsx @@ -100,7 +100,7 @@ const AtBatView2: React.FC = () => { /> {atBat && showPitchOutcome && atBat.outcome !== 0 && atBat.pitches.length > 0 && (
router.push("/")}> - Go to home page + play more
)}