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
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${file}"
}
]
}
35 changes: 35 additions & 0 deletions rpgsaga/src/effects/BurnEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type IEffect, EffectType } from './IEffect';
import { Hero } from '../models/Hero';
import { Logger } from '../logging/Logger';

export class BurnEffect implements IEffect {
readonly type = EffectType.Burn;
readonly name = 'Горение';
remainingTurns: number;

constructor(
readonly damagePerTurn: number,
readonly duration: number
) {
this.remainingTurns = duration;
}

apply(target: Hero): void {
Logger.log(`${target.toString()} загорается! Будет получать ${this.damagePerTurn} урона каждый ход в течение ${this.duration} ходов`);
}

onTurnEnd(target: Hero): void {
if (this.remainingTurns > 0) {
target.takeDamage(this.damagePerTurn);
// this.remainingTurns--;
Logger.log(`${target.toString()} горит и теряет ${this.damagePerTurn} здоровья`);
}
}

merge(effect: IEffect): void {
if (effect.type === this.type) {
this.remainingTurns = Math.min(5, Math.max(this.remainingTurns, effect.remainingTurns));
Logger.log(`Эффект горения усилен! Урон: ${this.damagePerTurn}, осталось ходов: ${this.remainingTurns}`);
}
}
}
75 changes: 75 additions & 0 deletions rpgsaga/src/effects/EffectManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { type IEffect, EffectType } from './IEffect';
import { Hero } from '../models/Hero';
import { Logger } from '../logging/Logger';

export class EffectManager {
private static instance: EffectManager;
private _globalEffects: Map<string, IEffect[]> = new Map();

private constructor() {}

static getInstance(): EffectManager {
if (!EffectManager.instance) {
EffectManager.instance = new EffectManager();
}
return EffectManager.instance;
}

applyEffect(target: Hero, effect: IEffect): void {
if (this.hasImmunity(target, effect.type)) {
Logger.log(`${target.toString()} иммунен к ${effect.name}`);
return;
}

target.applyEffect(effect);
this.trackGlobalEffect(target.name, effect);
}

private hasImmunity(target: Hero, effectType: EffectType): boolean {
if (target.constructor.name === 'Mage') {
return effectType === EffectType.Freeze || effectType === EffectType.Poison;
}

if (target.constructor.name === 'Knight') {
return effectType === EffectType.Freeze;
}

return false;
}

private trackGlobalEffect(heroName: string, effect: IEffect): void {
if (!this._globalEffects.has(heroName)) {
this._globalEffects.set(heroName, []);
}
this._globalEffects.get(heroName)!.push(effect);
}

processAllEffects(heroes: Hero[]): void {
for (const hero of heroes) {
if (hero.isAlive) {
hero.processEffects();
}
}
}

getActiveEffects(hero: Hero): IEffect[] {
return [...hero.activeEffects];
}

clearAllEffects(hero: Hero): void {
hero.removeEffectsByType(EffectType.Burn);
hero.removeEffectsByType(EffectType.Freeze);
hero.removeEffectsByType(EffectType.Poison);
this._globalEffects.delete(hero.name);
}

getStatistics(): Map<string, number> {
const stats = new Map<string, number>();

for (const [heroName, effects] of this._globalEffects.entries()) {
stats.set(heroName, effects.length);
}

return stats;
}
}
35 changes: 35 additions & 0 deletions rpgsaga/src/effects/FreezeEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type IEffect, EffectType } from './IEffect';
import { Hero } from '../models/Hero';
import { Logger } from '../logging/Logger';

