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

7.8 KiB
Raw Blame History

Логика портов игр в 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 follow mouse, @drop="gameMoved(from, to)" fly translate3d from to.
  • Анимация: 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).