add "dead" button

This commit is contained in:
Viktor Rådberg
2023-08-30 12:38:04 +02:00
parent 8219d36e01
commit 9abadc1f38
10 changed files with 158 additions and 89 deletions

View File

@@ -17,6 +17,7 @@
"ga-4-react": "^0.1.281",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-screen-wake-lock": "^3.0.2",
"styled-components": "^6.0.7"
},
"devDependencies": {

View File

@@ -8,6 +8,7 @@ import CommanderDamageBar from '../Counters/CommanderDamageBar';
import ExtraCountersBar from '../Counters/ExtraCountersBar';
import { OutlinedText } from '../Misc/OutlinedText';
import PlayerMenu from '../PlayerMenu/PlayerMenu';
import { Skull } from '../../Icons/generated';
const LifeCounterContentWrapper = styled.div<{
backgroundColor: string;
}>`
@@ -78,8 +79,9 @@ const LifeCountainer = styled.div<{
}}
`;
const StartingPlayer = styled.div<{
const PlayerNoticeWrapper = styled.div<{
rotation: Rotation;
backgroundColor: string;
}>`
z-index: 1;
display: flex;
@@ -88,7 +90,7 @@ const StartingPlayer = styled.div<{
height: 100%;
justify-content: center;
align-items: center;
background: ${theme.palette.primary.main};
background: ${(props) => props.backgroundColor};
pointer-events: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
@@ -109,7 +111,7 @@ const StartingPlayer = styled.div<{
}}
`;
const StartingPlayerText = styled.div<{ rotation: Rotation }>`
const DynamicText = styled.div<{ rotation: Rotation }>`
font-size: 8vmin;
${(props) => {
if (
@@ -175,6 +177,36 @@ export const RecentDifference = styled.span`
animation: ${fadeOut} 3s 1s ease-out forwards;
`;
export const LoseGameButton = styled.button<{ rotation: Rotation }>`
position: absolute;
flex-grow: 1;
border: none;
outline: none;
cursor: pointer;
top: 12vmin;
right: 6vmax;
background-color: #43434380;
border-radius: 15px;
${(props) => {
if (props.rotation === Rotation.SideFlipped) {
return css`
right: auto;
top: 6vmax;
left: 12vmin;
rotate: ${props.rotation}deg;
`;
} else if (props.rotation === Rotation.Side) {
return css`
right: auto;
top: 6vmax;
left: 12vmin;
rotate: ${props.rotation - 180}deg;
`;
}
}}
`;
interface LifeCounterProps {
backgroundColor: string;
player: Player;
@@ -183,6 +215,18 @@ interface LifeCounterProps {
resetCurrentGame: () => void;
}
const hasCommanderDamageReached21 = (player: Player) => {
const commanderDamageTotal = player.commanderDamage.reduce(
(totalDamage, commander) => totalDamage + commander.damageTotal,
0
);
const partnerDamageTotal = player.commanderDamage.reduce(
(totalDamage, commander) => totalDamage + commander.partnerDamageTotal,
0
);
return commanderDamageTotal >= 21 || partnerDamageTotal >= 21;
};
const LifeCounter = ({
backgroundColor,
player,
@@ -192,12 +236,21 @@ const LifeCounter = ({
}: LifeCounterProps) => {
const handleLifeChange = (updatedLifeTotal: number) => {
const difference = updatedLifeTotal - player.lifeTotal;
const updatedPlayer = { ...player, lifeTotal: updatedLifeTotal };
const updatedPlayer = {
...player,
lifeTotal: updatedLifeTotal,
hasLost: false,
};
setRecentDifference(recentDifference + difference);
onPlayerChange(updatedPlayer);
setKey(Date.now());
};
const setGameLost = () => {
const updatedPlayer = { ...player, hasLost: true };
onPlayerChange(updatedPlayer);
};
const [showPlayerMenu, setShowPlayerMenu] = useState(false);
const [recentDifference, setRecentDifference] = useState(0);
const [showStartingPlayer, setShowStartingPlayer] = useState(
@@ -249,11 +302,21 @@ const LifeCounter = ({
<LifeCounterContentWrapper backgroundColor={backgroundColor}>
<LifeCounterWrapper rotation={player.settings.rotation}>
{player.isStartingPlayer && !showStartingPlayer && (
<StartingPlayer rotation={player.settings.rotation}>
<StartingPlayerText rotation={player.settings.rotation}>
<PlayerNoticeWrapper
rotation={player.settings.rotation}
backgroundColor={theme.palette.primary.main}
>
<DynamicText rotation={player.settings.rotation}>
You start!
</StartingPlayerText>
</StartingPlayer>
</DynamicText>
</PlayerNoticeWrapper>
)}
{player.hasLost && (
<PlayerNoticeWrapper
rotation={player.settings.rotation}
backgroundColor={'#00000070'}
/>
)}
<CommanderDamageBar
opponents={opponents}
@@ -267,6 +330,14 @@ const LifeCounter = ({
}}
rotation={player.settings.rotation}
/>
{(player.lifeTotal < 1 || hasCommanderDamageReached21(player)) && (
<LoseGameButton
rotation={player.settings.rotation}
onClick={setGameLost}
>
<Skull size="5vmin" color="black" opacity={0.5} />
</LoseGameButton>
)}
<LifeCountainer rotation={player.settings.rotation}>
<LifeCounterButton
lifeTotal={player.lifeTotal}
@@ -296,6 +367,7 @@ const LifeCounter = ({
</LifeCountainer>
<ExtraCountersBar player={player} onPlayerChange={onPlayerChange} />
</LifeCounterWrapper>
{showPlayerMenu && (
<PlayerMenu
player={player}

View File

@@ -1,8 +1,8 @@
import { Checkbox } from '@mui/material';
import styled, { css } from 'styled-components';
import { useWakeLock } from '../../Data/useWakeLock';
import { Energy, Experience, PartnerTax, Poison } from '../../Icons/generated';
import { Player, Rotation } from '../../Types/Player';
import { useWakeLock } from 'react-screen-wake-lock';
type SettingsProps = {
player: Player;

View File

@@ -236,6 +236,7 @@ export const createInitialPlayers = ({
isStartingPlayer: isStartingPlayer,
extraCounters: [],
commanderDamage,
hasLost: false,
};
players.push(player);

View File

@@ -1,80 +0,0 @@
// https://github.com/jorisre/react-screen-wake-lock
import * as React from 'react';
const warn = (content: string) =>
console.warn('[react-screen-wake-lock]: ' + content);
export interface WakeLockOptions {
onError?: (error: Error) => void;
onRequest?: () => void;
onRelease?: EventListener;
}
export const useWakeLock = ({
onError,
onRequest,
onRelease,
}: WakeLockOptions | undefined = {}) => {
const [released, setReleased] = React.useState<boolean | undefined>();
const wakeLock = React.useRef<WakeLockSentinel | null>(null);
// https://caniuse.com/mdn-api_wakelock
const isSupported = typeof window !== 'undefined' && 'wakeLock' in navigator;
const request = React.useCallback(
async (type: WakeLockType = 'screen') => {
const isWakeLockAlreadyDefined = wakeLock.current != null;
if (!isSupported) {
return warn(
"Calling the `request` function has no effect, Wake Lock Screen API isn't supported"
);
}
if (isWakeLockAlreadyDefined) {
return warn(
'Calling `request` multiple times without `release` has no effect'
);
}
try {
wakeLock.current = await navigator.wakeLock.request(type);
wakeLock.current.onrelease = (e: Event) => {
// Default to `true` - `released` API is experimental: https://caniuse.com/mdn-api_wakelocksentinel_released
setReleased((wakeLock.current && wakeLock.current.released) || true);
onRelease && onRelease(e);
wakeLock.current = null;
};
onRequest && onRequest();
setReleased((wakeLock.current && wakeLock.current.released) || false);
} catch (error: unknown) {
onError && onError(error as Error);
}
},
[isSupported, onRequest, onError, onRelease]
);
const release = React.useCallback(async () => {
const isWakeLockUndefined = wakeLock.current == null;
if (!isSupported) {
return warn(
"Calling the `release` function has no effect, Wake Lock Screen API isn't supported"
);
}
if (isWakeLockUndefined) {
return warn('Calling `release` before `request` has no effect.');
}
wakeLock.current && (await wakeLock.current.release());
}, [isSupported]);
return {
isSupported,
request,
released,
release,
type: (wakeLock.current && wakeLock.current.type) || undefined,
};
};

View File

@@ -0,0 +1,33 @@
import PropTypes from 'prop-types';
import { SVGProps } from 'react';
interface SVGRProps {
title?: string;
titleId?: string;
size?: string;
}
const Skull = ({
title,
titleId,
...props
}: SVGProps<SVGSVGElement> & SVGRProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={props.size || 16}
height={props.size || 16}
viewBox="0 0 512 512"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<g fill="currentColor">
<path d="M237.2 37.1c-30.6 2.9-64.8 15-89.4 31.6-12.2 8.2-31.7 23.4-38.3 30-15.2 14.9-24.6 33.8-27.5 55.1-4.5 32.9 10.8 64.5 38.7 79.8 16.3 8.9 22.3 12.7 28.1 18 15 13.5 23.5 31.4 24.9 52.3.8 11.5 3.2 17.9 8.9 23.1 8.6 7.9 4.4 7.5 73.4 7.5 60.6 0 61.6 0 66-2.1 10-4.8 14.7-12.3 16-25.7 1.3-12.5 1.8-15.2 4.2-22.7 3.9-12.1 11.9-24.4 21.2-32.7 5.5-4.9 8.1-6.5 25.3-16 16.4-9.1 26.1-19.1 33.9-34.8 5.6-11.4 7.4-18.5 8.1-30.8 1.5-26.6-9.6-54.2-29.4-72.7-8.8-8.3-27.3-22.7-38.3-29.8-35.8-23.3-82.1-34.3-125.8-30.1zm-46 72c15 3.2 26.2 13.8 28.7 27.3 1.7 9.2-2.6 18.2-14 29.7-14.1 14.1-28.2 21.9-40.9 22.6-6.6.4-7.9.2-11-1.9-7.5-5-10.9-17.2-11-39 0-14.7 1.9-20.6 8.9-27.6 10.1-10 24.7-14.2 39.3-11.1zm145 0c11.6 2.5 22.7 10.8 26.9 20.2 2.2 4.8 2.4 6.4 2.3 20.7-.1 22.2-3.1 32.2-11 37.1-2.9 1.8-4.6 2.1-10.8 1.7-6.2-.3-8.6-1-15.9-4.6-15.2-7.5-30.9-21.8-36.5-33.2-11.3-23.1 15.6-48.1 45-41.9zm-73.5 102.8c4.7 3.4 11.8 13.7 13.3 19.3 2 7.3-2 13.6-11.5 17.9-12.1 5.6-29.5-2.7-29.5-14.1 0-6.8 7.2-19 13.9-23.6 4.8-3.2 8.9-3.1 13.8.5z" />
<path d="M56.8 304.7c-1.4 1-3.6 3.1-4.9 4.6-5.1 6.6-8.2 25.8-4.7 29.3 2.9 2.9 19.8 7.1 67.8 16.9 36.1 7.4 79 17.5 79 18.7 0 .4-.6.8-1.4.8-2.3 0-73.5 23.6-95.6 31.7-20.8 7.6-40.7 16.4-44.8 19.9-1.6 1.3-2.7 4.1-3.8 9.4-2.9 14.3.2 20.8 12.5 25.5 6.3 2.4 7.3 2.5 11.7 1.4 6.2-1.6 18.9-6.3 48.4-18.2 56.7-22.9 114.7-42.6 140.7-47.7l5.8-1.2 24.5 8.5c13.5 4.7 45.2 16 70.5 25.2 78.6 28.6 95.9 34 101.1 31.6 3.2-1.4 5.4-6.7 5.4-12.8 0-9.6-3.3-16.3-10.8-22-4.8-3.6-8.7-4.9-27.7-8.8-7.1-1.5-15.9-3.8-19.5-5.2-3.6-1.3-17.1-6.5-30-11.5-12.9-4.9-30.4-11.4-38.8-14.4-8.4-2.9-15.8-5.7-16.4-6-.6-.4 4.2-2.2 10.8-4.1 11.6-3.3 27-7.7 58.3-16.9 17.6-5.2 48.9-16.6 54.1-19.6 8.8-5.2 14.8-13.3 17.4-23.3 1.3-5.3 1.4-6.2.1-8.2-2-3-5.3-3.7-13.4-2.9-19.7 1.8-89.3 19.4-175.4 44l-29.8 8.6-18.2-5.1c-20.2-5.6-63.5-18.8-91.2-27.9-18.1-5.9-52.3-16.5-62.5-19.4-9.6-2.7-16.1-3-19.2-.9z" />
</g>
</svg>
);
};
Skull.propTypes = {
title: PropTypes.string,
};
export default Skull;

View File

@@ -5,3 +5,4 @@ export { default as Experience } from './Experience';
export { default as LittleGuy } from './LittleGuy';
export { default as PartnerTax } from './PartnerTax';
export { default as Poison } from './Poison';
export { default as Skull } from './Skull';

35
src/Icons/svgs/Skull.svg Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="currentColor" stroke="none">
<path d="M2372 4749 c-306 -29 -648 -150 -894 -316 -122 -82 -317 -234 -383
-300 -152 -149 -246 -338 -275 -551 -45 -329 108 -645 387 -798 163 -89 223
-127 281 -180 150 -135 235 -314 249 -523 8 -115 32 -179 89 -231 86 -79 44
-75 734 -75 606 0 616 0 660 21 100 48 147 123 160 257 13 125 18 152 42 227
39 121 119 244 212 327 55 49 81 65 253 160 164 91 261 191 339 348 56 114 74
185 81 308 15 266 -96 542 -294 727 -88 83 -273 227 -383 298 -358 233 -821
343 -1258 301z m-460 -720 c150 -32 262 -138 287 -273 17 -92 -26 -182 -140
-297 -141 -141 -282 -219 -409 -226 -66 -4 -79 -2 -110 19 -75 50 -109 172
-110 390 0 147 19 206 89 276 101 100 247 142 393 111z m1450 0 c116 -25 227
-108 269 -202 22 -48 24 -64 23 -207 -1 -222 -31 -322 -110 -371 -29 -18 -46
-21 -108 -17 -62 3 -86 10 -159 46 -152 75 -309 218 -365 332 -113 231 156
481 450 419z m-735 -1028 c47 -34 118 -137 133 -193 20 -73 -20 -136 -115
-179 -121 -56 -295 27 -295 141 0 68 72 190 139 236 48 32 89 31 138 -5z"/>
<path d="M568 2073 c-14 -10 -36 -31 -49 -46 -51 -66 -82 -258 -47 -293 29
-29 198 -71 678 -169 361 -74 790 -175 790 -187 0 -4 -6 -8 -14 -8 -23 0 -735
-236 -956 -317 -208 -76 -407 -164 -448 -199 -16 -13 -27 -41 -38 -94 -29
-143 2 -208 125 -255 63 -24 73 -25 117 -14 62 16 189 63 484 182 567 229
1147 426 1407 477 l58 12 245 -85 c135 -47 452 -160 705 -252 786 -286 959
-340 1011 -316 32 14 54 67 54 128 0 96 -33 163 -108 220 -48 36 -87 49 -277
88 -71 15 -159 38 -195 52 -36 13 -171 65 -300 115 -129 49 -304 114 -388 144
-84 29 -158 57 -164 60 -6 4 42 22 108 41 116 33 270 77 583 169 176 52 489
166 541 196 88 52 148 133 174 233 13 53 14 62 1 82 -20 30 -53 37 -134 29
-197 -18 -893 -194 -1754 -440 l-298 -86 -182 51 c-202 56 -635 188 -912 279
-181 59 -523 165 -625 194 -96 27 -161 30 -192 9z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -7,6 +7,7 @@ export type Player = {
commanderDamage: CommanderDamage[];
extraCounters: ExtraCounter[];
isStartingPlayer: boolean;
hasLost: boolean;
};
export type PlayerSettings = {

View File

@@ -6475,6 +6475,11 @@ react-is@^18.2.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-screen-wake-lock@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-screen-wake-lock/-/react-screen-wake-lock-3.0.2.tgz#ce185ebfdb74a82c89d532726738f60466f00438"
integrity sha512-f88vcfMG1AWYRSIWQ5Qx5YVboH6TSL0F4ZlFLERZp6aKiZRGVRAAJ9wedJdO5jqTMcCDZ4OXJ8PjcSkDmvGSBg==
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"