Commit ebe4288e authored by Smirnov Oleg's avatar Smirnov Oleg

pretty

parent be00b7bf
Pipeline #167 failed with stages
...@@ -141,7 +141,7 @@ export default function Home() { ...@@ -141,7 +141,7 @@ export default function Home() {
)} )}
{currentPlayer && ( {currentPlayer && (
<div className="fixed top-4 left-4 bg-gray-800 rounded p-2"> <div className="fixed top-4 left-4 bg-gray-800 rounded p-2 z-50">
<PlayerInfo <PlayerInfo
nickname={`${currentPlayer.nickname} (${currentPlayer.score})`} nickname={`${currentPlayer.nickname} (${currentPlayer.score})`}
isCurrentTurn={gameState.isYourTurn} isCurrentTurn={gameState.isYourTurn}
...@@ -151,7 +151,7 @@ export default function Home() { ...@@ -151,7 +151,7 @@ export default function Home() {
</div> </div>
)} )}
{opponent && ( {opponent && (
<div className="fixed bottom-4 left-4 bg-gray-800 rounded p-2"> <div className="fixed bottom-4 left-4 bg-gray-800 rounded p-2 z-50">
<PlayerInfo <PlayerInfo
nickname={`${opponent.nickname} (${opponent.score})`} nickname={`${opponent.nickname} (${opponent.score})`}
isCurrentTurn={!gameState.isYourTurn} isCurrentTurn={!gameState.isYourTurn}
...@@ -161,45 +161,42 @@ export default function Home() { ...@@ -161,45 +161,42 @@ export default function Home() {
</div> </div>
)} )}
<div className="pt-20 pb-20"> <div className="h-screen">
{gameState.status === 'waiting' ? ( {gameState.status === 'waiting' ? (
<div className="text-center text-xl"> <div className="text-center text-xl">
Waiting for opponent to join... Waiting for opponent to join...
</div> </div>
) : ( ) : (
<> <GameBoard
<div className="max-w-screen-sm mx-auto"> cells={gameState.cells}
<GameBoard onCellClick={handleMove}
cells={gameState.cells} isYourTurn={gameState.isYourTurn}
onCellClick={handleMove} playerSymbol={gameState.playerSymbol}
isYourTurn={gameState.isYourTurn} lastMove={gameState.lastMove}
playerSymbol={gameState.playerSymbol} turnTimeLeft={gameState.isYourTurn ? turnTimeLeft : undefined}
lastMove={gameState.lastMove} />
turnTimeLeft={gameState.isYourTurn ? turnTimeLeft : undefined} )}
/>
</div> {gameState.winner && (
{gameState.winner && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="bg-gray-800 p-8 rounded-lg text-center">
<div className="bg-gray-800 p-8 rounded-lg text-center"> <h2 className="text-3xl font-bold mb-4">
<h2 className="text-3xl font-bold mb-4"> {(gameState.winner === 'X' && currentPlayer?.isAttacker) ||
{(gameState.winner === 'X' && currentPlayer?.isAttacker) || (gameState.winner === 'O' && !currentPlayer?.isAttacker)
(gameState.winner === 'O' && !currentPlayer?.isAttacker) ? 'Вы победили!'
? 'Вы победили!' : 'Вы проиграли!'}
: 'Вы проиграли!'} </h2>
</h2> <div className="text-xl mb-4">
<div className="text-xl mb-4"> Счет: {currentPlayer?.nickname} ({currentPlayer?.score}) - {opponent?.nickname} ({opponent?.score})
Счет: {currentPlayer?.nickname} ({currentPlayer?.score}) - {opponent?.nickname} ({opponent?.score})
</div>
<button
onClick={handleNewGame}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
Играть снова
</button>
</div>
</div> </div>
)} <button
</> onClick={handleNewGame}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
Играть снова
</button>
</div>
</div>
)} )}
</div> </div>
</div> </div>
......
...@@ -22,6 +22,7 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -22,6 +22,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
const [viewportPosition, setViewportPosition] = useState({ x: 0, y: 0 }); const [viewportPosition, setViewportPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
const [isAnimatingMove, setIsAnimatingMove] = useState(false);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
...@@ -30,20 +31,60 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -30,20 +31,60 @@ const GameBoard: React.FC<GameBoardProps> = ({
useEffect(() => { useEffect(() => {
if (containerRef.current) { if (containerRef.current) {
const container = containerRef.current;
setViewportPosition({ setViewportPosition({
x: viewportSize / 2, x: container.clientWidth / 2,
y: viewportSize / 2 y: container.clientHeight / 2
}); });
} }
}, []); }, []);
useEffect(() => {
if (lastMove && isAnimatingMove) {
const container = containerRef.current;
if (!container) return;
const targetX = container.clientWidth / 2 - lastMove.x * cellSize;
const targetY = container.clientHeight / 2 - lastMove.y * cellSize;
// Анимируем перемещение к последнему ходу
const startX = viewportPosition.x;
const startY = viewportPosition.y;
const startTime = performance.now();
const duration = 1000; // 1 секунда на анимацию
const animate = (currentTime: number) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Функция плавности (ease-in-out)
const easeProgress = progress < 0.5
? 4 * progress * progress * progress
: 1 - Math.pow(-2 * progress + 2, 3) / 2;
setViewportPosition({
x: startX + (targetX - startX) * easeProgress,
y: startY + (targetY - startY) * easeProgress
});
if (progress < 1) {
requestAnimationFrame(animate);
} else {
setIsAnimatingMove(false);
}
};
requestAnimationFrame(animate);
}
}, [lastMove, isAnimatingMove, cellSize]);
const getCellValue = (x: number, y: number): CellValue => { const getCellValue = (x: number, y: number): CellValue => {
const key = `${x},${y}`; const key = `${x},${y}`;
return cells[key] || null; return cells[key] || null;
}; };
const handleCellClick = (x: number, y: number) => { const handleCellClick = (x: number, y: number) => {
if (!isDragging) { if (!isDragging && !isAnimatingMove) {
const value = getCellValue(x, y); const value = getCellValue(x, y);
if (!value && isYourTurn) { if (!value && isYourTurn) {
onCellClick(x, y); onCellClick(x, y);
...@@ -52,6 +93,7 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -52,6 +93,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
}; };
const handleMouseDown = (e: React.MouseEvent) => { const handleMouseDown = (e: React.MouseEvent) => {
if (isAnimatingMove) return;
setIsDragging(true); setIsDragging(true);
setDragStart({ setDragStart({
x: e.clientX - viewportPosition.x, x: e.clientX - viewportPosition.x,
...@@ -60,7 +102,7 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -60,7 +102,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
}; };
const handleMouseMove = (e: React.MouseEvent) => { const handleMouseMove = (e: React.MouseEvent) => {
if (isDragging) { if (isDragging && !isAnimatingMove) {
setViewportPosition({ setViewportPosition({
x: e.clientX - dragStart.x, x: e.clientX - dragStart.x,
y: e.clientY - dragStart.y y: e.clientY - dragStart.y
...@@ -102,13 +144,15 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -102,13 +144,15 @@ const GameBoard: React.FC<GameBoardProps> = ({
const value = getCellValue(x, y); const value = getCellValue(x, y);
const key = `${x},${y}`; const key = `${x},${y}`;
const isHoverable = !value && isYourTurn; const isHoverable = !value && isYourTurn;
const isLastMove = lastMove && lastMove.x === x && lastMove.y === y;
return ( return (
<div <div
key={key} key={key}
onClick={() => handleCellClick(x, y)} onClick={() => handleCellClick(x, y)}
className={`absolute flex items-center justify-center w-[58px] h-[58px] border border-gray-700 bg-gray-800 className={`absolute flex items-center justify-center w-[58px] h-[58px] border border-gray-700 bg-gray-800
transition-all duration-200 ${isHoverable ? 'hover:bg-gray-700 cursor-pointer' : ''}`} transition-all duration-200 ${isHoverable ? 'hover:bg-gray-700 cursor-pointer' : ''}
${isLastMove ? 'ring-2 ring-yellow-500' : ''}`}
style={{ style={{
left: x * cellSize, left: x * cellSize,
top: y * cellSize, top: y * cellSize,
...@@ -116,7 +160,8 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -116,7 +160,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
}} }}
> >
{value && ( {value && (
<div className="transform transition-transform duration-200 scale-100"> <div className={`transform transition-transform duration-200 scale-100
${isLastMove ? 'animate-pulse' : ''}`}>
<ServerIcon state={getServerIconState(value)} /> <ServerIcon state={getServerIconState(value)} />
</div> </div>
)} )}
...@@ -130,11 +175,8 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -130,11 +175,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
}; };
const moveToLastMove = () => { const moveToLastMove = () => {
if (lastMove) { if (lastMove && !isAnimatingMove) {
setViewportPosition({ setIsAnimatingMove(true);
x: viewportSize / 2 - lastMove.x * cellSize,
y: viewportSize / 2 - lastMove.y * cellSize
});
} }
}; };
...@@ -142,12 +184,27 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -142,12 +184,27 @@ const GameBoard: React.FC<GameBoardProps> = ({
<div className="relative"> <div className="relative">
<div <div
ref={containerRef} ref={containerRef}
className={`relative w-[800px] h-[800px] bg-gray-900 overflow-hidden mx-auto className={`relative w-screen h-screen bg-gray-900 overflow-hidden mx-auto
${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`} ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp} onMouseLeave={handleMouseUp}
onTouchStart={(e) => {
const touch = e.touches[0];
handleMouseDown({
clientX: touch.clientX,
clientY: touch.clientY
} as React.MouseEvent);
}}
onTouchMove={(e) => {
const touch = e.touches[0];
handleMouseMove({
clientX: touch.clientX,
clientY: touch.clientY
} as React.MouseEvent);
}}
onTouchEnd={handleMouseUp}
> >
<div <div
style={{ style={{
...@@ -159,13 +216,14 @@ const GameBoard: React.FC<GameBoardProps> = ({ ...@@ -159,13 +216,14 @@ const GameBoard: React.FC<GameBoardProps> = ({
</div> </div>
</div> </div>
<div className="absolute top-4 right-4 flex flex-col gap-2"> <div className="fixed top-4 right-4 flex flex-col gap-2 z-50">
{lastMove && ( {lastMove && (
<button <button
onClick={moveToLastMove} onClick={moveToLastMove}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors" className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors"
disabled={isAnimatingMove}
> >
Показать последний ход Последний ход
</button> </button>
)} )}
{turnTimeLeft !== undefined && ( {turnTimeLeft !== undefined && (
......
...@@ -9,8 +9,8 @@ interface PlayerInfoProps { ...@@ -9,8 +9,8 @@ interface PlayerInfoProps {
const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAttacker, position }) => { const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAttacker, position }) => {
return ( return (
<div className={`fixed ${position === 'top' ? 'top-4' : 'bottom-4'} left-4 flex items-center gap-4 bg-gray-800 rounded-lg p-4 text-white`}> <div className={`fixed ${position === 'top' ? 'top-0' : 'bottom-0'} left-0 flex items-center gap-4 bg-gray-800 rounded-lg p-4 text-white`}>
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${ <div className={`w-12 h-12 rounded-full flex items-center justify-center ${
isCurrentTurn ? (isAttacker ? 'bg-red-500' : 'bg-green-500') : 'bg-gray-600' isCurrentTurn ? (isAttacker ? 'bg-red-500' : 'bg-green-500') : 'bg-gray-600'
}`}> }`}>
{nickname.charAt(0).toUpperCase()} {nickname.charAt(0).toUpperCase()}
...@@ -18,7 +18,7 @@ const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAtta ...@@ -18,7 +18,7 @@ const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAtta
<div> <div>
<div className="font-medium">{nickname}</div> <div className="font-medium">{nickname}</div>
<div className={`text-sm ${isCurrentTurn ? 'text-yellow-400' : 'text-gray-400'}`}> <div className={`text-sm ${isCurrentTurn ? 'text-yellow-400' : 'text-gray-400'}`}>
{isCurrentTurn ? 'Your turn' : 'Waiting...'} {isCurrentTurn ? 'Твой ход' : 'Ожидание...'}
</div> </div>
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment