Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions rpgsaga/saga/src/game/game.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
25 changes: 25 additions & 0 deletions rpgsaga/saga/src/hero/archer.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
45 changes: 45 additions & 0 deletions rpgsaga/saga/src/hero/hero.ts
Original file line number Diff line number Diff line change
@@ -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;
}
22 changes: 22 additions & 0 deletions rpgsaga/saga/src/hero/knight.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
20 changes: 20 additions & 0 deletions rpgsaga/saga/src/hero/mage.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
18 changes: 3 additions & 15 deletions rpgsaga/saga/src/index.ts
Original file line number Diff line number Diff line change
@@ -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();
5 changes: 5 additions & 0 deletions rpgsaga/saga/src/logger/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class Logger {
log(message: string) {
console.log(message);
}
}
27 changes: 27 additions & 0 deletions rpgsaga/saga/tests/archer.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
30 changes: 30 additions & 0 deletions rpgsaga/saga/tests/game.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
20 changes: 20 additions & 0 deletions rpgsaga/saga/tests/knight.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
19 changes: 19 additions & 0 deletions rpgsaga/saga/tests/mage.spec.ts
Original file line number Diff line number Diff line change
@@ -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 пропустит следующий ход.`,
);
});
});