Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"beautiful-react-hooks": "^3.12.2",
"classnames": "^2.3.2",
"cross-env": "^7.0.3",
"cyber-dice": "github:blopa/cyber-dice",
"is-mobile": "^3.1.1",
"jest": "^27.5.1",
"phaser": "^3.55.2",
Expand Down
140 changes: 140 additions & 0 deletions src/components/Battle/Battle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useEffect, useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';

// Selectors
import {
selectBattleItems,
selectBattleOnHover,
selectBattleSetters,
selectBattleOnSelect,
} from '../../zustand/battle/selectBattle';

// Constants
import {
ENTER_KEY,
ARROW_UP_KEY,
ARROW_DOWN_KEY,
ARROW_LEFT_KEY,
ARROW_RIGHT_KEY,
} from '../../constants';

// Utils
import { getTranslationVariables } from '../../utils/utils';

// Store
import { useGameStore } from '../../zustand/store';

// Styles
import styles from './Battle.module.scss';

// Components
import BattleDiceViewer from '../BattleDiceViewer/BattleDiceViewer';

function Battle() {
const battleListRef = useRef();

// TODO for now only works for four items
const battleItems = useGameStore(selectBattleItems);

const [selectedItemIndex, setSelectedItemIndex] = useState(0);
const onSelected = useGameStore(selectBattleOnSelect);
const onHover = useGameStore(selectBattleOnHover);
const { setBattleItemsListDom } = useGameStore(selectBattleSetters);

useEffect(() => {
setBattleItemsListDom(battleListRef.current);
}, [battleListRef, setBattleItemsListDom]);

useEffect(() => {
onHover?.(selectedItemIndex);
}, [onHover, selectedItemIndex]);

useEffect(() => {
const handleKeyPressed = (e) => {
switch (e.code) {
case ENTER_KEY: {
onSelected(battleItems[selectedItemIndex], selectedItemIndex);
break;
}

case ARROW_UP_KEY: {
const increment = selectedItemIndex === 1 ? 1 : -2;
setSelectedItemIndex(
Math.max(0, selectedItemIndex + increment)
);

break;
}

case ARROW_DOWN_KEY: {
const increment = selectedItemIndex === battleItems.length / 2 ? -1 : 2;
setSelectedItemIndex(
Math.min(3, selectedItemIndex + increment)
);

break;
}

case ARROW_LEFT_KEY: {
setSelectedItemIndex(
Math.max(0, selectedItemIndex - 1)
);

break;
}

case ARROW_RIGHT_KEY: {
setSelectedItemIndex(
Math.min(3, selectedItemIndex + 1)
);

break;
}

default: {
break;
}
}
};

window.addEventListener('keydown', handleKeyPressed);

return () => window.removeEventListener('keydown', handleKeyPressed);
}, [battleItems, onSelected, selectedItemIndex]);

return (
<div className={classNames(styles['battle-wrapper'])}>
<BattleDiceViewer />
<ul ref={battleListRef} className={styles['battle-items-wrapper']}>
{battleItems.map((item, index) => {
const [key, variables] = getTranslationVariables(item);

return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<li
key={key}
className={classNames(styles['battle-item'], {
[styles['selected-battle-item']]: index === selectedItemIndex,
[styles['fewer-items']]: battleItems.length < 3,
})}
onMouseEnter={() => {
setSelectedItemIndex(index);
}}
onClick={() => {
onSelected?.(item, index);
}}
>
<FormattedMessage
id={key}
values={variables}
/>
</li>
);
})}
</ul>
</div>
);
}

export default Battle;
44 changes: 44 additions & 0 deletions src/components/Battle/Battle.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.battle-wrapper {
height: 100%;

&.paused {
background-color: rgb(0 0 0 / 70%);
}
}

.battle-items-wrapper {
user-select: none;
user-drag: none;
position: absolute;
font-size: calc(var(--game-zoom) * 10px);
list-style: none;
image-rendering: pixelated;
font-family: "Press Start 2P", serif;
text-transform: uppercase;
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
bottom: 0;
background-color: #83a37d;
border: calc(var(--game-zoom) * 1px) solid #53814b;
outline-offset: calc(var(--game-zoom) * -1px);
justify-content: center;
}

.battle-item {
cursor: pointer;
padding: 3% 0;
margin: 0.22%;
text-align: center;
border: calc(var(--game-zoom) * 1px) solid #000000;
min-width: calc(((var(--game-width) * var(--game-zoom) / 2) - (var(--game-zoom) * 2) - (var(--game-width) * var(--game-zoom) * 0.008)) * 1px);

&.fewer-items {
width: calc(((var(--game-width) * var(--game-zoom)) - (var(--game-zom) * 2)) * 1px);
}

&.selected-battle-item {
background-color: #53814b;
}
}
33 changes: 33 additions & 0 deletions src/components/BattleDice/BattleDice.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useMemo, useState } from 'react';
import { DiceWithAnimation } from 'cyber-dice';

// Store
import { useGameStore } from '../../zustand/store';

// Selectors
import { selectGameZoom } from '../../zustand/game/selectGameData';

