This commit is contained in:
Viktor Rådberg
2023-12-24 15:15:32 +01:00
parent 47251b6f7b
commit ea2114e048
14 changed files with 7482 additions and 5 deletions

View File

@@ -27,17 +27,22 @@
"devDependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@savvywombat/tailwindcss-grid-areas": "^3.1.0",
"@svgr/cli": "^8.1.0",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.16",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"firebase-tools": "^12.5.2",
"install": "^0.13.0",
"postcss": "^8.4.32",
"prettier": "2.8.8",
"tailwindcss": "^3.4.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -2,6 +2,7 @@ import styled from 'styled-components';
import Play from './Views/Play';
import StartMenu from './Views/StartMenu/StartMenu';
import { useGlobalSettings } from '../Hooks/useGlobalSettings';
import Play2 from '../Components2/Views/Play2';
const StartWrapper = styled.div`
max-width: fit-content;
@@ -35,7 +36,8 @@ export const LifeTrinket = () => {
<>
{showPlay && initialGameSettings ? (
<PlayWrapper>
<Play gridAreas={initialGameSettings?.gridAreas} />
{/* <Play gridAreas={initialGameSettings?.gridAreas} /> */}
<Play2 />
<EmergencyResetButton onClick={goToStart}>
<p>If you can see this, something is wrong.</p>
<p>Press screen to go to start.</p>

View File

@@ -119,6 +119,8 @@ const Start = () => {
startingLifeTotal: 40,
useCommanderDamage: true,
gridAreas: GridTemplateAreas.FourPlayers,
orientation: 'portrait',
gameFormat: 'commander',
}
);
@@ -181,6 +183,7 @@ const Start = () => {
setPlayerOptions({
...playerOptions,
gridAreas: defaultLayout,
orientation: 'landscape',
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playerOptions.numberOfPlayers]);
@@ -228,6 +231,7 @@ const Start = () => {
setPlayerOptions({
...playerOptions,
numberOfPlayers: value as number,
orientation: 'landscape',
});
}}
/>
@@ -246,6 +250,7 @@ const Start = () => {
setPlayerOptions({
...playerOptions,
startingLifeTotal: value as number,
orientation: 'landscape',
})
}
/>
@@ -267,6 +272,7 @@ const Start = () => {
useCommanderDamage: value,
numberOfPlayers: 4,
startingLifeTotal: 40,
orientation: 'landscape',
});
return;
}
@@ -275,6 +281,7 @@ const Start = () => {
useCommanderDamage: value,
numberOfPlayers: 2,
startingLifeTotal: 20,
orientation: 'landscape',
});
}}
/>
@@ -295,7 +302,12 @@ const Start = () => {
numberOfPlayers={playerOptions.numberOfPlayers}
gridAreas={playerOptions.gridAreas}
onChange={(gridAreas) =>
setPlayerOptions({ ...playerOptions, gridAreas })
setPlayerOptions({
...playerOptions,
gridAreas,
//TODO fix the layout selection
orientation: 'portrait',
})
}
/>
</FormControl>

View File

@@ -0,0 +1,59 @@
import { Player, Rotation } from '../../Types/Player';
import styled from 'styled-components';
import { css } from 'styled-components';
import { CommanderDamage } from '../Buttons/CommanderDamage';
const CommanderDamageGrid = styled.div<{ $rotation: number }>`
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
${(props) => {
if (
props.$rotation === Rotation.SideFlipped ||
props.$rotation === Rotation.Side
) {
return css`
flex-direction: column;
height: 100%;
width: auto;
`;
}
}}
`;
type CommanderDamageBarProps = {
opponents: Player[];
player: Player;
handleLifeChange: (updatedLifeTotal: number) => void;
};
const CommanderDamageBar = ({
opponents,
player,
handleLifeChange,
}: CommanderDamageBarProps) => {
return (
<CommanderDamageGrid
$rotation={player.settings.rotation}
key={player.index}
>
{opponents.map((opponent) => {
if (!opponent.settings.useCommanderDamage) {
return null;
}
return (
<CommanderDamage
player={player}
opponent={opponent}
key={opponent.index}
handleLifeChange={handleLifeChange}
/>
);
})}
</CommanderDamageGrid>
);
};
export default CommanderDamageBar;

View File

@@ -0,0 +1,196 @@
import { CounterType, Player } from '../../Types/Player';
import ExtraCounter from '../Buttons/ExtraCounter';
import styled from 'styled-components';
import { css } from 'styled-components';
import { Rotation } from '../../Types/Player';
import {
CommanderTax,
Energy,
Experience,
PartnerTax,
Poison,
} from '../../Icons/generated';
import { usePlayers } from '../../Hooks/usePlayers';
const Container = styled.div<{ $rotation: Rotation }>`
width: 100%;
height: 20vmin;
display: flex;
${(props) => {
if (
props.$rotation === Rotation.SideFlipped ||
props.$rotation === Rotation.Side
) {
return css`
height: 100%;
width: 9.3vmax;
`;
}
}}
`;
export const ExtraCountersGrid = styled.div<{ $rotation: Rotation }>`
display: flex;
position: absolute;
width: 100%;
flex-direction: row;
flex-grow: 1;
bottom: 0;
pointer-events: none;
${(props) => {
if (
props.$rotation === Rotation.SideFlipped ||
props.$rotation === Rotation.Side
) {
return css`
flex-direction: column-reverse;
height: 100%;
width: auto;
bottom: auto;
right: -6px;
`;
}
}}
`;
type ExtraCountersBarProps = {
player: Player;
};
const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
const { updatePlayer } = usePlayers();
const handleCounterChange = (
updatedCounterTotal: number,
type: CounterType
) => {
if (updatedCounterTotal < 0) {
return;
}
const existingCounter = player.extraCounters.find(
(counter) => counter.type === type
);
if (!existingCounter) {
const newCounter = {
type,
value: updatedCounterTotal,
};
const updatedExtraCounters = [...player.extraCounters, newCounter];
const updatedPlayer = { ...player, extraCounters: updatedExtraCounters };
updatePlayer(updatedPlayer);
return;
}
const updatedExtraCounters = player.extraCounters.map((counter) => {
if (counter.type === type) {
return { ...counter, value: updatedCounterTotal };
}
return counter;
});
const updatedPlayer = { ...player, extraCounters: updatedExtraCounters };
updatePlayer(updatedPlayer);
};
const iconSize =
player.settings.rotation === Rotation.SideFlipped ||
player.settings.rotation === Rotation.Side
? '5vmax'
: '10vmin';
const {
useCommanderDamage,
usePartner,
usePoison,
useEnergy,
useExperience,
} = player.settings;
const hasExtraCounters =
useCommanderDamage || usePartner || usePoison || useEnergy || useExperience;
if (!hasExtraCounters) {
return null;
}
return (
<Container $rotation={player.settings.rotation}>
<ExtraCountersGrid $rotation={player.settings.rotation}>
{useCommanderDamage && (
<ExtraCounter
rotation={player.settings.rotation}
Icon={<CommanderTax size={iconSize} opacity="0.5" color="black" />}
type={CounterType.CommanderTax}
counterTotal={
player.extraCounters?.find(
(counter) => counter.type === 'commanderTax'
)?.value
}
setCounterTotal={handleCounterChange}
playerIndex={player.index}
/>
)}
{Boolean(useCommanderDamage && usePartner) && (
<ExtraCounter
rotation={player.settings.rotation}
Icon={<PartnerTax size={iconSize} opacity="0.5" color="black" />}
type={CounterType.PartnerTax}
counterTotal={
player.extraCounters?.find(
(counter) => counter.type === 'partnerTax'
)?.value
}
setCounterTotal={handleCounterChange}
playerIndex={player.index}
/>
)}
{usePoison && (
<ExtraCounter
rotation={player.settings.rotation}
Icon={<Poison size={iconSize} opacity="0.5" color="black" />}
type={CounterType.Poison}
counterTotal={
player.extraCounters?.find((counter) => counter.type === 'poison')
?.value
}
setCounterTotal={handleCounterChange}
playerIndex={player.index}
/>
)}
{useEnergy && (
<ExtraCounter
rotation={player.settings.rotation}
Icon={<Energy size={iconSize} opacity="0.5" color="black" />}
type={CounterType.Energy}
counterTotal={
player.extraCounters?.find((counter) => counter.type === 'energy')
?.value
}
setCounterTotal={handleCounterChange}
playerIndex={player.index}
/>
)}
{useExperience && (
<ExtraCounter
rotation={player.settings.rotation}
Icon={<Experience size={iconSize} opacity="0.5" color="black" />}
type={CounterType.Experience}
counterTotal={
player.extraCounters?.find(
(counter) => counter.type === 'experience'
)?.value
}
setCounterTotal={handleCounterChange}
playerIndex={player.index}
/>
)}
</ExtraCountersGrid>
</Container>
);
};
export default ExtraCountersBar;

View File

@@ -0,0 +1,62 @@
import { Player } from '../../Types/Player';
import LifeCounter from '../../Components/LifeCounter/LifeCounter';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
export const FourPlayers = ({ players }: { players: Player[] }) => {
const { initialGameSettings } = useGlobalSettings();
let gridClasses: string;
switch (initialGameSettings?.orientation) {
case 'portrait':
gridClasses = 'grid-areas-fourPlayerPortrait';
break;
case 'side':
case 'landscape':
default:
gridClasses = 'grid-areas-fourPlayer';
break;
}
return (
<div className="w-full h-full bg-black">
<div className={`grid w-full h-full gap-1 box-border ${gridClasses} `}>
{players.map((player) => {
const gridArea = getGridArea(player);
return (
<div
key={player.index}
className={`flex justify-center items-center align-middle ${gridArea}`}
>
<LifeCounter
player={player}
opponents={players.filter(
(opponent) => opponent.index !== player.index
)}
/>
</div>
);
})}
</div>
</div>
);
};
export const getGridArea = (player: Player) => {
switch (player.index) {
case 0:
return 'grid-in-player0';
case 1:
return 'grid-in-player1';
case 2:
return 'grid-in-player2';
case 3:
return 'grid-in-player3';
case 4:
return 'grid-in-player4';
case 5:
return 'grid-in-player5';
default:
throw new Error('Invalid player index');
}
};

View File

@@ -0,0 +1,46 @@
import { Player } from '../../Types/Player';
import LifeCounter from '../../Components/LifeCounter/LifeCounter';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { getGridArea } from './FourPlayers';
export const TwoPlayers = ({ players }: { players: Player[] }) => {
const { initialGameSettings } = useGlobalSettings();
let gridClasses: string;
switch (initialGameSettings?.orientation) {
case 'portrait':
gridClasses = 'grid-areas-twoPlayersOppositePortrait';
break;
default:
case 'side':
gridClasses = 'grid-areas-twoPlayersSameSideLandscape';
break;
case 'landscape':
gridClasses = 'grid-areas-twoPlayersOppositeLandscape';
break;
}
return (
<div className="w-full h-full bg-black">
<div className={`grid w-full h-full gap-1 box-border ${gridClasses} `}>
{players.map((player) => {
const gridArea = getGridArea(player);
return (
<div
key={player.index}
className={`flex justify-center items-center align-middle ${gridArea}`}
>
<LifeCounter
player={player}
opponents={players.filter(
(opponent) => opponent.index !== player.index
)}
/>
</div>
);
})}
</div>
</div>
);
};

View File

@@ -0,0 +1,43 @@
import styled from 'styled-components';
import { usePlayers } from '../../Hooks/usePlayers';
import { TwoPlayers } from '../Layouts/TwoPlayers';
import { FourPlayers } from '../Layouts/FourPlayers';
const MainWrapper = styled.div`
width: 100vmax;
height: 100vmin;
width: 100dvmax;
height: 100dvmin;
overflow: hidden;
`;
const Play2 = () => {
const { players } = usePlayers();
let Layout: JSX.Element;
switch (players.length) {
case 1:
Layout = <FourPlayers players={players} />;
break;
case 2:
Layout = <TwoPlayers players={players} />;
break;
case 3:
Layout = <FourPlayers players={players} />;
break;
default:
case 4:
Layout = <FourPlayers players={players} />;
break;
case 5:
Layout = <FourPlayers players={players} />;
break;
case 6:
Layout = <FourPlayers players={players} />;
break;
}
return <MainWrapper>{Layout}</MainWrapper>;
};
export default Play2;

View File

@@ -6,8 +6,6 @@ export enum GridTemplateAreas {
TwoPlayersSameSide = '"player0 player1"',
ThreePlayers = '"player0 player0" "player1 player2"',
ThreePlayersSide = '"player0 player0 player0 player2" "player1 player1 player1 player2"',
FourPlayers = '"player0 player1" "player2 player3"',
FourPlayersSide = '"player0 player1 player1 player1 player3" "player0 player2 player2 player2 player3"',
FivePlayers = '"player0 player0 player0 player1 player1 player1" "player2 player2 player3 player3 player4 player4"',
FivePlayersSide = '"player0 player0 player0 player0 player0 player1 player1 player1 player1 player1 player2" "player3 player3 player3 player3 player3 player4 player4 player4 player4 player4 player2"',
SixPlayers = '"player0 player1 player2" "player3 player4 player5"',

View File

@@ -6,6 +6,7 @@ import {
import { useWakeLock } from 'react-screen-wake-lock';
import { useAnalytics } from '../Hooks/useAnalytics';
import { InitialGameSettings, Settings } from '../Types/Settings';
import { GridTemplateAreas } from '../Data/GridTemplateAreas';
export const GlobalSettingsProvider = ({
children,
@@ -22,11 +23,23 @@ export const GlobalSettingsProvider = ({
savedShowPlay ? savedShowPlay === 'true' : false
);
const [initialGameSettings, setInitialGameSettings] =
const [initialGameSettings, setInitialSettings] =
useState<InitialGameSettings | null>(
savedGameSettings ? JSON.parse(savedGameSettings) : null
);
const setInitialGameSettings = (initialGameSettings: InitialGameSettings) => {
const defaultSettings: InitialGameSettings = {
numberOfPlayers: 4,
startingLifeTotal: 40,
useCommanderDamage: true,
gridAreas: GridTemplateAreas.FourPlayers,
orientation: 'portrait',
gameFormat: 'commander',
};
setInitialSettings({ ...defaultSettings, ...initialGameSettings });
};
const [settings, setSettings] = useState<Settings>(
savedSettings
? JSON.parse(savedSettings)

View File

@@ -1,3 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

41
tailwind.config.js Normal file
View File

@@ -0,0 +1,41 @@
import tailwindcssGridAreas from '@savvywombat/tailwindcss-grid-areas';
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
gridTemplateAreas: {
fourPlayerPortrait: [
'player0 player1 player1 player1 player1 player3',
'player0 player2 player2 player2 player2 player3',
],
fourPlayer: ['player0 player1', 'player2 player3'],
onePlayerLandscape: ['player0 player0'],
onePlayerPortrait: ['player0', 'player0'],
twoPlayersOppositeLandscape: ['player0', 'player1'],
twoPlayersOppositePortrait: ['player0 player1', 'player0 player1'],
twoPlayersSameSideLandscape: ['player0 player1'],
threePlayers: ['player0 player0', 'player1 player2'],
threePlayersSide: [
'player0 player0 player0 player2',
'player1 player1 player1 player2',
],
fivePlayers: [
'player0 player0 player0 player1 player1 player1',
'player2 player2 player3 player3 player4 player4',
],
fivePlayersSide: [
'player0 player0 player0 player0 player0 player1 player1 player1 player1 player1 player2',
'player3 player3 player3 player3 player3 player4 player4 player4 player4 player4 player2',
],
sixPlayers: ['player0 player1 player2', 'player3 player4 player5'],
sixPlayersSide: [
'player0 player1 player1 player1 player1 player2 player2 player2 player2 player3',
'player0 player4 player4 player4 player4 player5 player5 player5 player5 player3',
],
},
},
},
plugins: [tailwindcssGridAreas],
};

6990
yarn.lock Normal file

File diff suppressed because it is too large Load Diff