diff --git a/rpgsaga/saga/src/game/game.ts b/rpgsaga/saga/src/game/game.ts new file mode 100644 index 0000000..a258e11 --- /dev/null +++ b/rpgsaga/saga/src/game/game.ts @@ -0,0 +1,103 @@ +import { Archer } from '../hero/archer'; +import { Hero } from '../hero/hero'; +import { Knight } from '../hero/knight'; +import { Mage } from '../hero/mage'; +import { Logger } from '../logger/logger'; + +const MAX_HEALTH = 200; +const MAX_STRENGTH = 20; + +const heroes = [Archer, Knight, Mage]; +const names = ['Artem', 'Sergey', 'Anna', 'Oksana', 'Yulya', 'Aleksey', 'German', 'Kseniya']; + +function getRandomInt(max: number) { + return Math.floor(Math.random() * max); +} + +export class Game { + players: Hero[] = []; + logger: Logger; + + constructor(playersCount: number) { + this.createPlayers(playersCount); + this.logger = new Logger(); + } + + createPlayers(playersCount: number) { + const logger = new Logger(); + + for (let i = 0; i < playersCount; i++) { + const randomNum = getRandomInt(3); + const Hero = heroes[randomNum]; + + const nameIndex = getRandomInt(names.length); + const name = names[nameIndex]; + const health = getRandomInt(MAX_HEALTH); + const strength = getRandomInt(MAX_STRENGTH); + + this.players.push(new Hero(name, health, strength, logger)); + } + } + + startGame() { + while (this.players.length > 1) { + // перемешивание массива + this.players.sort(() => Math.random() - 0.5); + + for (let i = 0; i < Math.floor(this.players.length / 2); i++) { + const player1 = this.players[i]; + const player2 = this.players[i + 1]; + this.battle(player1, player2); + } + + this.players = this.players.filter(player => player.currentHealth > 0); + } + + this.logger.log(`${this.players[0].name} побеждает!`); + } + + battle(player1: Hero, player2: Hero) { + this.logger.log(`(${player1.heroType}) ${player1.name} vs (${player2.heroType}) ${player2.name}.`); + + player1.refreshHero(); + player2.refreshHero(); + + while (player1.currentHealth > 0 && player2.currentHealth > 0) { + if (Math.random() < 0.5) { + this.attackOpponent(player1, player2); + + if (player2.currentHealth > 0) { + this.attackOpponent(player2, player1); + } + } else { + this.attackOpponent(player2, player1); + + if (player1.currentHealth > 0) { + this.attackOpponent(player1, player2); + } + } + } + + if (player1.currentHealth <= 0) { + this.logger.log(`(${player1.heroType}) ${player1.name} погибает.`); + this.players = this.players.filter(player => player !== player1); + } else { + this.logger.log(`(${player2.heroType}) ${player2.name} погибает.`); + this.players = this.players.filter(player => player !== player2); + } + } + + attackOpponent(player1: Hero, player2: Hero) { + if (player1.canWalk) { + + if (!player1.usedAbility) { + player1.useAbility(player2); + } else { + player1.attack(player2); + } + + } else { + player1.canWalk = true; + } + } +} diff --git a/rpgsaga/saga/src/hero/archer.ts b/rpgsaga/saga/src/hero/archer.ts new file mode 100644 index 0000000..3274a6a --- /dev/null +++ b/rpgsaga/saga/src/hero/archer.ts @@ -0,0 +1,25 @@ +import { Logger } from '../logger/logger'; +import { Hero } from './hero'; + +export class Archer extends Hero { + heroType: string = 'Лучник'; + + constructor(name: string, health: number, strength: number, logger: Logger) { + super(name, health, strength, logger); + } + + useAbility(opponent: Hero) { + if (!this.usedAbility) { + this.usedAbility = true; + this.logger.log( + `(${this.heroType}) ${this.name} использует (Огненные стрелы) на игрока (${opponent.heroType}) ${opponent.name}, он загорается и теряет 2 единицы жизни.`, + ); + opponent.getDamage(2, this, false); + } + } + + attack(opponent: Hero) { + const strength = this.useAbility ? this.strength + 2 : this.strength; + opponent.getDamage(strength, this); + } +} diff --git a/rpgsaga/saga/src/hero/hero.ts b/rpgsaga/saga/src/hero/hero.ts new file mode 100644 index 0000000..9b4b873 --- /dev/null +++ b/rpgsaga/saga/src/hero/hero.ts @@ -0,0 +1,45 @@ +import { Logger } from '../logger/logger'; + +export abstract class Hero { + name: string; + fullHealth: number; + currentHealth: number; + strength: number; + usedAbility: boolean = false; + canWalk: boolean = true; + + // abstract - значение которое должно быть у наследников (archer, knight, mage) + abstract heroType: string; + logger: Logger; + + constructor(name: string, health: number, strength: number, logger: Logger) { + this.name = name; + this.fullHealth = health; + this.currentHealth = health; + this.strength = strength; + this.logger = logger; + } + + attack(opponent: Hero) { + opponent.getDamage(this.strength, this); + } + + getDamage(damage: number, opponent: Hero, showLog = true) { + this.currentHealth -= damage; + + if (showLog) { + this.logger.log( + `(${opponent.heroType}) ${opponent.name} наносит урон ${this.strength} противнику (${this.heroType}) ${this.name}.`, + ); + } + } + + refreshHero() { + this.currentHealth = this.fullHealth; + this.usedAbility = false; + this.canWalk = true; + } + + // abstract - метод класса который должен реализоваться у наследников (archer, knight, mage) + abstract useAbility(opponent: Hero): void; +} diff --git a/rpgsaga/saga/src/hero/knight.ts b/rpgsaga/saga/src/hero/knight.ts new file mode 100644 index 0000000..264055f --- /dev/null +++ b/rpgsaga/saga/src/hero/knight.ts @@ -0,0 +1,22 @@ +import { Logger } from '../logger/logger'; +import { Hero } from './hero'; + +export class Knight extends Hero { + heroType: string = 'Рыцарь'; + + constructor(name: string, health: number, strength: number, logger: Logger) { + super(name, health, strength, logger); + } + + useAbility(opponent: Hero) { + if (!this.usedAbility) { + this.usedAbility = true; + const damage = this.strength * 1.3; + + this.logger.log( + `(${this.heroType}) ${this.name} использует (Удар возмездия) и наносит урон ${damage} противнику (${opponent.heroType}) ${opponent.name}.`, + ); + opponent.getDamage(damage, this, false); + } + } +} diff --git a/rpgsaga/saga/src/hero/mage.ts b/rpgsaga/saga/src/hero/mage.ts new file mode 100644 index 0000000..36f159d --- /dev/null +++ b/rpgsaga/saga/src/hero/mage.ts @@ -0,0 +1,20 @@ +import { Logger } from '../logger/logger'; +import { Hero } from './hero'; + +export class Mage extends Hero { + heroType: string = 'Маг'; + + constructor(name: string, health: number, strength: number, logger: Logger) { + super(name, health, strength, logger); + } + + useAbility(opponent: Hero) { + if (!this.usedAbility) { + this.usedAbility = true; + this.logger.log( + `(${this.heroType}) ${this.name} использует (заворожение), противник (${opponent.heroType}) ${opponent.name} пропустит следующий ход.`, + ); + opponent.canWalk = false; + } + } +} diff --git a/rpgsaga/saga/src/index.ts b/rpgsaga/saga/src/index.ts index 7805559..2da6e53 100644 --- a/rpgsaga/saga/src/index.ts +++ b/rpgsaga/saga/src/index.ts @@ -1,16 +1,4 @@ -import { Phone } from './phone'; +import { Game } from './game/game'; -const first = new Phone('+7900-000 000 (123)', 1990, 'Телефон 1'); -first.year = 1998; - -first.year = -1998; -first.call('12345'); -first.endCall(); - -const second = new Phone('+799900000', -5); -// second.name = 'Телефон 2'; -console.log(second.year); -second.call('12345'); -second.endCall(); - -console.log(first, second, Phone.phoneCount); +const game = new Game(6); +game.startGame(); diff --git a/rpgsaga/saga/src/logger/logger.ts b/rpgsaga/saga/src/logger/logger.ts new file mode 100644 index 0000000..34ff67a --- /dev/null +++ b/rpgsaga/saga/src/logger/logger.ts @@ -0,0 +1,5 @@ +export class Logger { + log(message: string) { + console.log(message); + } +} diff --git a/rpgsaga/saga/tests/archer.spec.ts b/rpgsaga/saga/tests/archer.spec.ts new file mode 100644 index 0000000..4188a5f --- /dev/null +++ b/rpgsaga/saga/tests/archer.spec.ts @@ -0,0 +1,27 @@ +import { Archer } from '../src/hero/archer'; +import { Logger } from '../src/logger/logger'; + +const logger = new Logger(); +const archer = new Archer('archer', 50, 20, logger); +const opponent = new Archer('opponent', 80, 15, logger); + +describe('Archer', () => { + it('useAbility test', () => { + const logMock = jest.spyOn(logger, 'log'); + const initialHealth = opponent.currentHealth; + archer.useAbility(opponent); + + expect(opponent.currentHealth).toBe(initialHealth - 2); + expect(archer.usedAbility).toBe(true); + expect(logMock).toHaveBeenCalledWith( + '(Лучник) archer использует (Огненные стрелы) на игрока (Лучник) opponent, он загорается и теряет 2 единицы жизни.', + ); + }); + + it('attack test', () => { + const getDamageMock = jest.spyOn(opponent, 'getDamage'); + archer.attack(opponent); + + expect(getDamageMock).toHaveBeenCalledWith(archer.strength + 2, archer); + }); +}); diff --git a/rpgsaga/saga/tests/game.spec.ts b/rpgsaga/saga/tests/game.spec.ts new file mode 100644 index 0000000..63d7728 --- /dev/null +++ b/rpgsaga/saga/tests/game.spec.ts @@ -0,0 +1,30 @@ +import { Game } from '../src/game/game'; +import { Knight } from '../src/hero/knight'; +import { Logger } from '../src/logger/logger'; + +const logger = new Logger(); +const game = new Game(4); + +describe('Game', () => { + it('createPlayers test', () => { + expect(game.players.length).toBe(4); + }); + + it('battle test', () => { + const player1 = new Knight('player1', 200, 20, logger); + const player2 = new Knight('player2', 100, 10, logger); + + const logMock = jest.spyOn(game.logger, 'log'); + const useAbilityMock = jest.spyOn(player1, 'useAbility'); + const attackMock = jest.spyOn(player1, 'attack'); + + game.battle(player1, player2); + + expect(useAbilityMock).toHaveBeenCalled(); + expect(attackMock).toHaveBeenCalled(); + expect(logMock).toHaveBeenCalledWith(`(Рыцарь) player2 погибает.`); + expect(logMock).toHaveBeenCalledWith('(Рыцарь) player1 vs (Рыцарь) player2.'); + + expect(game.players).not.toContain(player2); + }); +}); diff --git a/rpgsaga/saga/tests/knight.spec.ts b/rpgsaga/saga/tests/knight.spec.ts new file mode 100644 index 0000000..4fa1277 --- /dev/null +++ b/rpgsaga/saga/tests/knight.spec.ts @@ -0,0 +1,20 @@ +import { Knight } from '../src/hero/knight'; +import { Logger } from '../src/logger/logger'; + +const logger = new Logger(); +const knight = new Knight('knight', 50, 20, logger); +const opponent = new Knight('opponent', 80, 15, logger); + +describe('Knight', () => { + it('useAbility test', () => { + const logMock = jest.spyOn(logger, 'log'); + const initialHealth = opponent.currentHealth; + knight.useAbility(opponent); + + expect(knight.usedAbility).toBe(true); + expect(logMock).toHaveBeenCalledWith( + `(Рыцарь) knight использует (Удар возмездия) и наносит урон 26 противнику (Рыцарь) opponent.`, + ); + expect(opponent.currentHealth).toBe(initialHealth - knight.strength * 1.3); + }); +}); diff --git a/rpgsaga/saga/tests/mage.spec.ts b/rpgsaga/saga/tests/mage.spec.ts new file mode 100644 index 0000000..0b1afcf --- /dev/null +++ b/rpgsaga/saga/tests/mage.spec.ts @@ -0,0 +1,19 @@ +import { Mage } from '../src/hero/mage'; +import { Logger } from '../src/logger/logger'; + +const logger = new Logger(); +const mage = new Mage('mage', 50, 20, logger); +const opponent = new Mage('opponent', 80, 15, logger); + +describe('Mage', () => { + it('useAbility test', () => { + const logMock = jest.spyOn(logger, 'log'); + mage.useAbility(opponent); + + expect(opponent.canWalk).toBe(false); + expect(mage.usedAbility).toBe(true); + expect(logMock).toHaveBeenCalledWith( + `(Маг) mage использует (заворожение), противник (Маг) opponent пропустит следующий ход.`, + ); + }); +});