diff --git a/cards.js b/cards.js index 3a383f5..1fa8b42 100644 --- a/cards.js +++ b/cards.js @@ -23,11 +23,12 @@ module.exports = { health: 8, type: 'minion', faction: 'empire', - text: 'Sith Lord. Fear is his weapon. Даёт +1/+1 штурмовикам и клонам.', + text: 'Sith Lord. Fear is his weapon. Даёт +1/+1 штурмовикам и клонам. Пока на поле, все джедаи противника получают -1/-1.', art: 'vader', legendary: true, deathrattle: 'Нанеси 2 урона всем врагам.', deathrattleId: 'deal_2_all_enemies', + aura: 'jedi_debuff_enemy', bio: 'Дарт Вейдер — бывший Энакин Скайуокер, Избранный, самый сильный в Силе. Рыцарь-джедай, павший на тёмную сторону из-за страха потерять Падме. Ученик Палпатина, ставший тёмным лордом ситхов. Правой рукой Императора, командовал армией Империи. В битве на Мустафаре потерял конечности и был сожжён лавой, выжил благодаря кибернетическим имплантам и костюму. В конце жизни вернулся к свету, убив Палпатина и спася сына, но погиб от полученных ран.', }, yoda: { @@ -1034,11 +1035,11 @@ module.exports = { cost: 3, type: 'spell', faction: 'pirates', - text: 'Укради 2 карты из колоды противника.', + text: 'Укради 2 карты с доски противника (они перейдут в твою руку).', art: 'plunder', spellEffect: 'steal_cards', - spellTarget: 'enemy_player', - bio: 'Пираты грабят и крадут карты у противников.', + spellTarget: 'enemy_board', + bio: 'Пираты грабят и крадут карты у противников с поля боя.', }, mandalorian_rage: { name: 'Ярость мандалорца', @@ -1308,12 +1309,13 @@ module.exports = { health: 6, type: 'minion', faction: 'rebellion', - text: 'Избранный. Может пасть на тёмную сторону. Даёт +1/+1 Оби-Вану и Падме.', + text: 'Избранный. Может пасть на тёмную сторону. Даёт +1/+1 Оби-Вану, Падме и Асоке. Пока на поле, все джедаи получают +1/+1.', art: 'anakin', legendary: true, battlecry: 'Уничтожь вражеского миньона с атакой 4 или меньше.', battlecryId: 'destroy_medium_minion', transformOnDeath: 'vader', + aura: 'jedi_buff_all', bio: 'Избранный, самый сильный в Силе. Ученик Оби-Вана, муж Падме. Пад на тёмную сторону и стал Дартом Вейдером.', }, kanan: { diff --git a/public/game.js b/public/game.js index 3ed7339..a3c7b29 100644 --- a/public/game.js +++ b/public/game.js @@ -325,10 +325,10 @@ stealCardsMode.targetDeck = []; stealCardsMode.selectedIndices = []; - // Получаем колоду противника из gameState + // Получаем доску противника из gameState const targetPlayer = gameState.players[data.targetPlayerIndex]; - if (targetPlayer && targetPlayer.deck) { - stealCardsMode.targetDeck = [...targetPlayer.deck]; + if (targetPlayer && targetPlayer.board) { + stealCardsMode.targetDeck = targetPlayer.board.map(m => m.cardId); } showStealCardsModal(gameState, data); @@ -1571,22 +1571,22 @@ // Специальная обработка для Грабежа - открываем модальное окно const spellCard = cardDb[spellMode.cardId]; - if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') { + if (spellCard && spellCard.spellEffect === 'steal_cards' && (spellCard.spellTarget === 'enemy_player' || spellCard.spellTarget === 'enemy_board')) { // Выбираем противника (tp должен быть индексом противника, tb игнорируем для enemy_player) if (tp !== state.yourIndex && tp >= 0) { const targetPlayer = state.players[tp]; - if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) { + if (targetPlayer && targetPlayer.board && targetPlayer.board.length > 0) { stealCardsMode.active = true; stealCardsMode.handIndex = spellMode.handIndex; stealCardsMode.targetPlayerIndex = tp; - // Получаем актуальную колоду из gameState - stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : []; + // Получаем актуальную доску из gameState + stealCardsMode.targetDeck = targetPlayer.board ? targetPlayer.board.map(m => m.cardId) : []; stealCardsMode.selectedIndices = []; showStealCardsModal(state, { targetPlayerIndex: tp, targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' }; $('spell-mode')?.classList.add('hidden'); @@ -1614,21 +1614,21 @@ // Специальная обработка для Грабежа const spellCard = cardDb[spellMode.cardId]; - if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') { + if (spellCard && spellCard.spellEffect === 'steal_cards' && (spellCard.spellTarget === 'enemy_player' || spellCard.spellTarget === 'enemy_board')) { if (tp !== state.yourIndex && tp >= 0) { const targetPlayer = state.players[tp]; - if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) { + if (targetPlayer && targetPlayer.board && targetPlayer.board.length > 0) { stealCardsMode.active = true; stealCardsMode.handIndex = spellMode.handIndex; stealCardsMode.targetPlayerIndex = tp; - // Получаем актуальную колоду из gameState - stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : []; + // Получаем актуальную доску из gameState + stealCardsMode.targetDeck = targetPlayer.board ? targetPlayer.board.map(m => m.cardId) : []; stealCardsMode.selectedIndices = []; showStealCardsModal(state, { targetPlayerIndex: tp, targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' }; $('spell-mode')?.classList.add('hidden'); @@ -1682,23 +1682,23 @@ el.onclick = function (e) { if (!spellMode.active) return; const spellCard = cardDb[spellMode.cardId]; - if (!spellCard || spellCard.spellEffect !== 'steal_cards' || spellCard.spellTarget !== 'enemy_player') return; + if (!spellCard || spellCard.spellEffect !== 'steal_cards' || (spellCard.spellTarget !== 'enemy_player' && spellCard.spellTarget !== 'enemy_board')) return; e.stopPropagation(); var tp = parseInt(el.dataset.playerIndex ?? el.dataset.opponentIndex, 10); if (tp === state.yourIndex || tp < 0) return; const targetPlayer = state.players[tp]; - if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) { + if (targetPlayer && targetPlayer.board && targetPlayer.board.length > 0) { stealCardsMode.active = true; stealCardsMode.handIndex = spellMode.handIndex; stealCardsMode.targetPlayerIndex = tp; - // Получаем актуальную колоду из gameState - stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : []; + // Получаем актуальную доску из gameState + stealCardsMode.targetDeck = targetPlayer.board ? targetPlayer.board.map(m => m.cardId) : []; stealCardsMode.selectedIndices = []; showStealCardsModal(state, { targetPlayerIndex: tp, targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' }; $('spell-mode')?.classList.add('hidden'); @@ -1710,24 +1710,24 @@ el.ontouchstart = function (e) { if (!spellMode.active) return; const spellCard = cardDb[spellMode.cardId]; - if (!spellCard || spellCard.spellEffect !== 'steal_cards' || spellCard.spellTarget !== 'enemy_player') return; + if (!spellCard || spellCard.spellEffect !== 'steal_cards' || (spellCard.spellTarget !== 'enemy_player' && spellCard.spellTarget !== 'enemy_board')) return; e.preventDefault(); e.stopPropagation(); var tp = parseInt(el.dataset.playerIndex ?? el.dataset.opponentIndex, 10); if (tp === state.yourIndex || tp < 0) return; const targetPlayer = state.players[tp]; - if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) { + if (targetPlayer && targetPlayer.board && targetPlayer.board.length > 0) { stealCardsMode.active = true; stealCardsMode.handIndex = spellMode.handIndex; stealCardsMode.targetPlayerIndex = tp; - // Получаем актуальную колоду из gameState - stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : []; + // Получаем актуальную доску из gameState + stealCardsMode.targetDeck = targetPlayer.board ? targetPlayer.board.map(m => m.cardId) : []; stealCardsMode.selectedIndices = []; showStealCardsModal(state, { targetPlayerIndex: tp, targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' }; $('spell-mode')?.classList.add('hidden'); @@ -2410,18 +2410,18 @@ return; } - // Если противник уже выбран, показываем его колоду + // Если противник уже выбран, показываем его доску (карты на столе) if (stealCardsMode.targetPlayerIndex !== null) { - // Обновляем колоду из актуального gameState + // Обновляем доску из актуального gameState const targetPlayer = currentState.players[stealCardsMode.targetPlayerIndex]; - if (targetPlayer && targetPlayer.deck) { - stealCardsMode.targetDeck = [...targetPlayer.deck]; + if (targetPlayer && targetPlayer.board) { + stealCardsMode.targetDeck = targetPlayer.board.map(m => m.cardId); } else { - console.warn('Target player not found or deck is empty:', stealCardsMode.targetPlayerIndex); + console.warn('Target player not found or board is empty:', stealCardsMode.targetPlayerIndex); } if (stealCardsMode.targetDeck.length === 0) { - // Колода пуста, закрываем модальное окно + // Доска пуста, закрываем модальное окно $('steal-cards-overlay')?.classList.add('hidden'); stealCardsMode.active = false; return; @@ -2431,10 +2431,11 @@ deckList.classList.remove('hidden'); if (hintEl) { - hintEl.textContent = `Выберите до 2 карт из колоды ${targetPlayer?.name || `Игрока ${stealCardsMode.targetPlayerIndex + 1}`} (${stealCardsMode.targetDeck.length} карт)`; + hintEl.textContent = `Выберите до 2 карт с доски ${targetPlayer?.name || `Игрока ${stealCardsMode.targetPlayerIndex + 1}`} (${stealCardsMode.targetDeck.length} карт на столе)`; } - deckList.innerHTML = stealCardsMode.targetDeck.map((cardId, idx) => { + deckList.innerHTML = targetPlayer.board.map((minion, idx) => { + const cardId = minion.cardId; const cardDbToUse = currentState?.cardDb || cardDb; const meta = cardDbToUse[cardId]; if (!meta) { @@ -2451,7 +2452,7 @@ const costDisplay = `
${cost}
`; const art = typeof getCardArt === 'function' ? getCardArt(meta) : '✦'; - return `
+ return `
${art}
@@ -2467,9 +2468,9 @@ const handleCardClick = function(e) { e.preventDefault(); e.stopPropagation(); - const idx = parseInt(card.dataset.deckIndex, 10); + const idx = parseInt(card.dataset.boardIndex, 10); if (isNaN(idx)) { - console.warn('Invalid deck index:', card.dataset.deckIndex); + console.warn('Invalid board index:', card.dataset.boardIndex); return; } const selectedIdx = stealCardsMode.selectedIndices.indexOf(idx); @@ -2482,8 +2483,8 @@ showStealCardsModal(currentState, data || { targetPlayerIndex: stealCardsMode.targetPlayerIndex, targetPlayerName: currentState?.players?.[stealCardsMode.targetPlayerIndex]?.name || `Игрок ${stealCardsMode.targetPlayerIndex + 1}`, - targetDeckSize: stealCardsMode.targetDeck.length, - maxCards: Math.min(2, stealCardsMode.targetDeck.length) + targetBoardSize: currentState?.players?.[stealCardsMode.targetPlayerIndex]?.board?.length || 0, + maxCards: Math.min(2, currentState?.players?.[stealCardsMode.targetPlayerIndex]?.board?.length || 0) }); }; card.onclick = handleCardClick; @@ -2554,18 +2555,18 @@ return; } const targetPlayer = currentState.players[playerIdx]; - if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) { + if (targetPlayer && targetPlayer.board && targetPlayer.board.length > 0) { stealCardsMode.targetPlayerIndex = playerIdx; - stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : []; - console.log('Selected target player:', playerIdx, 'Deck size:', stealCardsMode.targetDeck.length); + stealCardsMode.targetDeck = targetPlayer.board ? targetPlayer.board.map(m => m.cardId) : []; + console.log('Selected target player:', playerIdx, 'Board size:', stealCardsMode.targetDeck.length); showStealCardsModal(currentState, { targetPlayerIndex: playerIdx, targetPlayerName: targetPlayer.name || `Игрок ${playerIdx + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); } else { - console.warn('Target player has no deck or is invalid:', playerIdx, targetPlayer); + console.warn('Target player has no board or is invalid:', playerIdx, targetPlayer); } }; option.onclick = handleOptionClick; diff --git a/server.js b/server.js index a689634..6aa41e8 100644 --- a/server.js +++ b/server.js @@ -122,9 +122,18 @@ function initGame(room) { // Если режим ИИ, добавляем ИИ игрока if (room.aiMode) { + const starWarsHeroes = [ + 'Darth Maul', 'Count Dooku', 'General Grievous', 'Grand Moff Tarkin', + 'Admiral Thrawn', 'Kylo Ren', 'Snoke', 'General Hux', + 'Captain Phasma', 'Moff Gideon', 'Cad Bane', 'Asajj Ventress', + 'Savage Opress', 'Darth Sidious', 'Darth Tyranus', 'Darth Plagueis', + 'Jango Fett', 'Boba Fett', 'IG-88', 'Bossk', + 'Dengar', 'Zuckuss', '4-LOM', 'Aurra Sing' + ]; + const randomHero = starWarsHeroes[Math.floor(Math.random() * starWarsHeroes.length)]; players.push({ id: 'AI_' + Date.now(), - name: 'ИИ Противник', + name: randomHero, deck: createDeck(factionChoice), hand: [], board: [], @@ -1076,6 +1085,28 @@ function applySynergies(room) { } } }); + + // Аура: Пока Дарт Вейдер на поле, все джедаи противника получают -1/-1 + if (card.aura === 'jedi_debuff_enemy') { + gameState.players.forEach((enemyPlayer, enemyPlayerIndex) => { + if (enemyPlayerIndex !== playerIndex && enemyPlayer.board) { + enemyPlayer.board.forEach((enemyMinion) => { + const enemyCard = cardDb[enemyMinion.cardId]; + if (enemyCard && (enemyCard.name?.includes('Jedi') || + enemyCard.id === 'luke' || enemyCard.id === 'obiwan' || + enemyCard.id === 'ahsoka' || enemyCard.id === 'mace' || + enemyCard.id === 'quigon' || enemyCard.id === 'plo_koon' || + enemyCard.id === 'ki_adi' || enemyCard.id === 'aayla' || + enemyCard.id === 'shaak_ti' || enemyCard.id === 'kanan' || + enemyCard.id === 'ezra' || enemyCard.id === 'cal_kestis' || + enemyCard.id === 'yoda' || enemyCard.id === 'anakin')) { + enemyMinion.synergyAttackBonus = (enemyMinion.synergyAttackBonus || 0) - 1; + enemyMinion.synergyHealthBonus = (enemyMinion.synergyHealthBonus || 0) - 1; + } + }); + } + }); + } } // Люк Скайуокер даёт +1/+1 всем повстанцам @@ -1195,17 +1226,39 @@ function applySynergies(room) { }); } - // Энакин даёт +1/+1 Оби-Вану + // Энакин даёт +1/+1 Оби-Вану, Падме и Асоке if (card.id === 'anakin' || card.name === 'Anakin Skywalker') { player.board.forEach((other, otherIdx) => { if (idx !== otherIdx) { const otherCard = cardDb[other.cardId]; - if (otherCard && (otherCard.id === 'obiwan' || otherCard.name === 'Obi-Wan Kenobi')) { + if (otherCard && (otherCard.id === 'obiwan' || otherCard.name === 'Obi-Wan Kenobi' || + otherCard.id === 'padme' || otherCard.name === 'Padmé Amidala' || + otherCard.id === 'ahsoka' || otherCard.name === 'Ahsoka Tano')) { other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1; other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1; } } }); + + // Аура: Пока Анакин на поле, все джедаи получают +1/+1 + if (card.aura === 'jedi_buff_all') { + player.board.forEach((other, otherIdx) => { + if (idx !== otherIdx) { + const otherCard = cardDb[other.cardId]; + if (otherCard && (otherCard.name?.includes('Jedi') || + otherCard.id === 'luke' || otherCard.id === 'obiwan' || + otherCard.id === 'ahsoka' || otherCard.id === 'mace' || + otherCard.id === 'quigon' || otherCard.id === 'plo_koon' || + otherCard.id === 'ki_adi' || otherCard.id === 'aayla' || + otherCard.id === 'shaak_ti' || otherCard.id === 'kanan' || + otherCard.id === 'ezra' || otherCard.id === 'cal_kestis' || + otherCard.id === 'yoda')) { + other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1; + other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1; + } + } + }); + } } // Йода даёт +1/+1 всем джедаям @@ -1390,11 +1443,11 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde const eff = card.spellEffect; const needTarget = card.spellTarget && card.spellTarget !== 'none'; - // Специальная обработка для steal_cards - требует только выбор противника (не требует targetBoardIndex) + // Специальная обработка для steal_cards - требует выбор противника и карт с его доски if (eff === 'steal_cards') { if (targetPlayerIndex == null || targetPlayerIndex === pi) return; const targetPlayer = gameState.players[targetPlayerIndex]; - if (!targetPlayer || targetPlayer.health <= 0 || !targetPlayer.deck || targetPlayer.deck.length === 0) return; + if (!targetPlayer || targetPlayer.health <= 0 || !targetPlayer.board || targetPlayer.board.length === 0) return; // Отправляем запрос на выбор карт для кражи const socket = io.sockets.sockets.get(p.id); @@ -1402,8 +1455,8 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde socket.emit('stealCardsRequest', { targetPlayerIndex: targetPlayerIndex, targetPlayerName: targetPlayer.name || `Игрок ${targetPlayerIndex + 1}`, - targetDeckSize: targetPlayer.deck.length, - maxCards: Math.min(2, targetPlayer.deck.length) + targetBoardSize: targetPlayer.board.length, + maxCards: Math.min(2, targetPlayer.board.length) }); } return; // Не тратим ману и не удаляем карту пока - это сделаем после выбора @@ -1579,22 +1632,25 @@ function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIn const targetPlayer = gameState.players[targetPlayerIndex]; if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return; - if (!targetPlayer.deck || targetPlayer.deck.length === 0) return; + if (!targetPlayer.board || targetPlayer.board.length === 0) return; - // Проверяем индексы карт + // Проверяем индексы карт на доске if (!Array.isArray(cardIndices) || cardIndices.length === 0 || cardIndices.length > 2) return; - const validIndices = cardIndices.filter(idx => idx >= 0 && idx < targetPlayer.deck.length); + const validIndices = cardIndices.filter(idx => idx >= 0 && idx < targetPlayer.board.length); if (validIndices.length === 0) return; // Убираем дубликаты и сортируем по убыванию (чтобы удалять с конца) const uniqueIndices = [...new Set(validIndices)].sort((a, b) => b - a); - // Крадём карты + // Крадём карты с доски противника const stolenCards = []; - uniqueIndices.forEach(idx => { - if (idx >= 0 && idx < targetPlayer.deck.length) { - stolenCards.push(targetPlayer.deck[idx]); - targetPlayer.deck.splice(idx, 1); + uniqueIndices.forEach(boardIdx => { + if (boardIdx >= 0 && boardIdx < targetPlayer.board.length) { + const minion = targetPlayer.board[boardIdx]; + if (minion && minion.cardId) { + stolenCards.push(minion.cardId); + targetPlayer.board.splice(boardIdx, 1); + } } });