This commit is contained in:
2026-01-27 01:18:34 +03:00
parent c59b51c69e
commit 1c231fe28d
4 changed files with 510 additions and 28 deletions

225
server.js
View File

@ -113,8 +113,28 @@ function initGame(room) {
manualDrawUsed: false,
fatigueCounter: 0,
heroAbilityUsed: false,
isAI: false,
}));
// Если режим ИИ, добавляем ИИ игрока
if (room.aiMode) {
players.push({
id: 'AI_' + Date.now(),
name: 'ИИ Противник',
deck: createDeck(factionChoice),
hand: [],
board: [],
mana: 0,
maxMana: 0,
health: 30,
hero: 'vader',
manualDrawUsed: false,
fatigueCounter: 0,
heroAbilityUsed: false,
isAI: true,
});
}
players.forEach((p, i) => {
p.hand = drawCards(p.deck, i < 2 ? 3 : 4);
});
@ -131,6 +151,193 @@ function initGame(room) {
room.gameStarted = true;
startTurnTimer(room);
broadcastGameState(room);
// Если первый ход ИИ, делаем его ход
if (room.aiMode && players[0].isAI) {
setTimeout(() => {
if (room.gameState && room.gameState.phase === 'playing') {
makeAITurn(room);
}
}, 1000);
}
}
// Функция для хода ИИ
function makeAITurn(room) {
const gameState = room.gameState;
if (!gameState || gameState.phase !== 'playing') return;
const aiPlayerIndex = gameState.currentPlayerIndex;
const aiPlayer = gameState.players[aiPlayerIndex];
if (!aiPlayer || !aiPlayer.isAI || aiPlayer.health <= 0) return;
if (gameState.currentPlayerIndex !== aiPlayerIndex) return;
// Применяем синергии перед ходом
applySynergies(room);
// Небольшая задержка для визуализации
setTimeout(() => {
let actionsDone = 0;
const maxActions = 5; // Максимум действий за ход
// 1. Играем карты (приоритет дешёвым и эффективным)
const playableCards = aiPlayer.hand
.map((cardId, index) => {
const card = cardDb[cardId];
if (!card) return null;
const cost = card.cost || 0;
if (cost > aiPlayer.mana) return null;
if (card.type === 'minion' && aiPlayer.board.length >= 7) return null;
// Оценка карты (чем выше, тем лучше)
let value = 0;
if (card.type === 'minion') {
value = (card.attack || 0) + (card.health || 0) - cost * 2;
if (card.battlecryId) value += 2;
if (card.legendary) value += 1;
} else if (card.type === 'spell') {
value = 3 - cost;
if (card.spellEffect === 'deal_3_minion' || card.spellEffect === 'deal_3_any') value += 2;
if (card.spellEffect === 'buff_3_2') value += 1;
}
return { index, card, cost, value };
})
.filter(Boolean)
.sort((a, b) => b.value - a.value);
// Играем лучшие карты
let cardIndex = 0;
const playNextCard = () => {
if (cardIndex >= playableCards.length || actionsDone >= 3) {
// Переходим к атакам
setTimeout(() => {
performAIAttacks(room, aiPlayerIndex);
}, 500);
return;
}
const playable = playableCards[cardIndex];
const currentPlayer = gameState.players[aiPlayerIndex];
if (!currentPlayer || playable.cost > currentPlayer.mana) {
cardIndex++;
playNextCard();
return;
}
if (playable.card.type === 'minion') {
// Играем миньона (пропускаем карты с выбором цели для упрощения)
if (playable.card.battlecryId === 'return_hand_enemy') {
cardIndex++;
playNextCard();
return;
}
const boardPos = currentPlayer.board.length;
playCard(room, aiPlayer.id, playable.index, boardPos);
actionsDone++;
cardIndex++;
setTimeout(playNextCard, 600);
} else if (playable.card.type === 'spell') {
// Используем заклинание
const enemies = gameState.players.filter((p, i) => i !== aiPlayerIndex && p.health > 0);
if (enemies.length > 0) {
const targetEnemy = enemies[0];
let targetBoardIndex = -1;
if (playable.card.spellTarget === 'enemy_minion' || playable.card.spellTarget === 'any_minion') {
if (targetEnemy.board && targetEnemy.board.length > 0) {
targetBoardIndex = 0;
} else {
targetBoardIndex = -1;
}
} else if (playable.card.spellTarget === 'friendly_minion') {
if (currentPlayer.board && currentPlayer.board.length > 0) {
targetBoardIndex = 0;
} else {
cardIndex++;
playNextCard();
return;
}
} else if (playable.card.spellTarget === 'enemy_player') {
// Для заклинаний типа "Грабеж" пропускаем (требует выбор карт)
if (playable.card.spellEffect === 'steal_cards') {
cardIndex++;
playNextCard();
return;
}
targetBoardIndex = -1;
}
playSpell(room, aiPlayer.id, playable.index, gameState.players.indexOf(targetEnemy), targetBoardIndex);
actionsDone++;
cardIndex++;
setTimeout(playNextCard, 600);
} else {
cardIndex++;
playNextCard();
}
} else {
cardIndex++;
playNextCard();
}
};
playNextCard();
}, 800);
}
// Функция для атак ИИ
function performAIAttacks(room, aiPlayerIndex) {
const gameState = room.gameState;
if (!gameState || gameState.phase !== 'playing') {
endTurn(room);
return;
}
const aiPlayer = gameState.players[aiPlayerIndex];
if (!aiPlayer || aiPlayer.health <= 0) {
endTurn(room);
return;
}
const attackableMinions = aiPlayer.board.filter(m => m.canAttack && !m.frozen);
const enemies = gameState.players.filter((p, i) => i !== aiPlayerIndex && p.health > 0);
if (attackableMinions.length === 0 || enemies.length === 0) {
setTimeout(() => endTurn(room), 500);
return;
}
let attackIndex = 0;
const performNextAttack = () => {
if (attackIndex >= attackableMinions.length) {
setTimeout(() => endTurn(room), 500);
return;
}
const minion = attackableMinions[attackIndex];
const boardIndex = aiPlayer.board.indexOf(minion);
const targetEnemy = enemies[0];
const targetPlayerIndex = gameState.players.indexOf(targetEnemy);
let targetBoardIndex = -1;
// Выбираем цель: сначала слабые миньоны, потом герой
if (targetEnemy.board && targetEnemy.board.length > 0) {
const weakMinion = targetEnemy.board.find(m => m.health <= minion.attack) ||
targetEnemy.board[0];
targetBoardIndex = targetEnemy.board.indexOf(weakMinion);
} else {
targetBoardIndex = -1; // Атакуем героя
}
attack(room, aiPlayer.id, boardIndex, targetPlayerIndex, targetBoardIndex);
attackIndex++;
setTimeout(performNextAttack, 800);
};
performNextAttack();
}
function clearTurnTimer(room) {
@ -364,6 +571,15 @@ function endTurn(room) {
checkGameOver(room);
startTurnTimer(room);
broadcastGameState(room);
// Если следующий игрок - ИИ, делаем его ход
if (np.isAI && gameState.phase === 'playing') {
setTimeout(() => {
if (room.gameState && room.gameState.phase === 'playing') {
makeAITurn(room);
}
}, 1500);
}
}
function manualDraw(room, socketId) {
@ -1519,7 +1735,9 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
}
io.on('connection', (socket) => {
socket.on('createRoom', (name) => {
socket.on('createRoom', (data) => {
const name = typeof data === 'string' ? data : (data?.name || 'Host');
const aiMode = typeof data === 'object' && data?.aiMode === true;
const code = generateRoomCode();
const room = {
code,
@ -1527,6 +1745,7 @@ io.on('connection', (socket) => {
gameState: null,
gameStarted: false,
faction: null,
aiMode: aiMode,
turnTimerInterval: null,
turnTimeLeft: TURN_SECONDS,
turnTimerWarnFired: false,
@ -1539,6 +1758,7 @@ io.on('connection', (socket) => {
lobby: room.lobby,
serverIP: serverIP,
serverPort: serverPort,
aiMode: aiMode,
});
cleanupEmptyRooms();
});
@ -1593,7 +1813,8 @@ io.on('connection', (socket) => {
socket.emit('error', 'Только хост может начать игру');
return;
}
if (room.lobby.length < MIN_PLAYERS) {
// В режиме ИИ можно начать игру с одним игроком
if (!room.aiMode && room.lobby.length < MIN_PLAYERS) {
socket.emit('error', `Нужно минимум ${MIN_PLAYERS} игроков`);
return;
}