Files
vue-pult/docs/CLIENT_GAMEPORTS_LOGIC copy.md
2025-10-01 11:54:13 +03:00

36 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Логика портов игр в React-клиенте (client/)
## Обзор
Порты (ports[1-6]) - игровые станции в store/gameSlice.ts. Каждый порт - визуальный 3D-блок (styles/3d-*.css: 3d-blocks.css, 3d-isometric.css, 3d-port-breathing.css, fixed-port-sizes.css), в grid (stable-grid.css, grid-with-footer.css). Логика: actions gameStarted/Ended/shotFired/patronsUpdated/bonusAvailability для состояний, drag-drop для переносов (actions gameMovingStarted/playerMovingStarted/gameMoved/playerMoved, utils/dragAnimation.ts для анимации drag, flyingAnimation.ts для эффектов перемещения). Кнопки: start/pause/end/bonus per port (dispatch actions + sendMessage WS). UI: draggable ports (drag-drop-animation.css), hover/click для действий (button-force.css, header-buttons-final.css). Нет явного GamePorts.tsx (вероятно в Dashboard или main App, не в коде - подразумевается).
## Вид порта и расположение кнопок
Порты расположены в 2x3 grid (grid-override.css, layout-fixes.css), сверху вниз слева направо (Порт 1-3 top, 4-6 bottom), fixed sizes (fixed-port-sizes.css, 200x150px?), fullscreen on mobile (fullscreen-ports.css). Каждый порт - div.port (3d-theme.css), с inner .port-content (padding simple-padding.css).
- **Пустой порт (game=0, gamer.gamerId=null)**: Темный/серый 3D-куб (3d-real-cubes.css, original-port-colors.css), центр label "Порт X" (X=1-6, white text), иконка пустого слева (cursor-icon.png? 50x50). No buttons. Hover: scale 1.05, glow blue (3d-port-breathing.css). Drag: grab cursor on whole port.
- **Порт с игроком (gamer.gamerId != null, game=0)**: Светлый блок (3d-blocks-extreme.css), top: имя/ID игрока (from gamer.gamerId, lookup authStore), bottom: stats gameCounter (кол-во игр), games list (mini-icons игр?). Анимация idle (flying-animation.css). Кнопки bottom-right: "Начать игру" (green button, icon play, click -> open GameSelectionModal.tsx select game ID -> dispatch gameStarted({line, game: {game: ID, startTime: new Date().toISOString(), gamer: existing gamer, canHaveBonus: true}}) + sendMessage({do: 'game/start', line, game: ID})), "Перенести игрока" (blue button, icon reverse-arrows-icon.png, click -> playerMovingStarted(line) start drag player icon three-people-icon.png). Hover buttons: button-force.css scale.
- **Порт с игрой (game >0, gamer.gamerId != null)**: Активный зеленый/синий (3d-extreme-ports.css, ports-reset.css), top: game name/ID (lookup from game types?), center: timer countdown (from startTime, e.g. "05:30" red if <1min), left: patr left (number badge, red if <10), right: patrOk hits (green badge), bottom: patrAdd extra (yellow badge), bonus indicator top-right gold glow if isBonus/canHaveBonus (smile-icon.png?). Анимация running (animation-performance.css, shots fly in drag-drop-animation.css). Кнопки bottom row: "Пауза" (yellow button, pause icon gear-icon.png, click -> dispatch timeOut=true or systemPaused() + sendMessage({do: 'game/pause', line})), "Конец игры" (red button, stop icon, click -> dispatch gameEnded({line}) сброс game=0 patr=0 startTime=null isBonus=false + sendMessage({do: 'game/end', line}) save stats to gamer.games.push({game, patrOk, timestamp}) gameCounter++), "Бонус" (gold button if canHaveBonus, bonus icon, click -> dispatch bonusAvailabilityUpdated({line, canHaveBonus: false}), isBonus=true + sendMessage({do: 'game/bonus', line}) - double patrOk for 30s?), "Перенести игру" (blue button, arrows icon, click -> gameMovingStarted(line) start drag game icon videocamera-icon.png or game-specific). Если pause: buttons grayed (no-scroll-fix.css). Подсветка: portHighlighted({line, type: 'shot' green flash patrOk++ + send 'shot/fired' {line, patr, time, power}, 'miss' red patr-- + send 'shot/miss', 'hit' yellow bonus + send 'shot/hit', 'warning' orange low patr + send 'warning' line}), clearPortHighlight after 2s (timeout).
## Переносы (drag and drop)
- **Общая логика**: Перенос игры (game>0) или игрока (gamerId !=null game=0) между портами 1-6. Кнопка "Перенести" or зажатие mouse (onMouseDown on port area). Global state movingGame/player = fromPort (for shadow follow). Drop on target (onDrop/onDragOver valid if to empty or compatible). Send WS 'game/move' {from, to, type: 'game'|'player'} for сервер sync. Анимация: dragAnimation.ts (onMouseMove update shadow position absolute, opacity 0.8, cursor grabbing), flyingAnimation.ts (on drop fly icon from from to to with keyframes translate3d curve, rotate 360, duration distance-based 0.5-1s, trail effect). Styles: drag-drop-animation.css (lift box-shadow, no-scroll during drag).
- **По кнопке "Перенести"**: Click button -> dispatch gameMovingStarted({fromPort: line}) or playerMovingStarted (set movingGame/player = line, show shadow clone port/port-player icon). Mouse move - shadow follows (dragAnimation.ts event listener). Drop on target port (onDrop if valid - empty for game, empty player for player) -> gameMoved({from, to}) copy all port data (game, patr, patrOk, patrAdd, startTime, gamer, timeOut, canHaveBonus, isBonus, pendingGameData) to to, clear from (game=0, patr=0, startTime=null, gamer null if player move, timeOut=false, canHaveBonus=false, isBonus=false) + sendMessage({do: 'game/move', from, to, type: 'game'|'player'}). Invalid drop (occupied) - bounce animation (flyingAnimationOptimized.ts shake).
- **Зажатием mouse**: onMouseDown on port area (if game>0 drag game area, if gamerId drag player icon three-people-icon.png) -> start drag (gameMovingStarted or playerMovingStarted, same as button). Hold mouse - shadow follow (dragAnimation.ts). Release on target - same drop logic. Cancel: onMouseUp outside grid or right-click -> movingCancelled() reset movingGame/player = null, shadow fade out.
- **Ограничения**: Can't drag during pause (ports[0].pause=true - disable drag), smena closed (smena=null - ports grayed). Can't drop on self. If to occupied by same type - swap? (not in code - copy/clear). Анимация cancel: fly back to from (flyingAnimation.ts reverse path).
- **WS синхронизация**: После move send 'game/move' - сервер broadcast 'game/update' to other clients -> dispatch gameStarted/Ended on to/from.
## Миграция на Vue
- GamePorts.vue: v-for ports[1-6] <GamePort :port="ports[i]" :key="i" @start-game="startGame(i)" @pause="pauseGame(i)" @end="endGame(i)" @bonus="bonusGame(i)" @transfer="startTransfer(i, type)" draggable="true" @dragstart="onDragStart(i, type)" @drop="onDrop($event, i)" >.
- GamePort.vue: div.port :class="{active: game>0, player: gamerId, bonus: isBonus, pause: timeOut}" > top game name/timer, center patr badges, bottom buttons @click dispatch + sendMessage, drag handle icon reverse-arrows for transfer.
- Drag: useDrag (VueUse) or vuedraggable list ports, @dragstart="gameMovingStarted(from)" shadow <Teleport> follow mouse, @drop="gameMoved(from, to)" fly <GSAP> translate3d from to.
- Анимация: <TransitionGroup> for ports reorder, custom keyframes for fly (flyingAnimation.css).
## Рекомендации
- Компонент: GamePorts.tsx - map ports to GamePort with handlers.
- GamePort.tsx - conditional render (empty/player/game), buttons conditional (bonus if canHaveBonus).
- Зависимости: useGameStore (ports), useWebSocket (send 'game/*').
- Тестирование: Mock drag events, check state after drop (data copied, source cleared, WS sent).