chg: dev: massive refactor on front-end - and remove unnecessary deps #4
This commit is contained in:
28
assets/js/mine-seeker/components/GameBoard.jsx
Normal file
28
assets/js/mine-seeker/components/GameBoard.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useGame } from '../contexts/GameContext';
|
||||
import useServerComm from '../hooks/useServerComm';
|
||||
import GridControl from './grid/GridControl';
|
||||
|
||||
export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
|
||||
const { gridReady } = useGame();
|
||||
const { onClick, resign } = useServerComm(gameAssoc, gameInherited, isEnvDev);
|
||||
|
||||
if (!gridReady) {
|
||||
return (
|
||||
<div className="game-overlay">
|
||||
<div className="game-overlay-window"><h1>Loading…</h1></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <GridControl onClick={onClick} resign={resign} />;
|
||||
};
|
||||
69
assets/js/mine-seeker/components/grid/GridControl.jsx
Normal file
69
assets/js/mine-seeker/components/grid/GridControl.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { useGame } from '../../contexts/GameContext';
|
||||
import GridField from './GridField';
|
||||
import UserControl from '../user/UserControl';
|
||||
import { BOMB_SYMBOLS, bombRadius } from '../../constants';
|
||||
|
||||
const GridControl = ({ onClick, resign }) => {
|
||||
const {
|
||||
overlay, overlayTitle, overlaySubTitle,
|
||||
webPlayer, activePlayer, mines, foundMines, bombSelected,
|
||||
red, blue, cells, setCells, onBombToggle,
|
||||
} = useGame();
|
||||
|
||||
const handleHover = (row, col) => {
|
||||
if (!bombSelected) return;
|
||||
const activeColor = activePlayer ? 'blue' : 'red';
|
||||
if (activeColor !== webPlayer) return;
|
||||
|
||||
setCells(prev => {
|
||||
const next = prev.map(r => r.map(c =>
|
||||
null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c,
|
||||
));
|
||||
bombRadius(row, col, prev.length, prev[0]?.length ?? 0).forEach(([r, c], i) => {
|
||||
if (!next[r]?.[c]) return;
|
||||
|
||||
next[r] = [...next[r]];
|
||||
next[r][c] = { ...next[r][c], bombTargetArea: BOMB_SYMBOLS[i] };
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="game-wrapper">
|
||||
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
|
||||
<div className="game-overlay-window">
|
||||
<h1>{overlayTitle}</h1>
|
||||
<h2>{overlaySubTitle}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<UserControl
|
||||
webPlayer={webPlayer}
|
||||
activePlayer={activePlayer}
|
||||
mines={mines}
|
||||
foundMines={foundMines}
|
||||
red={red}
|
||||
blue={blue}
|
||||
onBombToggle={onBombToggle}
|
||||
resign={resign}
|
||||
/>
|
||||
<div className="grid-container">
|
||||
<div className="grid">
|
||||
{cells.flatMap((row, r) =>
|
||||
row.map((cell, c) => (
|
||||
<GridField
|
||||
key={`${r}_${c}`}
|
||||
cell={cell}
|
||||
onClick={() => onClick([r, c])}
|
||||
onMouseEnter={() => handleHover(r, c)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GridControl;
|
||||
45
assets/js/mine-seeker/components/grid/GridField.jsx
Normal file
45
assets/js/mine-seeker/components/grid/GridField.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { memo } from 'react';
|
||||
import { IMG } from '../../constants';
|
||||
|
||||
const bombSrc = area => {
|
||||
if (null === area) return null;
|
||||
const vert = ['left', 'center', 'right'][area[0]] ?? null;
|
||||
const hor = ['top', 'middle', 'bottom'][area[1]] ?? null;
|
||||
if (null === vert || null === hor) return IMG + 'bg-bomb-empty-outbg.png';
|
||||
return `${IMG}bg-bomb-${hor}-${vert}-outbg.png`;
|
||||
};
|
||||
|
||||
const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
|
||||
const { currentImage, currentObj, active, lastClickedRed, lastClickedBlue, bombTargetArea } = cell;
|
||||
|
||||
const fieldClass = 'field'
|
||||
+ (active ? ' active' : '')
|
||||
+ (active && 'm' === currentObj ? ' mine' : '')
|
||||
+ ' color-' + currentObj;
|
||||
|
||||
const inner = isNaN(currentImage)
|
||||
? (
|
||||
<div className="flag-mine"><img src={currentImage} alt="" />
|
||||
<div className="flag-mine-base" />
|
||||
</div>
|
||||
)
|
||||
: currentImage ? <div className="flag-number">{currentImage}</div> : null;
|
||||
|
||||
const bSrc = bombSrc(bombTargetArea);
|
||||
const showLast = lastClickedRed || lastClickedBlue;
|
||||
const lastClass = 'field-' + (lastClickedRed ? 'red' : 'blue') + '-last last-clicked';
|
||||
const lastSrc = lastClickedRed ? IMG + 'bg-last-red-outbg.png' : IMG + 'bg-last-blue-outbg.png';
|
||||
|
||||
return (
|
||||
<div className="field-wrapper" onClick={onClick} onMouseEnter={onMouseEnter}>
|
||||
<img className="field-target" src={IMG + 'bg-target-outbg.png'} alt="" />
|
||||
{bSrc && <img className="field-bomb-target" src={bSrc} alt="" />}
|
||||
{showLast && <img className={lastClass} src={lastSrc} alt="" />}
|
||||
<div className={fieldClass}>
|
||||
<div className="field-corner">{inner}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default GridField;
|
||||
39
assets/js/mine-seeker/components/user/User.jsx
Normal file
39
assets/js/mine-seeker/components/user/User.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
const SRC = '/images/';
|
||||
|
||||
const User = memo(function User({
|
||||
color, webPlayer,
|
||||
name, desc, active, mines, haveBomb, enabledBomb,
|
||||
onClickBombSelector,
|
||||
}) {
|
||||
const buzzClass = 'bomb-container'
|
||||
+ (active && color === webPlayer && haveBomb && enabledBomb ? ' buzz' : '');
|
||||
|
||||
const bombImg = haveBomb
|
||||
? SRC + (enabledBomb && active ? 'bg-bomb-outbg.png' : 'bg-bomb-disabled-outbg.png')
|
||||
: SRC + 'bg-bomb-exploded-outbg.png';
|
||||
|
||||
return (
|
||||
<div className={`user-container user-${color}`}>
|
||||
<div className="user-header">
|
||||
<div className="user-color">{color}</div>
|
||||
{active && <img src={`${SRC}bg-cursor-${color}-outbg.png`} alt="" className="user-cursor" />}
|
||||
<img src={`${SRC}bg-figure-${color}-outbg.png`} alt="" />
|
||||
</div>
|
||||
<div className="user-name"> {name} </div>
|
||||
<div className="user-caret"><i className="fa fa-caret-down" /></div>
|
||||
<div className="user-desc"> {desc} </div>
|
||||
<div className="user-control">
|
||||
<img src={`${SRC}bg-flag-${color}-outbg.png`} alt="" />
|
||||
<div className="user-control-mines">{mines}</div>
|
||||
<div className={buzzClass} onClick={onClickBombSelector}>
|
||||
<div className="bomb"><img src={bombImg} alt="" /></div>
|
||||
</div>
|
||||
<div className="clear" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default User;
|
||||
43
assets/js/mine-seeker/components/user/UserControl.jsx
Normal file
43
assets/js/mine-seeker/components/user/UserControl.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import User from './User';
|
||||
|
||||
const UserControl = ({ webPlayer, activePlayer, mines, foundMines, red, blue, onBombToggle, resign }) => {
|
||||
const activeColor = activePlayer ? 'blue' : 'red';
|
||||
const resignClass = 'resign' + (activeColor !== webPlayer ? ' disabled' : '');
|
||||
const minesClass = 'active-mines' + (foundMines ? ' found-mine' : '');
|
||||
|
||||
const handleBombClick = (color, player) => {
|
||||
const p = 'red' === color ? red : blue;
|
||||
if (p.haveBomb && p.enabledBomb && activePlayer === player) {
|
||||
onBombToggle();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="users">
|
||||
<User
|
||||
color="blue" webPlayer={webPlayer} {...blue}
|
||||
onClickBombSelector={() => handleBombClick('blue', 1)}
|
||||
/>
|
||||
<div className="active-mines-container">
|
||||
<i className="fa fa-star" />
|
||||
<div className={minesClass}>
|
||||
<div className="active-mines-nbr">{mines}</div>
|
||||
<div className="active-mines-shine" />
|
||||
</div>
|
||||
<i className="fa fa-star" />
|
||||
</div>
|
||||
<div className="clear" />
|
||||
<User
|
||||
color="red" webPlayer={webPlayer} {...red}
|
||||
onClickBombSelector={() => handleBombClick('red', 0)}
|
||||
/>
|
||||
<button className={resignClass} onClick={resign}>
|
||||
<div className="resign-shine" />
|
||||
Resign
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserControl;
|
||||
Reference in New Issue
Block a user