function BattleDice() {
const gameZoom = useGameStore(selectGameZoom);

const [shouldAnimateDice, setShouldAnimateDice] = useState(true);
const [randomNumber, setRandomNumber] = useState(Math.floor(Math.random() * 6) + 1);
const diceSize = useMemo(() => 80 * gameZoom, [gameZoom]);

const animationEndHandler = () => {
setShouldAnimateDice(false);
console.log(randomNumber);
};

return (
<DiceWithAnimation
key={diceSize}
size={diceSize}
randomNumber={randomNumber}
isAnimation={shouldAnimateDice}
animationEndHandler={animationEndHandler}
/>
);
}

export default BattleDice;
Empty file.
80 changes: 80 additions & 0 deletions src/components/BattleDiceViewer/BattleDiceViewer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useMemo } from 'react';
import { Dice } from 'cyber-dice';

// Styles
import styles from './BattleDiceViewer.module.scss';

// Store
import { useGameStore } from '../../zustand/store';

// Selectors
import { selectGameHeight, selectGameZoom } from '../../zustand/game/selectGameData';
import { selectHeroEquipedInventoryDice } from '../../zustand/hero/selectHeroData';
import { selectBattleHoveredItem, selectBattleItemsListDOM } from '../../zustand/battle/selectBattle';

function BattleDiceViewer() {
const equipedDice = useGameStore(selectHeroEquipedInventoryDice);
const itemsListDOM = useGameStore(selectBattleItemsListDOM);
const hoveredItem = useGameStore(selectBattleHoveredItem);
const gameHeight = useGameStore(selectGameHeight);
const gameZoom = useGameStore(selectGameZoom);
const availableScreenHeight = useMemo(
// eslint-disable-next-line no-unsafe-optional-chaining
() => gameHeight - (itemsListDOM?.offsetHeight || 0) / gameZoom,
[gameZoom, gameHeight, itemsListDOM?.offsetHeight]
);

const baseDiceSize = useMemo(
() => Math.min((availableScreenHeight / 5), 40), [availableScreenHeight]
);
const diceSize = useMemo(() => baseDiceSize * gameZoom, [gameZoom, baseDiceSize]);
const diceMargin = useMemo(() => (diceSize * 1.25), [diceSize]);

if (equipedDice.length === 0 || hoveredItem === null || hoveredItem === 3) {
return null;
}

const { faces: diceFaces } = equipedDice[hoveredItem];
return (
<div
className={styles['dice-faces-wrapper']}
style={{
// marginTop: `${90 * gameZoom}px`,
}}
>
<ul className={styles['dice-faces-list-wrapper']}>
{diceFaces.map((faceNumber, index) => (
<li
// eslint-disable-next-line react/no-array-index-key
key={index}
className={styles.face}
style={{
...index === 0 && { marginTop: `${diceMargin}px` },
...index === 1 && {
marginTop: `${diceMargin}px`,
marginLeft: `${diceMargin}px`,
},
...index === 2 && {
marginTop: `${diceMargin * 2}px`,
marginLeft: `${diceMargin}px`,
},
...index === 3 && { marginLeft: `${diceMargin}px` },
...index > 3 && {
marginLeft: `${diceMargin * (index - 2)}px`,
marginTop: `${diceMargin}px`,
},
}}
>
<Dice
randomNumber={faceNumber}
size={diceSize}
key={diceSize}
/>
</li>
))}
</ul>
</div>
);
}

export default BattleDiceViewer;
18 changes: 18 additions & 0 deletions src/components/BattleDiceViewer/BattleDiceViewer.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.dice-faces-list-wrapper {
list-style: none;
margin: 0;
padding: 0;
padding-inline-start: 0;
position: absolute;
left: 55%;
transform: translateX(-25%);
width: 100%;
}

.dice-faces-wrapper {
padding: 0;
}

.face {
position: absolute;
}
6 changes: 6 additions & 0 deletions src/components/ReactWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ import useMutationObserver from '../hooks/useMutationObserver';
import DialogBox from './DialogBox/DialogBox';
import GameMenu from './GameMenu/GameMenu';
import GameText from './GameText/GameText';
import Battle from './Battle/Battle';

// Selectors
import { selectGameCanvasElement } from '../zustand/game/selectGameData';
import { selectDialogMessages } from '../zustand/dialog/selectDialog';
import { selectBattleItems } from '../zustand/battle/selectBattle';
import { selectMenuItems } from '../zustand/menu/selectMenu';
import { selectTexts } from '../zustand/text/selectText';

function ReactWrapper() {
const canvas = useGameStore(selectGameCanvasElement);
const dialogMessages = useGameStore(selectDialogMessages);
const menuItems = useGameStore(selectMenuItems);
const battleItems = useGameStore(selectBattleItems);
const gameTexts = useGameStore(selectTexts);
// const s = useGameStore((store) => store);
// console.log(s);
Expand Down Expand Up @@ -76,6 +79,9 @@ function ReactWrapper() {
style={inlineStyles}
// onClick={handleWrapperClicked}
>
{battleItems.length > 0 && (
<Battle />
)}
<DialogBox show={dialogMessages.length > 0} />
{menuItems.length > 0 && (
<GameMenu />
Expand Down
Loading