123
This commit is contained in:
225
server.js
225
server.js
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user