diff --git a/rpgsaga/saga/src/classes/laba3Archer.ts b/rpgsaga/saga/src/classes/laba3Archer.ts new file mode 100644 index 00000000..70d41b39 --- /dev/null +++ b/rpgsaga/saga/src/classes/laba3Archer.ts @@ -0,0 +1,81 @@ +import { Player } from '../laba3CreatingPlayer'; +import { Logs } from '../laba3Logs'; + +export class Archer extends Player { + abilities: string[] = ['Огненные стрелы', 'Ледяные стрелы']; + private fireArrowsUsed: boolean = false; + private iceArrowsUsed: number = 0; + private fireArrowsDuration: number = 0; + private iceArrowsDuration: number = 0; + + constructor(name: string, strength: number, health: number) { + super(name, strength, health); + } + + // Метод для сброса состояния огненных стрел + public resetFireArrowsUsed(): void { + this.fireArrowsUsed = false; + this.fireArrowsDuration = 0; + } + + // Метод для сброса состояния ледяных стрел + public resetIceArrowsUsed(): void { + this.iceArrowsUsed = 0; + this.iceArrowsDuration = 0; + } + + useAbility(target: Player, logs: Logs): void { + const ability = this.getSelectedAbility(); + if (!ability) return; + + const baseDamage = this.getStrength(); + target.takeDamage(baseDamage); + + logs.logAbility( + this, + ability, + target, + `и наносит ${baseDamage} основного урона. ${target.getName()} теряет 3 единицы жизни следующие 2 хода`, + ); + + if (ability === 'Огненные стрелы' && !this.fireArrowsUsed) { + this.fireArrowsUsed = true; + this.fireArrowsDuration = 3; + } else if (ability === 'Ледяные стрелы' && this.iceArrowsUsed < 2) { + this.iceArrowsUsed++; + this.iceArrowsDuration = 3; + } + } + + applyAbilityEffects(target: Player, logs: Logs): void { + this.applyArrowEffect(target, logs, 'Огненные стрелы', this.fireArrowsUsed, this.fireArrowsDuration); + this.applyArrowEffect(target, logs, 'Ледяные стрелы', this.iceArrowsUsed > 0, this.iceArrowsDuration); + + if (this.fireArrowsDuration > 0) this.fireArrowsDuration--; + if (this.iceArrowsDuration > 0) this.iceArrowsDuration--; + } + + private applyArrowEffect(target: Player, logs: Logs, arrowType: string, isUsed: boolean, duration: number): void { + if (isUsed && duration > 0) { + const damage = 3; + target.takeDamage(damage); + logs.logDamageOverTime(target, damage, arrowType); + } + } + + public setStrength(strength: number): void { + if (strength >= 15 && strength <= 70) { + super.setStrength(strength); + } else { + throw new Error('Strength for Archer must be between 15 and 70'); + } + } + + public setHealth(health: number): void { + if (health >= 60 && health <= 180) { + super.setHealth(health); + } else { + throw new Error('Health for Archer must be between 60 and 180'); + } + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/classes/laba3Knight.ts b/rpgsaga/saga/src/classes/laba3Knight.ts new file mode 100644 index 00000000..80b8b287 --- /dev/null +++ b/rpgsaga/saga/src/classes/laba3Knight.ts @@ -0,0 +1,38 @@ +import { Player } from '../laba3CreatingPlayer'; +import { Logs } from '../laba3Logs'; + +export class Knight extends Player { + abilities: string[] = ['Короночка']; + private abilityUsed: boolean = false; + + public resetAbilityUsed(): void { + this.abilityUsed = false; + } + + useAbility(target: Player, logs: Logs): void { + const ability = this.getSelectedAbility(); + if (!ability) return; + + if (ability === 'Короночка' && !this.abilityUsed) { + const damage = Math.round(this.getStrength() * 1.5); + target.takeDamage(damage); + this.abilityUsed = true; + logs.logAbility(this, ability, target, `который получает ${damage} урона`); + this.setSelectedAbility(null); // Сбрасываем выбранную способность + } + } + public setStrength(strength: number): void { + if (strength >= 20 && strength <= 100) { + super.setStrength(strength); + } else { + throw new Error('Strength for Knight must be between 20 and 100'); + } + } + public setHealth(health: number): void { + if (health >= 80 && health <= 200) { + super.setHealth(health); + } else { + throw new Error('Health for Knight must be between 80 and 200'); + } + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/classes/laba3Mage.ts b/rpgsaga/saga/src/classes/laba3Mage.ts new file mode 100644 index 00000000..0b123d7f --- /dev/null +++ b/rpgsaga/saga/src/classes/laba3Mage.ts @@ -0,0 +1,50 @@ +import { Player } from '../laba3CreatingPlayer'; +import { Logs } from '../laba3Logs'; + +export class Mage extends Player { + abilities: string[] = ['Оглушение', 'Лечение']; + private abilityUsed: boolean = false; + + public resetAbilityUsed(): void { + this.abilityUsed = false; + } + + useAbility(target: Player, logs: Logs): void { + const ability = this.getSelectedAbility(); // Используем выбранную способность + if (!ability) return; + + if (ability === 'Оглушение' && !this.abilityUsed) { + target.setStunDuration(2); // Устанавливаем длительность оглушения на 2 хода + target.setIsStunned(true); // Применяем оглушение + this.abilityUsed = true; + logs.logAbility(this, ability, target, 'будет оглушен на 2 хода'); + + // Маг сразу атакует после применения оглушения + this.attack(target, logs); + } else if (ability === 'Лечение') { + const healAmount = 20; + this.setHealth(this.getHealth() + healAmount); + logs.logHeal(this, healAmount); // Логируем лечение + } + } + + takeDamage(damage: number): void { + super.takeDamage(damage); // Всегда наносим урон, независимо от оглушения + } + + public setStrength(strength: number): void { + if (strength >= 10 && strength <= 50) { + super.setStrength(strength); + } else { + throw new Error('Strength for Mage must be between 10 and 50'); + } + } + + public setHealth(health: number): void { + if (health >= 50 && health <= 150) { + super.setHealth(health); + } else { + throw new Error('Health for Mage must be between 50 and 150'); + } + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/index.ts b/rpgsaga/saga/src/index.ts index 696eeab6..80adb154 100644 --- a/rpgsaga/saga/src/index.ts +++ b/rpgsaga/saga/src/index.ts @@ -1,5 +1,22 @@ -import { taskA, taskB } from './laba1'; -import { Car } from './laba2'; +import { taskA, taskB } from './lab1and2/laba1'; +import { Car } from './lab1and2/laba2'; +import { Game } from './laba3Gameplay'; +import { Optional } from './laba3Optionals'; +import * as readline from 'readline'; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + // Функция для асинхронного запроса ввода пользователя + function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer); + }); + }); + } function output(answersX: number[], answersY: number[]): void { for (let i = 0; i < answersX.length; i++) { @@ -7,7 +24,7 @@ function output(answersX: number[], answersY: number[]): void { } } -function main(): void { +async function main() { console.log("Бусыгин Андрей Михайлович"); let [xL, yL] = taskA(1.25, 3.25, 0.4); output(xL, yL); @@ -19,5 +36,30 @@ function main(): void { const car1 = new Car('Toyota Corolla', 'Gasoline'); car1.setSpeed(120); car1.displayInfo(); + +const optional = new Optional(); + + // Запрашиваем у пользователя выбор + const choice = await askQuestion('Выберите вариант создания игроков (1 - случайные, 2 - вручную): '); + let players; + + if (choice === '2') { + const numberOfPlayersInput = await askQuestion('Введите количество игроков: '); + const numberOfPlayers = parseInt(numberOfPlayersInput, 10); + players = await optional.createPlayersManually(numberOfPlayers, askQuestion); // Передаем askQuestion + } else { + const numberOfPlayers = 6; // По умолчанию 6 игроков + players = optional.createRandomPlayers(numberOfPlayers); + } + + const game = new Game(players); + game.start(); + + // Закрываем интерфейс чтения + rl.close(); } -main(); \ No newline at end of file + +// Вызываем main +main().catch((error) => { + console.error('Ошибка:', error); +}); diff --git a/rpgsaga/saga/src/laba1.ts b/rpgsaga/saga/src/lab1and2/laba1.ts similarity index 100% rename from rpgsaga/saga/src/laba1.ts rename to rpgsaga/saga/src/lab1and2/laba1.ts diff --git a/rpgsaga/saga/src/laba2.ts b/rpgsaga/saga/src/lab1and2/laba2.ts similarity index 100% rename from rpgsaga/saga/src/laba2.ts rename to rpgsaga/saga/src/lab1and2/laba2.ts diff --git a/rpgsaga/saga/src/laba3CreatingPlayer.ts b/rpgsaga/saga/src/laba3CreatingPlayer.ts new file mode 100644 index 00000000..a83a6f4a --- /dev/null +++ b/rpgsaga/saga/src/laba3CreatingPlayer.ts @@ -0,0 +1,170 @@ +import { Logs } from './laba3Logs'; // Импортируем logs + +export class Player { + private name: string; + private strength: number; + private health: number; + private maxHealth: number; + private isDead: boolean; + protected abilities: string[] = []; + private selectedAbility: string | null = null; + protected isStunned: boolean = false; // Текущее состояние оглушения + private willBeStunned: boolean = false; // Будет оглушен на следующий ход + private stunDuration: number = 0; // Длительность оглушения (в ходах атакующего игрока) + private skipNextTurn: boolean = false; // Флаг для пропуска следующего хода + + constructor(name: string, strength: number, health: number) { + this.name = name; + this.strength = strength; + this.health = health; + this.maxHealth = health; // Устанавливаем максимальное здоровье + this.isDead = false; + } + + // Обновление состояния оглушения + public updateStun(duration: number = 0): void { + if (duration > 0) { + this.stunDuration = duration; + this.isStunned = true; + } else if (this.stunDuration > 0) { + this.stunDuration--; + if (this.stunDuration === 0) { + this.isStunned = false; // Сбрасываем оглушение + } + } + } + + // Метод для применения эффекта оглушения + public applyStunEffect(): void { + this.updateStun(); // Обновляем состояние оглушения + } + + // Метод для установки длительности оглушения + public setStunDuration(duration: number): void { + this.stunDuration = duration; + this.isStunned = duration > 0; + } + + // Метод для установки состояния оглушения + public setIsStunned(isStunned: boolean): void { + this.isStunned = isStunned; + } + + public setSkipNextTurn(skip: boolean): void { + this.skipNextTurn = skip; + } + + public getSkipNextTurn(): boolean { + return this.skipNextTurn; + } + + public setWillBeStunned(willBeStunned: boolean): void { + this.willBeStunned = willBeStunned; + } + + public getWillBeStunned(): boolean { + return this.willBeStunned; + } + + public isCurrentlyStunned(): boolean { + return this.stunDuration > 0; + } + + // Восстановление здоровья до максимального значения (только для живых бойцов) + public restoreHealth(): void { + if (!this.isDead) { + this.health = this.maxHealth; + } + } + + // Устанавливаем выбранную способность + public setSelectedAbility(ability: string | null): void { + if (ability && this.abilities.includes(ability)) { + this.selectedAbility = ability; + } else if (ability === null) { + this.selectedAbility = null; + } else { + throw new Error('Недопустимая способность'); + } + } + + // Получаем выбранную способность + public getSelectedAbility(): string | null { + return this.selectedAbility; + } + + // Геттер для abilities + public getAbilities(): string[] { + return this.abilities; + } + + public getIsDead(): boolean { + return this.isDead; + } + + public setIsDead(isDead: boolean): void { + this.isDead = isDead; + } + + public getName(): string { + return this.name; + } + + public setName(name: string): void { + this.name = name; + } + + public getStrength(): number { + return this.strength; + } + + public setStrength(strength: number): void { + if (strength >= 0) { + this.strength = strength; + } else { + throw new Error('Strength cannot have negative values'); + } + } + + public getHealth(): number { + return this.health; + } + + public setHealth(health: number): void { + if (health >= 0) { + this.health = health; + } else { + throw new Error('Health cannot have negative values'); + } + } + + public displayInfo(): void { + const logs = new Logs(); + logs.log(`\nStrength: ${this.getStrength()}`); + logs.log(`Name: ${this.getName()}`); + logs.log(`Health: ${this.getHealth()}`); + } + + // Метод для проверки, оглушен ли игрок + public getIsStunned(): boolean { + return this.isStunned; + } + + // Изменяем метод takeDamage, чтобы учитывать оглушение + public takeDamage(damage: number): void { + this.health = Math.max(0, Math.round(this.health - damage)); // Округляем и ограничиваем здоровье + if (this.health <= 0) { + this.isDead = true; + } + } + + public attack(target: Player, logs: Logs): void { + const damage = this.strength; + target.takeDamage(damage); + logs.logAttack(this, target, damage); // Логируем атаку + } + + public useAbility(target: Player, logs: Logs): void { + // Базовый класс не имеет способностей + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/laba3Gameplay.ts b/rpgsaga/saga/src/laba3Gameplay.ts new file mode 100644 index 00000000..f34ab467 --- /dev/null +++ b/rpgsaga/saga/src/laba3Gameplay.ts @@ -0,0 +1,165 @@ +import { Player } from './laba3CreatingPlayer'; +import { Mage} from './classes/laba3Mage'; +import { Knight} from './classes/laba3Knight'; +import { Archer} from './classes/laba3Archer'; +import { Logs } from './laba3Logs'; + +export class Game { + private players: Player[] = []; + private logs: Logs; + + constructor(players: Player[]) { + this.logs = new Logs(); + this.players = players; + } + + // Отображение списка игроков + private displayPlayersInfo(): void { + this.logs.log('\nСписок игроков:'); + this.players.forEach((player, index) => { + const ability = player.getSelectedAbility() || 'Нет способности'; + this.logs.log( + `${index + 1}. ${player.getName()} (${player.constructor.name}): Здоровье = ${player.getHealth()}, Сила = ${player.getStrength()}, Способность = ${ability}`, + ); + }); + this.logs.log('\n'); + } + + // Начало игры + public start(): void { + this.displayPlayersInfo(); // Отображаем список игроков перед началом боя + + while (this.players.length > 1) { + this.logs.log(`\nКон ${this.logs.getTour() + 1}`); + this.fight(); + this.logs.setTour(this.logs.getTour() + 1); + } + this.logs.logWinner(this.players[0]); + this.logs.logChampion(this.players[0]); + } + + // Проведение раунда боя + private fight(): void { + // Восстанавливаем здоровье всех живых бойцов перед началом раунда + this.players.forEach(player => { + player.restoreHealth(); + if (player instanceof Mage) { + (player as Mage).resetAbilityUsed(); + } else if (player instanceof Knight) { + (player as Knight).resetAbilityUsed(); + } else if (player instanceof Archer) { + (player as Archer).resetFireArrowsUsed(); + (player as Archer).resetIceArrowsUsed(); + } + + // Выбираем случайную способность для персонажа, если они есть + const abilities = player.getAbilities(); + if (abilities.length > 0) { + const selectedAbility = abilities[Math.floor(Math.random() * abilities.length)]; + player.setSelectedAbility(selectedAbility); + } + }); + + let shuffledPlayers = this.shuffleArray(this.players); + + // Проверяем, чтобы в парах не было одинаковых персонажей + for (let i = 0; i < shuffledPlayers.length; i += 2) { + const player1 = shuffledPlayers[i]; + const player2 = shuffledPlayers[i + 1]; + if (player1 && player2 && player1.constructor.name === player2.constructor.name) { + // Если типы одинаковые, меняем местами с следующим игроком + if (i + 2 < shuffledPlayers.length) { + [shuffledPlayers[i + 1], shuffledPlayers[i + 2]] = [shuffledPlayers[i + 2], shuffledPlayers[i + 1]]; + } + } + } + + // Проводим бои + for (let i = 0; i < shuffledPlayers.length; i += 2) { + const player1 = shuffledPlayers[i]; + const player2 = shuffledPlayers[i + 1]; + if (player1 && player2) { + this.logs.logFight(player1, player2, 'vs'); + this.battle(player1, player2); + } + } + this.players = this.players.filter(player => !player.getIsDead()); + } + + // Бой между двумя игроками + private battle(player1: Player, player2: Player): void { + let isPlayer1Turn = true; // Флаг для отслеживания, чей ход + + while (!player1.getIsDead() && !player2.getIsDead()) { + // Применяем эффекты способностей для обоих игроков + if (player1 instanceof Archer) { + (player1 as Archer).applyAbilityEffects(player2, this.logs); + } + if (player2 instanceof Archer) { + (player2 as Archer).applyAbilityEffects(player1, this.logs); + } + + // Применяем эффект оглушения для обоих игроков + player1.applyStunEffect(); + player2.applyStunEffect(); + + if (isPlayer1Turn) { + // Ход игрока 1 + if (player1.getSkipNextTurn()) { + this.logs.log(`${player1.getName()} пропускает ход из-за оглушения.`); + player1.setSkipNextTurn(false); // Сбрасываем флаг после пропуска хода + } else { + this.makeMove(player1, player2); // Игрок 1 атакует + // Если игрок 2 оглушен, он пропустит следующий ход + if (player2.isCurrentlyStunned()) { + player2.setSkipNextTurn(true); + } + } + } else { + // Ход игрока 2 + if (player2.getSkipNextTurn()) { + this.logs.log(`${player2.getName()} пропускает ход из-за оглушения.`); + player2.setSkipNextTurn(false); // Сбрасываем флаг после пропуска хода + } else { + this.makeMove(player2, player1); // Игрок 2 атакует + // Если игрок 1 оглушен, он пропустит следующий ход + if (player1.isCurrentlyStunned()) { + player1.setSkipNextTurn(true); + } + } + } + + // Переключение хода + isPlayer1Turn = !isPlayer1Turn; + } + + // Логирование результата боя + if (player1.getIsDead()) { + this.logs.logLoser(player1); + this.logs.logWinner(player2); + } else { + this.logs.logLoser(player2); + this.logs.logWinner(player1); + } + } + private shuffleArray(array: any[]): any[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + } + private makeMove(attacker: Player, target: Player): void { + if (Math.random() < 0.5) { + attacker.attack(target, this.logs); // Обычная атака + } else { + const selectedAbility = attacker.getSelectedAbility(); + if (selectedAbility && attacker.getAbilities().includes(selectedAbility)) { + attacker.useAbility(target, this.logs); // Использование способности + attacker.setSelectedAbility(null); // Сбрасываем выбранную способность после использования + } else { + attacker.attack(target, this.logs); // Если способность не выбрана или недопустима, атакуем + } + } + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/laba3HeroFactory.ts b/rpgsaga/saga/src/laba3HeroFactory.ts new file mode 100644 index 00000000..1cada557 --- /dev/null +++ b/rpgsaga/saga/src/laba3HeroFactory.ts @@ -0,0 +1,58 @@ +import { Player } from './laba3CreatingPlayer'; +import { Mage } from './classes/laba3Mage'; +import { Knight } from './classes/laba3Knight'; +import { Archer } from './classes/laba3Archer'; + +export class HeroGeneration { + static Herocreate(type: string, name: string, strength: number, health: number): Player { + // Проверка и корректировка значений силы и здоровья + switch (type) { + case 'Knight': + strength = Math.max(20, Math.min(100, strength)); + health = Math.max(80, Math.min(200, health)); + return new Knight(name, strength, health); + case 'Mage': + strength = Math.max(10, Math.min(50, strength)); + health = Math.max(50, Math.min(150, health)); + return new Mage(name, strength, health); + case 'Archer': + strength = Math.max(15, Math.min(70, strength)); + health = Math.max(60, Math.min(180, health)); + return new Archer(name, strength, health); + default: + throw new Error('Unknown hero type'); + } + } + + static createRandomHeroes(count: number): Player[] { + const heroes: Player[] = []; + const types = ['Knight', 'Mage', 'Archer']; + for (let i = 0; i < count; i++) { + const type = types[Math.floor(Math.random() * types.length)]; + const name = `Hero${i + 1}`; + let strength: number; + let health: number; + + // Генерация случайных значений в пределах допустимых для каждого класса + switch (type) { + case 'Knight': + strength = Math.floor(Math.random() * 81) + 20; // 20-100 + health = Math.floor(Math.random() * 121) + 80; // 80-200 + break; + case 'Mage': + strength = Math.floor(Math.random() * 41) + 10; // 10-50 + health = Math.floor(Math.random() * 101) + 50; // 50-150 + break; + case 'Archer': + strength = Math.floor(Math.random() * 56) + 15; // 15-70 + health = Math.floor(Math.random() * 121) + 60; // 60-180 + break; + default: + throw new Error('Unknown hero type'); + } + + heroes.push(this.Herocreate(type, name, strength, health)); + } + return heroes; + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/laba3Heroes.ts b/rpgsaga/saga/src/laba3Heroes.ts new file mode 100644 index 00000000..3bed748a --- /dev/null +++ b/rpgsaga/saga/src/laba3Heroes.ts @@ -0,0 +1,132 @@ +import { Player } from './laba3CreatingPlayer'; +import { Logs } from './laba3Logs'; + +export class Mage extends Player { + abilities: string[] = ['Оглушение', 'Лечение']; + private abilityUsed: boolean = false; + public resetAbilityUsed(): void { + this.abilityUsed = false; + } + useAbility(target: Player, logs: Logs): void { + + const ability = this.getSelectedAbility(); // Используем выбранную способность + if (!ability) return; + + if (ability === 'Оглушение' && !this.abilityUsed) { + target.setStunDuration(2); // Устанавливаем длительность оглушения на 2 хода + target.setIsStunned(true); // Применяем оглушение + this.abilityUsed = true; + logs.logAbility(this, ability, target, 'будет оглушен на 2 хода'); + + // Маг сразу атакует после применения оглушения + this.attack(target, logs); + } else if (ability === 'Лечение') { + const healAmount = 20; + this.setHealth(this.getHealth() + healAmount); + logs.logHeal(this, healAmount); // Логируем лечение + } + } + + takeDamage(damage: number): void { + if (this.isStunned) { // Теперь доступ к isStunned разрешён + console.log(`${this.getName()} пропускает ход.`); + this.isStunned = false; + } else { + super.takeDamage(damage); + } + } +} + +export class Knight extends Player { + abilities: string[] = ['Короночка']; + private abilityUsed: boolean = false; + public resetAbilityUsed(): void { + this.abilityUsed = false; + } + useAbility(target: Player, logs: Logs): void { + const ability = this.getSelectedAbility(); // Используем выбранную способность + if (!ability) return; + + if (ability === 'Короночка' && !this.abilityUsed) { + const damage = Math.round(this.getStrength() * 1.5); // Увеличиваем урон + target.takeDamage(damage); + this.abilityUsed = true; + logs.logAbility(this, ability, target, `который получает ${damage} урона`); // Логируем атаку + } + } +} + +export class Archer extends Player { + abilities: string[] = ['Огненные стрелы', 'Ледяные стрелы']; + private fireArrowsUsed: boolean = false; + private iceArrowsUsed: number = 0; + private fireArrowsDuration: number = 0; + private iceArrowsDuration: number = 0; + + public resetFireArrowsUsed(): void { + this.fireArrowsUsed = false; + } + + public resetIceArrowsUsed(): void { + this.iceArrowsUsed = 0; + } + + constructor(name: string, strength: number, health: number) { + super(name, strength, health); + } + + useAbility(target: Player, logs: Logs): void { + const ability = this.getSelectedAbility(); // Используем выбранную способность + if (!ability) return; + + // Наносим базовый урон (обычная атака) + const baseDamage = this.getStrength(); + target.takeDamage(baseDamage); + + // Логируем использование способности и базовый урон + logs.logAbility( + this, + ability, + target, + `и наносит ${baseDamage} основного урона. ${target.getName()} теряет 3 единицы жизни следующие 2 хода`, + ); + + if (ability === 'Огненные стрелы' && !this.fireArrowsUsed) { + // Активируем эффект огненных стрел на 2 хода + this.fireArrowsUsed = true; + this.fireArrowsDuration = 2; // Устанавливаем длительность эффекта на 2 хода + } else if (ability === 'Ледяные стрелы' && this.iceArrowsUsed < 2) { + // Активируем эффект ледяных стрел на 2 хода + this.iceArrowsUsed++; + this.iceArrowsDuration = 2; // Устанавливаем длительность эффекта на 2 хода + } + } + + applyAbilityEffects(target: Player, logs: Logs): void { + // Применяем урон от огненных стрел, если они активны + if (this.fireArrowsUsed && this.fireArrowsDuration > 0) { + const fireDamage = 3; + target.takeDamage(fireDamage); + logs.log( + `${target.getName()} теряет ${fireDamage} единиц жизни из-за Огненных стрел. Текущее здоровье: ${target.getHealth()}`, + ); + this.fireArrowsDuration--; + if (this.fireArrowsDuration === 0) { + this.fireArrowsUsed = false; + } + } + + // Применяем урон от ледяных стрел, если они активны + if (this.iceArrowsDuration > 0) { + const iceDamage = 3; + target.takeDamage(iceDamage); + logs.log( + `${target.getName()} теряет ${iceDamage} единиц жизни из-за Ледяных стрел. Текущее здоровье: ${target.getHealth()}`, + ); + this.iceArrowsDuration--; + if (this.iceArrowsDuration === 0) { + this.iceArrowsUsed = 0; + } + } + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/laba3Logs.ts b/rpgsaga/saga/src/laba3Logs.ts new file mode 100644 index 00000000..c9fad50d --- /dev/null +++ b/rpgsaga/saga/src/laba3Logs.ts @@ -0,0 +1,83 @@ +import { Player } from './laba3CreatingPlayer'; +import * as fs from 'fs'; + +export class Logs { + private Tour: number = 0; + private logFile: string = 'game_log.txt'; + + constructor() { + // Очищаем файл логов при создании нового логгера + fs.writeFileSync(this.logFile, ''); + } + + public setTour(Tour: number): void { + this.Tour = Tour; + } + + public getTour(): number { + return this.Tour; + } + + public log(message: string): void { + console.log(message); // Вывод в консоль + fs.appendFileSync(this.logFile, message + '\n'); // Запись в файл + } + + // Логирование боя с отображением здоровья + public logFight(player1: Player, player2: Player, action: string): void { + if (player1 && player2) { + this.log( + `(${player1.constructor.name}: ${player1.getHealth()} здоровья) ${player1.getName()} ${action} (${player2.constructor.name}: ${player2.getHealth()} здоровья) ${player2.getName()}`, + ); + } + } + + // Логирование атаки с отображением урона + public logAttack(attacker: Player, target: Player, damage: number): void { + this.log( + `(${attacker.constructor.name}: ${attacker.getHealth()} здоровья) ${attacker.getName()} наносит ${damage} урона (${target.constructor.name}: ${target.getHealth()} здоровья) ${target.getName()}`, + ); + } + + // Логирование лечения + public logHeal(player: Player, healAmount: number): void { + this.log( + `(${player.constructor.name}: ${player.getHealth()} здоровья) ${player.getName()} восстанавливает ${healAmount} здоровья`, + ); + } + + public logWinner(winner: Player): void { + this.log(`Победитель: (${winner.constructor.name}: ${winner.getHealth()} здоровья) ${winner.getName()}`); + this.log('\n'); + } + + public logLoser(loser: Player): void { + this.log('\n'); + this.log(`Проигравший: (${loser.constructor.name}: ${loser.getHealth()} здоровья) ${loser.getName()}`); + } + + public logChampion(champion: Player): void { + this.log( + `И чемпионом турнира становится: ${champion.constructor.name} (${champion.getName()}: ${champion.getHealth()} здоровья). Поздравляем!`, + ); + this.log('\n'); + } + + public logAbility(player: Player, ability: string, target: Player, effect: string): void { + this.log( + `(${player.constructor.name}: ${player.getHealth()} здоровья) ${player.getName()} использует ${ability} на игрока (${target.constructor.name}: ${target.getHealth()} здоровья) ${target.getName()} ${effect}.`, + ); + } + + public logSkipTurn(player: Player): void { + this.log(`${player.getName()} пропускает ход из-за оглушения.`); + } + + public logStunEffect(player: Player, duration: number): void { + this.log(`${player.getName()} будет оглушен на ${duration} хода.`); + } + + public logDamageOverTime(target: Player, damage: number, effect: string): void { + this.log(`${target.getName()} теряет ${damage} единиц жизни из-за ${effect}. Текущее здоровье: ${target.getHealth()}`); + } +} \ No newline at end of file diff --git a/rpgsaga/saga/src/laba3Optionals.ts b/rpgsaga/saga/src/laba3Optionals.ts new file mode 100644 index 00000000..440b30f3 --- /dev/null +++ b/rpgsaga/saga/src/laba3Optionals.ts @@ -0,0 +1,190 @@ +import { Player } from './laba3CreatingPlayer'; +import { Logs } from './laba3Logs'; +import { HeroGeneration } from './laba3HeroFactory'; +import { Mage } from './classes/laba3Mage'; +import { Knight } from './classes/laba3Knight'; +import { Archer } from './classes/laba3Archer'; + +export class Optional { + private logs: Logs = new Logs(); + + // Массивы имен для каждого класса + private knightNames = ['Гигачадус', 'Данила', 'Даниил', 'Данил', 'Метрофан', 'Рубака', 'Sans', 'Олегадруг']; + private MageNames = ['Погорелый', 'Пришибленый', 'Магнус', 'Зевс', 'Добби', 'Йеннифер', 'Волшебник']; + private archerNames = ['Робин', 'Бобин', 'Толик', 'Болик', 'Тетева', 'Олег']; + + // Метод для создания игроков вручную + public async createPlayersManually( + numberOfPlayers: number, + askQuestion: (question: string) => Promise, + ): Promise { + const players: Player[] = []; + for (let i = 0; i < numberOfPlayers; i++) { + const player = await this.createPlayerManually(askQuestion); + if (player) { + players.push(player); + } else { + // Если пользователь отказался от создания игрока, заполняем оставшиеся места случайными игроками + const remainingPlayers = numberOfPlayers - players.length; + const randomPlayers = this.createRandomPlayers(remainingPlayers); + players.push(...randomPlayers); + break; + } + } + return players; + } + + // Метод для создания одного игрока вручную + private async createPlayerManually( + askQuestion: (question: string) => Promise, + ): Promise { + const name = await askQuestion('Введите имя игрока: '); + if (!name) return null; + + const classChoice = await askQuestion('Выберите класс (1 - Knight, 2 - Mage, 3 - Archer): '); + let playerClass: string; + switch (classChoice) { + case '1': + playerClass = 'Knight'; + break; + case '2': + playerClass = 'Mage'; + break; + case '3': + playerClass = 'Archer'; + break; + default: + this.logs.log('Неверный выбор класса. Игрок будет создан случайным образом.'); + return null; + } + + let strengthInput = await askQuestion('Введите силу игрока: '); + let strength = parseInt(strengthInput, 10); + if (isNaN(strength)) { + this.logs.log('Некорректное значение силы. Установлено значение по умолчанию.'); + strength = this.getDefaultStrength(playerClass); + } + + let healthInput = await askQuestion('Введите здоровье игрока: '); + let health = parseInt(healthInput, 10); + if (isNaN(health)) { + this.logs.log('Некорректное значение здоровья. Установлено значение по умолчанию.'); + health = this.getDefaultHealth(playerClass); + } + + // Проверка и корректировка значений силы и здоровья + switch (playerClass) { + case 'Knight': + strength = Math.max(20, Math.min(100, strength)); + health = Math.max(80, Math.min(200, health)); + break; + case 'Mage': + strength = Math.max(10, Math.min(50, strength)); + health = Math.max(50, Math.min(150, health)); + break; + case 'Archer': + strength = Math.max(15, Math.min(70, strength)); + health = Math.max(60, Math.min(180, health)); + break; + } + + const player = HeroGeneration.Herocreate(playerClass, name, strength, health); + + const abilities = player.getAbilities(); + if (abilities.length > 0) { + const abilityChoice = await askQuestion(`Выберите способность (${abilities.join(', ')}): `); + if (abilities.includes(abilityChoice)) { + player.setSelectedAbility(abilityChoice); + } else { + this.logs.log('Неверный выбор способности. Способность будет выбрана случайным образом.'); + player.setSelectedAbility(abilities[Math.floor(Math.random() * abilities.length)]); + } + } + + return player; + } + + private getDefaultStrength(playerClass: string): number { + switch (playerClass) { + case 'Knight': + return 60; // Среднее значение для рыцаря + case 'Mage': + return 30; // Среднее значение для мага + case 'Archer': + return 40; // Среднее значение для лучника + default: + return 50; // Значение по умолчанию + } + } + + private getDefaultHealth(playerClass: string): number { + switch (playerClass) { + case 'Knight': + return 140; // Среднее значение для рыцаря + case 'Mage': + return 100; // Среднее значение для мага + case 'Archer': + return 120; // Среднее значение для лучника + default: + return 100; // Значение по умолчанию + } + } + + // Метод для создания случайных игроков + public createRandomPlayers(numberOfPlayers: number): Player[] { + const players: Player[] = []; + const existingNames = new Set(); // Для уникальности имен + + for (let i = 0; i < numberOfPlayers; i++) { + const playerType = Math.floor(Math.random() * 3); // Случайный тип игрока + let name: string; + let player: Player; + + // Выбираем случайное имя в зависимости от типа игрока + switch (playerType) { + case 0: // Knight + name = this.generateUniqueName(this.knightNames, existingNames); + player = new Knight(name, Math.max(20, Math.min(100, Math.floor(Math.random() * 50) + 1)), Math.max(80, Math.min(200, Math.floor(Math.random() * 100) + 50))); + break; + case 1: // Mage + name = this.generateUniqueName(this.MageNames, existingNames); + player = new Mage(name, Math.max(10, Math.min(50, Math.floor(Math.random() * 50) + 1)), Math.max(50, Math.min(150, Math.floor(Math.random() * 100) + 50))); + break; + case 2: // Archer + name = this.generateUniqueName(this.archerNames, existingNames); + player = new Archer(name, Math.max(15, Math.min(70, Math.floor(Math.random() * 50) + 1)), Math.max(60, Math.min(180, Math.floor(Math.random() * 100) + 50))); + break; + default: + throw new Error('Неизвестный тип персонажа'); + } + + // Выбираем случайную способность для персонажа, если они есть + const abilities = player.getAbilities(); + if (abilities.length > 0) { + const selectedAbility = abilities[Math.floor(Math.random() * abilities.length)]; + player.setSelectedAbility(selectedAbility); + } + + players.push(player); + } + + return players; + } + + // Генерация уникального имени + private generateUniqueName(baseNames: string[], existingNames: Set): string { + let name = baseNames[Math.floor(Math.random() * baseNames.length)]; // Выбираем случайное имя + let uniqueName = name; + let index = 1; + + // Если имя уже существует, добавляем индекс + while (existingNames.has(uniqueName)) { + uniqueName = `${name}${index}`; + index++; + } + + // Добавляем новое имя в список существующих + existingNames.add(uniqueName); + return uniqueName; + } +} \ No newline at end of file diff --git a/rpgsaga/saga/tests/Tesrlaba1.spec.ts b/rpgsaga/saga/tests/test_for_labs/Tesrlaba1.spec.ts similarity index 95% rename from rpgsaga/saga/tests/Tesrlaba1.spec.ts rename to rpgsaga/saga/tests/test_for_labs/Tesrlaba1.spec.ts index 5658bb2b..41d61ae6 100644 --- a/rpgsaga/saga/tests/Tesrlaba1.spec.ts +++ b/rpgsaga/saga/tests/test_for_labs/Tesrlaba1.spec.ts @@ -1,4 +1,4 @@ -import { taskA, taskB, calc } from "../src/laba1"; +import { taskA, taskB, calc } from "../../src/lab1and2/laba1"; describe('calc function', () => { diff --git a/rpgsaga/saga/tests/Testlaba2.spec.ts b/rpgsaga/saga/tests/test_for_labs/Testlaba2.spec.ts similarity index 96% rename from rpgsaga/saga/tests/Testlaba2.spec.ts rename to rpgsaga/saga/tests/test_for_labs/Testlaba2.spec.ts index 7c851efe..850ceb81 100644 --- a/rpgsaga/saga/tests/Testlaba2.spec.ts +++ b/rpgsaga/saga/tests/test_for_labs/Testlaba2.spec.ts @@ -1,4 +1,4 @@ -import { Car } from "../src/laba2"; +import { Car } from "../../src/lab1and2/laba2"; describe('Car Class Tests', () => { diff --git a/rpgsaga/saga/tests/testforsaga/test_for_icearrows.spec.ts b/rpgsaga/saga/tests/testforsaga/test_for_icearrows.spec.ts new file mode 100644 index 00000000..d74c860a --- /dev/null +++ b/rpgsaga/saga/tests/testforsaga/test_for_icearrows.spec.ts @@ -0,0 +1,29 @@ +import { Archer } from '../../src/classes/laba3Archer'; +import { Player } from '../../src/laba3CreatingPlayer'; +import { Logs } from '../../src/laba3Logs'; + +jest.mock('./laba3Logs'); + +test('Ice Arrows core functionality', () => { + const archer = new Archer('Толик', 20, 100); + const target = new Player('Orc', 150, 25); + + // Проверка использования способности + archer.setSelectedAbility('Ледяные стрелы'); + archer.useAbility(target, new Logs()); + archer.useAbility(target, new Logs()); + + // Лимит использований и базовый урон + expect(archer['iceArrowsUsed']).toBe(2); + expect(target.getHealth()).toBe(150 - 20*2); + + // Проверка периодического урона + Array.from({length: 3}).forEach((_, i) => { + archer.applyAbilityEffects(target, new Logs()); + expect(target.getHealth()).toBe(150 - 40 - 3*(i+1)); + }); + + // Сброс состояния + archer.resetIceArrowsUsed(); + expect(archer['iceArrowsUsed'] + archer['iceArrowsDuration']).toBe(0); +}); \ No newline at end of file diff --git a/rpgsaga/saga/tests/testforsaga/test_for_knight.spec.ts b/rpgsaga/saga/tests/testforsaga/test_for_knight.spec.ts new file mode 100644 index 00000000..4aa4d4e0 --- /dev/null +++ b/rpgsaga/saga/tests/testforsaga/test_for_knight.spec.ts @@ -0,0 +1,85 @@ +import { Knight } from '../../src/classes/laba3Knight'; +import { Player } from '../../src/laba3CreatingPlayer'; +import { Logs } from '../../src/laba3Logs'; + +jest.mock('./laba3Logs'); + +describe('Knight', () => { + let knight: Knight; + let target: Player; + let mockLogs: jest.Mocked; + + beforeEach(() => { + mockLogs = new Logs() as jest.Mocked; + knight = new Knight('Олегадруг', 200, 30); + target = new Player('Enemy', 200, 30); + }); + + it('should initialize with valid parameters', () => { + expect(knight.getHealth()).toBeGreaterThanOrEqual(80); + expect(knight.getHealth()).toBeLessThanOrEqual(200); + expect(knight.getStrength()).toBeGreaterThanOrEqual(20); + expect(knight.getStrength()).toBeLessThanOrEqual(100); + expect(knight.getAbilities()).toEqual(['Короночка']); + }); + + describe('Ability Usage', () => { + it('should deal 1.5x damage with Короночка', () => { + knight.setStrength(40); + knight.setSelectedAbility('Короночка'); + + const initialHealth = target.getHealth(); + knight.useAbility(target, mockLogs); + + const expectedDamage = 40 * 1.5; + expect(target.getHealth()).toBe(initialHealth - expectedDamage); + expect(mockLogs.logAbility).toHaveBeenCalledWith( + knight, + 'Короночка', + target, + `который получает ${expectedDamage} урона` + ); + }); + + it('should prevent ability reuse without reset', () => { + knight.setSelectedAbility('Короночка'); + knight.useAbility(target, mockLogs); + const afterFirstUse = target.getHealth(); + + // Попытка повторного использования + knight.useAbility(target, mockLogs); + expect(target.getHealth()).toBe(afterFirstUse); // Здоровье не изменилось + }); + }); + + describe('Stat Validation', () => { + it('should throw error for invalid strength', () => { + expect(() => knight.setStrength(19)).toThrow('Strength for Knight must be between 20 and 100'); + expect(() => knight.setStrength(101)).toThrow('Strength for Knight must be between 20 and 100'); + }); + + it('should throw error for invalid health', () => { + expect(() => knight.setHealth(79)).toThrow('Health for Knight must be between 80 and 200'); + expect(() => knight.setHealth(201)).toThrow('Health for Knight must be between 80 and 200'); + }); + }); + + describe('Ability Reset', () => { + it('should reset ability cooldown', () => { + knight.setSelectedAbility('Короночка'); + knight.useAbility(target, mockLogs); + knight.resetAbilityUsed(); + + const healthAfterFirstHit = target.getHealth(); + knight.useAbility(target, mockLogs); + + expect(target.getHealth()).toBeLessThan(healthAfterFirstHit); + }); + }); + + it('should clear selected ability after use', () => { + knight.setSelectedAbility('Короночка'); + knight.useAbility(target, mockLogs); + expect(knight.getSelectedAbility()).toBeNull(); + }); +}); \ No newline at end of file diff --git a/rpgsaga/saga/tests/testforsaga/test_for_mag.spec.ts b/rpgsaga/saga/tests/testforsaga/test_for_mag.spec.ts new file mode 100644 index 00000000..78b76cd5 --- /dev/null +++ b/rpgsaga/saga/tests/testforsaga/test_for_mag.spec.ts @@ -0,0 +1,46 @@ +import { Mage } from '../../src/classes/laba3Mage'; +import { Player } from '../../src/laba3CreatingPlayer'; +import { Logs } from '../../src/laba3Logs'; + +jest.mock('./laba3Logs'); + +describe('Mage Short Tests', () => { + let mage: Mage; + let target: Player; + const mockLogs = new Logs() as jest.Mocked; + + beforeEach(() => { + mage = new Mage('Погорелый', 100, 20); + target = new Player('Enemy', 100, 20); + }); + + it('should correctly initialize with default values', () => { + expect(mage.getHealth()).toBeGreaterThanOrEqual(50); + expect(mage.getStrength()).toBeGreaterThanOrEqual(10); + expect(mage.getAbilities()).toEqual(['Оглушение', 'Лечение']); + }); + + it('should stun target and attack when using Оглушение', () => { + mage.setSelectedAbility('Оглушение'); + mage.useAbility(target, mockLogs); + + expect(target.isCurrentlyStunned()).toBe(true); + expect(target.getHealth()).toBeLessThan(100); // Проверка что атака прошла + expect(mockLogs.logAbility).toHaveBeenCalledWith( + mage, 'Оглушение', target, 'будет оглушен на 2 хода' + ); + }); + + it('should heal itself when using Лечение', () => { + mage.setHealth(80); + mage.setSelectedAbility('Лечение'); + mage.useAbility(target, mockLogs); + + expect(mage.getHealth()).toBe(100); + expect(mockLogs.logHeal).toHaveBeenCalledWith(mage, 20); + }); + + it('should throw error when setting invalid health', () => { + expect(() => mage.setHealth(200)).toThrow('Health for Mage must be between 50 and 150'); + }); +}); \ No newline at end of file diff --git a/rpgsaga/saga/tests/testforsaga/test_for_player.spec.ts b/rpgsaga/saga/tests/testforsaga/test_for_player.spec.ts new file mode 100644 index 00000000..722e0f09 --- /dev/null +++ b/rpgsaga/saga/tests/testforsaga/test_for_player.spec.ts @@ -0,0 +1,27 @@ +import { Player } from '../../src/laba3CreatingPlayer'; +import { Logs } from '../../src/laba3Logs'; + +describe('Player Class', () => { + let player: Player; + let logs: Logs; + + beforeEach(() => { + player = new Player('TestPlayer', 10, 100); + logs = new Logs(); + }); + + it('should reduce health by exact amount when taking damage', () => { + player.takeDamage(30); + expect(player.getHealth()).toBe(70); + }); + + it('should round damage values before subtracting', () => { + player.takeDamage(30.6); + expect(player.getHealth()).toBe(69); // 100 - 30.6 = 69.4 → округляется до 69 + }); + + it('should mark player as dead when health reaches zero', () => { + player.takeDamage(100); + expect(player.getIsDead()).toBe(true); + }); +}); \ No newline at end of file