Skip to content

Commit

Permalink
link to replay and spectate
Browse files Browse the repository at this point in the history
  • Loading branch information
Await-0x committed Mar 4, 2025
1 parent ab43023 commit 28d025e
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 18 deletions.
39 changes: 38 additions & 1 deletion client/src/api/indexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -517,4 +517,41 @@ export async function getTournamentScores(tournament_id) {
} catch (ex) {
console.log(ex)
}
}
}

export async function getTokenMetadata(game_id) {
const document = gql`
{
${NS_SHORT}TokenMetadataModels(where:{token_id:"${game_id}"}) {
edges {
node {
token_id
player_name
settings_id
lifecycle {
start {
Some
}
end {
Some
}
}
}
}
}
}`

const res = await request(GQL_ENDPOINT, document);
const metadata = res?.[`${NS_SHORT}TokenMetadataModels`]?.edges[0]?.node;

if (!metadata) return null;

return {
id: parseInt(metadata.token_id, 16),
playerName: hexToAscii(metadata.player_name),
settingsId: parseInt(metadata.settings_id, 16),
expires_at: parseInt(metadata.lifecycle.end.Some || 0, 16) * 1000,
available_at: parseInt(metadata.lifecycle.start.Some || 0, 16) * 1000
};
}

8 changes: 4 additions & 4 deletions client/src/components/battle/death.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Box, Button, Typography } from "@mui/material";
import { motion, useAnimationControls } from "framer-motion";
import React, { useContext, useEffect, useState } from "react";
import { GameContext } from "../../contexts/gameContext";
import { isBrowser, isMobile } from 'react-device-detect';
import { BattleContext } from "../../contexts/battleContext";
import { motion, useAnimationControls } from "framer-motion";
import { isMobile, isBrowser } from 'react-device-detect'
import { GameContext } from "../../contexts/gameContext";

function DeathDialog(props) {
const game = useContext(GameContext)
const battle = useContext(BattleContext)
const controls = useAnimationControls()

const [text, showText] = useState(false)
const [tweetMsg] = useState(`I fell to the darkness in Dark Shuffle after slaying ${game.values.monstersSlain} beasts, with a final score of ${game.score}. Can you outlast me? Play now at darkshuffle.io 🕷️⚔️ @provablegames @darkshuffle_gg`);
const [tweetMsg] = useState(`I fell to the darkness in Dark Shuffle after slaying ${game.values.monstersSlain} beasts, with a final score of ${game.score}. Want to see how I did it? Watch my replay here: darkshuffle.io/replay/${game.id} 🕷️⚔️ @provablegames @darkshuffle_gg`);

useEffect(() => {
startAnimation()
Expand Down
16 changes: 14 additions & 2 deletions client/src/components/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { LoadingButton } from '@mui/lab';
import { Box, Button, IconButton, Typography } from '@mui/material';
import { useConnect } from "@starknet-react/core";
import React, { useContext, useState } from 'react';
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import logo from '../assets/images/logo.svg';
import { BattleContext } from '../contexts/battleContext';
import { DojoContext } from '../contexts/dojoContext';
import { GameContext } from '../contexts/gameContext';
import { ellipseAddress } from '../helpers/utilities';
import ChooseName from './dialogs/chooseName';
import ConnectWallet from './dialogs/connectWallet';
Expand All @@ -23,6 +25,10 @@ const menuItems = [
]

function Header(props) {
const game = useContext(GameContext)
const battle = useContext(BattleContext)
const navigate = useNavigate()

const { connect, connector, connectors } = useConnect();
let cartridgeConnector = connectors.find(conn => conn.id === "controller")

Expand All @@ -42,11 +48,17 @@ function Header(props) {
setAnchorEl(null);
};

const backToMenu = () => {
navigate('/')
battle.utils.resetBattleState()
game.endGame()
}

return (
<Box sx={styles.header}>

<Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
<Box height={32} sx={{ opacity: 1 }}>
<Box height={32} sx={{ opacity: 1, cursor: 'pointer' }} onClick={backToMenu}>
<img alt='' src={logo} height='32' />
</Box>

Expand Down
19 changes: 13 additions & 6 deletions client/src/components/landing/leaderboard.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CheckIcon from '@mui/icons-material/Check';
import TheatersIcon from '@mui/icons-material/Theaters';
import VisibilityIcon from '@mui/icons-material/Visibility';
import { Box, IconButton, Pagination, Tab, Tabs, Typography } from '@mui/material';
Expand All @@ -9,7 +10,6 @@ import { getActiveLeaderboard, getLeaderboard, getTournamentRegistrations, popul
import { useReplay } from '../../contexts/replayContext';
import { useTournament } from "../../contexts/tournamentContext";
import { formatNumber } from '../../helpers/utilities';
import CheckIcon from '@mui/icons-material/Check';

function Leaderboard() {
const tournamentProvider = useTournament()
Expand Down Expand Up @@ -65,6 +65,7 @@ function Leaderboard() {
}, [page, tab, registrations])

const seasonPool = Math.floor(season.rewardPool / 1e18 * 0.98)
const cashPrizes = [99, 60, 39, 30, 21, 15, 12, 9, 9, 6]

return (
<Box sx={styles.container}>
Expand All @@ -89,7 +90,7 @@ function Leaderboard() {
<Typography>#</Typography>
</Box>

<Box width={isMobile ? '150px' : '250px'}>
<Box width={isMobile ? '135px' : '235px'}>
<Typography>Player</Typography>
</Box>

Expand All @@ -98,7 +99,7 @@ function Leaderboard() {
{tab === 'one' ? 'Score' : 'XP'}
</Typography>
</Box>
<Box width='75px' textAlign={'center'}></Box>
<Box width='90px' textAlign={'center'}></Box>
</Box>

{loading && <Box />}
Expand All @@ -124,21 +125,27 @@ function Leaderboard() {
<Typography>{rank}</Typography>
</Box>

<Box width={isMobile ? '150px' : '250px'}>
<Box width={isMobile ? '135px' : '235px'}>
<Typography>{game.playerName}</Typography>
</Box>

<Box width='80px' textAlign={'center'}>
<Typography>{game.xp}</Typography>
</Box>

<Box width='75px' display={'flex'} gap={0.5} alignItems={'center'}>
<Box width='90px' display={'flex'} gap={0.5} alignItems={'center'}>
{tab === 'one' && rank <= season.distribution?.length && <>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#FFE97F" height={12}><path d="M0 12v2h1v2h6V4h2v12h6v-2h1v-2h-2v2h-3V4h2V0h-2v2H9V0H7v2H5V0H3v4h2v10H2v-2z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#FFE97F" height={10}><path d="M0 12v2h1v2h6V4h2v12h6v-2h1v-2h-2v2h-3V4h2V0h-2v2H9V0H7v2H5V0H3v4h2v10H2v-2z"></path></svg>

<Typography color={'primary'} sx={{ fontSize: '12px' }}>
{formatNumber(seasonPool * season.distribution[rank - 1] / 100)}
</Typography>

{season.leaderboard[i] === game.tokenId && <CheckIcon sx={{ fontSize: '14px' }} color='primary' />}

{!isMobile && cashPrizes[i] && <Typography color={'#f59100'} sx={{ fontSize: '12px' }}>
+${cashPrizes[i]}
</Typography>}
</>}
</Box>
</Box>
Expand Down
3 changes: 2 additions & 1 deletion client/src/contexts/replayContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export const ReplayProvider = ({ children }) => {
fetchEvents(1, txs[1].tx_hash)
setTxHashes(txs.map(tx => tx.tx_hash))
} else {
enqueueSnackbar('Failed to load replay', { variant: 'error', anchorOrigin: { vertical: 'bottom', horizontal: 'right' } })
endReplay()
enqueueSnackbar('Failed to load replay', { variant: 'error', anchorOrigin: { vertical: 'top', horizontal: 'center' } })
}
}

Expand Down
8 changes: 8 additions & 0 deletions client/src/helpers/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ export const routes = [
path: '/',
content: <ArenaPage />
},
{
path: '/replay/:replayGameId',
content: <ArenaPage />
},
{
path: '/spectate/:spectateGameId',
content: <ArenaPage />
},
{
path: '/library',
content: <CollectionPage />
Expand Down
38 changes: 34 additions & 4 deletions client/src/pages/ArenaPage.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
import React, { useContext } from 'react'
import { useSnackbar } from 'notistack'
import React, { useContext, useEffect } from 'react'
import { Scrollbars } from 'react-custom-scrollbars'
import { useParams } from 'react-router-dom'
import { getTokenMetadata } from '../api/indexer'
import StartDraft from '../components/landing/startDraft'
import BattleContainer from '../container/BattleContainer'
import DraftContainer from '../container/DraftContainer'
import StartBattleContainer from '../container/StartBattleContainer'
import { GameContext } from '../contexts/gameContext'
import { useReplay } from '../contexts/replayContext'

function ArenaPage() {
const gameState = useContext(GameContext)
const { gameId, state, replay } = gameState.values
const { state } = gameState.values

const replay = useReplay()
const { replayGameId, spectateGameId } = useParams()
const { enqueueSnackbar } = useSnackbar()

useEffect(() => {
async function fetchGame() {
let game = await getTokenMetadata(replayGameId || spectateGameId)

if (game) {
if (replayGameId) {
replay.startReplay(game)
}

if (spectateGameId) {
replay.spectateGame(game)
}
} else {
enqueueSnackbar('Game not found', { variant: 'error', anchorOrigin: { vertical: 'top', horizontal: 'center' } })
}
}

if (replayGameId || spectateGameId) {
fetchGame()
}
}, [replayGameId, spectateGameId])

return (
<Scrollbars style={{ ...styles.container, border: replay ? '1px solid #f59100' : 'none' }}>
{gameId === null && <StartDraft />}
<Scrollbars style={{ ...styles.container, border: gameState.values.replay ? '1px solid #f59100' : 'none' }}>
{gameState.values.gameId === null && <StartDraft />}

{state === 'Draft' && <DraftContainer />}

Expand Down

0 comments on commit 28d025e

Please sign in to comment.