export class FreezeEffect implements IEffect {
readonly type = EffectType.Freeze;
readonly name = 'Заморозка';
remainingTurns: number;

constructor(
readonly damagePerTurn: number,
readonly duration: number
) {
this.remainingTurns = duration;
}

apply(target: Hero): void {
Logger.log(`${target.toString()} заморожен! Будет получать ${this.damagePerTurn} урона каждый ход в течение ${this.duration} ходов`);
}

onTurnEnd(target: Hero): void {
if (this.remainingTurns > 0) {
target.takeDamage(this.damagePerTurn);
// this.remainingTurns--;
Logger.log(`${target.toString()} заморожен и теряет ${this.damagePerTurn} здоровья`);
}
}

merge(effect: IEffect): void {
if (effect.type === this.type) {
const newDamage = this.damagePerTurn + effect.damagePerTurn;
Logger.log(`Эффект заморозки суммируется! Урон увеличен с ${this.damagePerTurn} до ${newDamage}`);
}
}
}
19 changes: 19 additions & 0 deletions rpgsaga/src/effects/IEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Hero } from "../models/Hero";

export enum EffectType {
Burn = 'burn',
Freeze = 'freeze',
Poison = 'poison'
}

export interface IEffect {
readonly type: EffectType;
readonly name: string;
readonly duration: number;
readonly damagePerTurn: number;
remainingTurns: number;

apply(target: Hero): void;
onTurnEnd(target: Hero): void;
merge(effect: IEffect): void;
}
35 changes: 35 additions & 0 deletions rpgsaga/src/effects/PoisonEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type IEffect, EffectType } from './IEffect';
import { Hero } from '../models/Hero';
import { Logger } from '../logging/Logger';

export class PoisonEffect implements IEffect {
readonly type = EffectType.Poison;
readonly name = 'Отравление';
remainingTurns: number;

constructor(
readonly damagePerTurn: number,
readonly duration: number
) {
this.remainingTurns = duration;
}

apply(target: Hero): void {
Logger.log(`${target.toString()} отравлен! Будет получать ${this.damagePerTurn} урона каждый ход в течение ${this.duration} ходов`);
}

onTurnEnd(target: Hero): void {
if (this.remainingTurns > 0) {
target.takeDamage(this.damagePerTurn);
// this.remainingTurns--;
Logger.log(`${target.toString()} страдает от отравления и теряет ${this.damagePerTurn} здоровья`);
}
}

merge(effect: IEffect): void {
if (effect.type === this.type && effect instanceof PoisonEffect) {
this.remainingTurns = Math.min(5, this.remainingTurns + effect.remainingTurns);
Logger.log(`Эффект отравления продлён! Осталось ходов: ${this.remainingTurns}`);
}
}
}
59 changes: 59 additions & 0 deletions rpgsaga/src/factory/HeroFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Hero } from '../models/Hero';
import { Knight } from '../models/Knight';
import { Archer } from '../models/Archer';
import { Mage } from '../models/Mage';

export type HeroType = 'knight' | 'archer' | 'mage';

export interface HeroParams {
name: string;
health: number;
strength: number;
}

export class HeroFactory {
private static readonly NAMES = [
'Артур', 'Эльдар', 'Гэндальф', 'Вильямс', 'Леголас',
'Арагорн', 'Гимли', 'Фродо', 'Саруман', 'Боромир',
'Турин', 'Беовульф', 'Сигурд', 'Роланд', 'Ланселот'
];

static createHero(type: HeroType, params: HeroParams): Hero {
switch (type) {
case 'knight':
return new Knight(params.name, params.health, params.strength);
case 'archer':
return new Archer(params.name, params.health, params.strength);
case 'mage':
return new Mage(params.name, params.health, params.strength);
default:
throw new Error(`Unknown hero type: ${type}`);
}
}

static createRandomHero(): Hero {
const types: HeroType[] = ['knight', 'archer', 'mage'];
const randomType = types[Math.floor(Math.random() * types.length)];
const randomName = this.NAMES[Math.floor(Math.random() * this.NAMES.length)];
const randomHealth = Math.floor(Math.random() * (200 - 80 + 1) + 80);
const randomStrength = Math.floor(Math.random() * (50 - 15 + 1) + 15);

return this.createHero(randomType, {
name: randomName,
health: randomHealth,
strength: randomStrength
});
}

static createHeroesArray(count: number): Hero[] {
if (count % 2 !== 0) {
throw new Error('Количество игроков должно быть чётным');
}

const heroes: Hero[] = [];
for (let i = 0; i < count; i++) {
heroes.push(this.createRandomHero());
}
return heroes;
}
}
Loading