diff --git a/rpgsaga/saga/eslint.config.mjs b/rpgsaga/saga/eslint.config.mjs index 5e7a9c75c..4fc33fcc9 100644 --- a/rpgsaga/saga/eslint.config.mjs +++ b/rpgsaga/saga/eslint.config.mjs @@ -1,157 +1,201 @@ -import { fixupConfigRules, fixupPluginRules } from "@eslint/compat"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import prettier from "eslint-plugin-prettier"; -import _import from "eslint-plugin-import"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import prettier from 'eslint-plugin-prettier'; +import _import from 'eslint-plugin-import'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, }); -export default [{ - ignores: ["**/.eslintrc.js", "**/react-app-env.d.ts"], -}, ...fixupConfigRules(compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:import/typescript", - "plugin:prettier/recommended", -)), { +export default [ + { + ignores: ['**/.eslintrc.js', '**/react-app-env.d.ts'], + }, + ...fixupConfigRules( + compat.extends( + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + 'plugin:prettier/recommended', + ), + ), + { plugins: { - "@typescript-eslint": fixupPluginRules(typescriptEslint), - prettier: fixupPluginRules(prettier), - import: fixupPluginRules(_import), + '@typescript-eslint': fixupPluginRules(typescriptEslint), + prettier: fixupPluginRules(prettier), + import: fixupPluginRules(_import), }, languageOptions: { - globals: { - ...globals.browser, - }, + globals: { + ...globals.browser, + }, - parser: tsParser, - ecmaVersion: 12, - sourceType: "module", + parser: tsParser, + ecmaVersion: 12, + sourceType: 'module', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, + parserOptions: { + ecmaFeatures: { + jsx: true, }, + }, }, settings: { - "import/resolver": { - node: { - extensions: [".js", ".jsx", ".ts", ".tsx"], - moduleDirectory: ["node_modules", "."], - }, + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + moduleDirectory: ['node_modules', '.'], }, + }, }, rules: { - "no-param-reassign": ["error"], + 'no-param-reassign': ['error'], - "prettier/prettier": ["error", { - endOfLine: "auto", - }], - - "linebreak-style": 0, + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], - "arrow-body-style": ["error", "as-needed", { - requireReturnForObjectLiteral: false, - }], + 'linebreak-style': 0, - curly: ["error", "all"], - "no-implicit-coercion": ["error"], - "spaced-comment": ["error", "always"], - eqeqeq: ["error", "always"], - "prefer-template": "error", - "no-useless-concat": "error", + 'arrow-body-style': [ + 'error', + 'as-needed', + { + requireReturnForObjectLiteral: false, + }, + ], + + curly: ['error', 'all'], + 'no-implicit-coercion': ['error'], + 'spaced-comment': ['error', 'always'], + eqeqeq: ['error', 'always'], + 'prefer-template': 'error', + 'no-useless-concat': 'error', + + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + array: false, + object: true, + }, + + AssignmentExpression: { + array: false, + object: true, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + + 'import/extensions': [ + 'error', + 'ignorePackages', + { + ts: 'never', + tsx: 'never', + }, + ], - "prefer-destructuring": ["error", { - VariableDeclarator: { - array: false, - object: true, + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + }, + ], + + 'import/order': [ + 'error', + { + 'newlines-between': 'always', + groups: ['builtin', 'external', 'parent', 'sibling', 'index'], + + pathGroups: [ + { + pattern: 'src/**', + group: 'parent', + position: 'after', }, + ], + }, + ], + + 'import/newline-after-import': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-object-type': ['error'], + '@typescript-eslint/no-unsafe-function-type': ['error'], + '@typescript-eslint/no-wrapper-object-types': ['error'], + '@typescript-eslint/no-shadow': ['error'], + + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + }, + { + selector: ['memberLike'], + modifiers: ['private'], + format: ['camelCase'], + leadingUnderscore: 'allow', + }, + { + selector: ['memberLike'], + modifiers: ['protected'], + format: ['camelCase'], + leadingUnderscore: 'allow', + }, + { + selector: ['enumMember', 'variable'], + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + }, + { + selector: 'parameter', + format: ['camelCase'], + leadingUnderscore: 'allow', + modifiers: ['unused'], + }, + { + selector: 'objectLiteralProperty', - AssignmentExpression: { - array: false, - object: true, - }, - }, { - enforceForRenamedProperties: false, - }], - - "import/extensions": ["error", "ignorePackages", { - ts: "never", - tsx: "never", - }], - - "import/no-extraneous-dependencies": ["error", { - devDependencies: true, - }], - - "import/order": ["error", { - "newlines-between": "always", - groups: ["builtin", "external", "parent", "sibling", "index"], - - pathGroups: [{ - pattern: "src/**", - group: "parent", - position: "after", - }], - }], - - "import/newline-after-import": "error", - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-empty-object-type": ["error"], - "@typescript-eslint/no-unsafe-function-type": ["error"], - "@typescript-eslint/no-wrapper-object-types": ["error"], - "@typescript-eslint/no-shadow": ["error"], - - "@typescript-eslint/naming-convention": ["error", { - selector: "default", - format: ["camelCase"], - }, { - selector: ["memberLike"], - modifiers: ["private"], - format: ["camelCase"], - leadingUnderscore: "allow", - }, { - selector: ["enumMember", "variable"], - format: ["camelCase", "PascalCase", "UPPER_CASE"], - }, { - selector: "parameter", - format: ["camelCase"], - leadingUnderscore: "allow", - modifiers: ["unused"], - }, { - selector: "objectLiteralProperty", - - filter: { - regex: "^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$", - match: true, - }, + filter: { + regex: '^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$', + match: true, + }, - format: null, - }, { - selector: "typeLike", - format: ["PascalCase"], - }, { - selector: "typeProperty", - format: ["snake_case", "camelCase"], - }], + format: null, + }, + { + selector: 'typeLike', + format: ['PascalCase'], + }, + { + selector: 'typeProperty', + format: ['snake_case', 'camelCase'], + }, + ], }, -}]; \ No newline at end of file + }, +]; diff --git a/rpgsaga/saga/src/person.ts b/rpgsaga/saga/src/funcAndClasses/classes/abstract/Person.ts similarity index 54% rename from rpgsaga/saga/src/person.ts rename to rpgsaga/saga/src/funcAndClasses/classes/abstract/Person.ts index 2c81a1074..d7a7ed5b5 100644 --- a/rpgsaga/saga/src/person.ts +++ b/rpgsaga/saga/src/funcAndClasses/classes/abstract/Person.ts @@ -1,35 +1,23 @@ -export class Person { - private _name: string; - private _sex: string; - private _age: number; +export abstract class Person { + protected _name: string; + protected _sex: string; + protected _age: number; - constructor(personFirstName, personAge, personSex) { - this.name = personFirstName; + constructor(personName, personAge, personSex) { + this.name = personName; this.age = personAge; this.sex = personSex; } - toString(): string { - return `${this._name} ${this._sex}`; - } - - toNumber(): number { - return this._age; - } - - print(): string { - return `first name: ${this._name}\nsex: ${this._sex}\nage: ${this._age}`; - } - public get age(): number { return this._age; } - public set age(n: number) { - if (n < 0 || n > 110) { + public set age(newAge: number) { + if (newAge < 0 || newAge > 110) { throw new Error('Недопустимый возраст'); } else { - this._age = n; + this._age = newAge; } } @@ -37,20 +25,34 @@ export class Person { return this._name; } - public set name(n: string) { - this._name = n; + public set name(newName: string) { + this._name = newName; } public get sex(): string { return this._sex; } - public set sex(n: string) { - const personSex = n.toLowerCase(); + public set sex(newSex: string) { + const personSex = newSex.toLowerCase(); if (personSex === 'male' || personSex === 'female') { this._sex = personSex; } else { throw new Error('Недопустимый гендер'); } } + + toString(): string { + return `${this._name} ${this._sex}`; + } + + toNumber(): number { + return this._age; + } + + print(): string { + return `name: ${this._name}\nsex: ${this._sex}\nage: ${this._age}`; + } + + abstract workRespons(): string; } diff --git a/rpgsaga/saga/src/funcAndClasses/classes/workers/Accountant.ts b/rpgsaga/saga/src/funcAndClasses/classes/workers/Accountant.ts new file mode 100644 index 000000000..0dd3468dd --- /dev/null +++ b/rpgsaga/saga/src/funcAndClasses/classes/workers/Accountant.ts @@ -0,0 +1,40 @@ +import { Person } from '../abstract/Person'; + +export class Accountant extends Person { + private _work: string; + private _salary: number; + + constructor(personName, personAge, personSex, personWork, personSalary) { + super(personName, personAge, personSex); + this.work = personWork; + this.salary = personSalary; + } + + public get work(): string { + return this._work; + } + + public set work(workName: string) { + this._work = workName; + } + + public get salary(): number { + return this._salary; + } + + public set salary(salaryValue: number) { + if (salaryValue < 0) { + throw new Error('Недопустимая зарплата'); + } else { + this._salary = salaryValue; + } + } + + public workRespons(): string { + return `${this.name} does paperwork`; + } + + toString(): string { + return `${this._name} ${this._sex} ${this._work}`; + } +} diff --git a/rpgsaga/saga/src/funcAndClasses/classes/workers/Doctor.ts b/rpgsaga/saga/src/funcAndClasses/classes/workers/Doctor.ts new file mode 100644 index 000000000..2a1918ca4 --- /dev/null +++ b/rpgsaga/saga/src/funcAndClasses/classes/workers/Doctor.ts @@ -0,0 +1,40 @@ +import { Person } from '../abstract/Person'; + +export class Doctor extends Person { + private _work: string; + private _salary: number; + + constructor(personName, personAge, personSex, personWork, personSalary) { + super(personName, personAge, personSex); + this.work = personWork; + this.salary = personSalary; + } + + public get work(): string { + return this._work; + } + + public set work(workName: string) { + this._work = workName; + } + + public get salary(): number { + return this._salary; + } + + public set salary(salaryValue: number) { + if (salaryValue < 0) { + throw new Error('Недопустимая зарплата'); + } else { + this._salary = salaryValue; + } + } + + public workRespons(): string { + return `${this.name} heals people`; + } + + toString(): string { + return `${this._name} ${this._sex} ${this._work}`; + } +} diff --git a/rpgsaga/saga/src/funcAndClasses/function/function.ts b/rpgsaga/saga/src/funcAndClasses/function/function.ts new file mode 100644 index 000000000..623507ff3 --- /dev/null +++ b/rpgsaga/saga/src/funcAndClasses/function/function.ts @@ -0,0 +1,43 @@ +function result(a: number, x: number): number { + return Math.pow(a, Math.pow(x, 2) - 1) - Math.log10(Math.pow(x, 2) - 1) + Math.cbrt(Math.pow(x, 2) - 1); +} + +export function taskA(a: number, xBegin: number, xEnd: number, xDelta: number): number[] { + const y: number[] = []; + if (xDelta === 0) { + return []; + } + if (xDelta > 0) { + for (let x = xBegin; x <= xEnd; x += xDelta) { + y.push(result(a, x)); + } + } else { + for (let x = xBegin; x >= xEnd; x += xDelta) { + if (Math.pow(x, 2) - 1 <= 0) { + continue; + } + y.push(result(a, x)); + } + } + return y; +} + +export function taskB(a: number, values: number[]): number[] { + if (values.length === 0) { + return []; + } + const y: number[] = []; + for (const x of values) { + y.push(result(a, x)); + } + return y; +} + +export function output(nameOfTask: string, results: number[]): string { + let resMsg = `Solutions to task ${nameOfTask}:\n`; + + results.forEach(resultNum => { + resMsg += `${resultNum},\n`; + }); + return resMsg; +} diff --git a/rpgsaga/saga/src/game/abstract/Player.ts b/rpgsaga/saga/src/game/abstract/Player.ts new file mode 100644 index 000000000..cdc83776b --- /dev/null +++ b/rpgsaga/saga/src/game/abstract/Player.ts @@ -0,0 +1,178 @@ +import { ISkill } from '../skills/ISkill'; +import { getRandomArrayElement } from '../utils/randomization'; +import { IWeapon } from '../weapon/IWeapon'; + +export abstract class Player { + protected _name: string; + protected _className?: string; + protected _initialHealth: number; + protected _health: number; + protected _initialStrength: number; + protected _strength: number; + protected _skills: ISkill[]; + protected _currentSkill?: ISkill; + protected _isAlive: boolean = true; + protected _countOfSkipingTurns: number = 0; + protected _weapon: IWeapon; + + constructor( + playerHealth: number, + playerStrength: number, + playerName: string, + playerWeapon: IWeapon, + playerSkills: ISkill[], + ) { + this._initialHealth = playerHealth; + this._health = this._initialHealth; + this._initialStrength = playerStrength; + this._strength = this._initialStrength; + this._name = playerName; + this._weapon = playerWeapon; + this._skills = playerSkills; + } + + public get className(): string | undefined { + return this._className; + } + + public get name(): string { + return this._name; + } + + public get isAlive(): boolean { + return this._isAlive; + } + + public get health(): number { + return this._health; + } + + public get strength(): number { + return this._strength; + } + + public get initialHealth(): number { + return this._initialHealth; + } + + public get initialStrength(): number { + return this._initialStrength; + } + + public get countOfSkipingTurns(): number { + return this._countOfSkipingTurns; + } + + public get weapon(): IWeapon { + return this._weapon; + } + + public get currentSkill(): ISkill | undefined { + return this._currentSkill; + } + + public get skills(): ISkill[] { + return this._skills; + } + + public choseSkill(): void { + this._currentSkill = getRandomArrayElement(this.skills); + } + + public useSkill(opponent: Player, skillName: string | null = null): boolean { + if (this.skills.length === 0) { + return; + } + + if (skillName !== null) { + this._currentSkill = this.skills.find(skill => skill.name === skillName.toLowerCase()); + } + + if (this._currentSkill !== undefined && this._currentSkill.usageCount > 0) { + if (this._currentSkill.effect) { + this._currentSkill.effect(this, opponent); + } + this._currentSkill.usageCount--; + this.skills.forEach(skill => { + if (skill.name === this._currentSkill.name) { + skill.usageCount--; + } + }); + + return true; + } + + return false; + } + + public attack(opponent: Player): number { + if (this.countOfSkipingTurns > 0) { + this._countOfSkipingTurns--; + return 0; + } + + if (this._currentSkill) { + const skillIndex = this._skills.findIndex(skill => skill.name === this._currentSkill.name); + + let skillsBuff: number = 0; + + if (skillIndex !== -1) { + this._skills[skillIndex].isUsed = true; + this._skills.forEach(skill => { + if (skill.isUsed && skill.buff) { + if (skill.turns <= 0) { + skill.turns = skill.initialTurns; + skill.isUsed = false; + } + if (skill.turns > 0) { + skill.turns--; + this._currentSkill.turns--; + } + skillsBuff += skill.buff.strength; + } + }); + + if (this._currentSkill.turns <= 0) { + this._currentSkill = undefined; + } + } + + return opponent.takeDamage(this._strength + this._weapon.damage + skillsBuff, this._currentSkill); + } else { + return opponent.takeDamage(this._strength + this._weapon.damage); + } + } + + public takeDamage(damage: number, skill: ISkill | undefined = undefined): number { + const currentDamage: number = damage; + this._health -= currentDamage; + if (this._health <= 0) { + this._health = 0; + this._isAlive = false; + } + return currentDamage; + } + + public heal(amount: number) { + if (this._health + amount > this.initialHealth) { + this._health = this.initialHealth; + } else { + this._health = this._health + amount; + } + } + + public reset(): void { + this._health = this.initialHealth; + this._strength = this.initialStrength; + this._currentSkill = undefined; + this._skills.forEach(skill => { + skill.usageCount = skill.initialSkillUsage; + skill.isUsed = false; + skill.turns = skill.initialTurns; + }); + } + + public skipTurns(value: number): void { + this._countOfSkipingTurns = value; + } +} diff --git a/rpgsaga/saga/src/game/classes/Archer.ts b/rpgsaga/saga/src/game/classes/Archer.ts new file mode 100644 index 000000000..15462c113 --- /dev/null +++ b/rpgsaga/saga/src/game/classes/Archer.ts @@ -0,0 +1,17 @@ +import { Player } from '../abstract/Player'; +import { ISkill } from '../skills/ISkill'; +import { IWeapon } from '../weapon/IWeapon'; + +export class Archer extends Player { + protected _className: string = 'Archer'; + + constructor( + playerHealth: number, + playerStrength: number, + playerName: string, + playerWeapon: IWeapon, + playerSkills: ISkill[], + ) { + super(playerHealth, playerStrength, playerName, playerWeapon, playerSkills); + } +} diff --git a/rpgsaga/saga/src/game/classes/Knight.ts b/rpgsaga/saga/src/game/classes/Knight.ts new file mode 100644 index 000000000..e9383ee06 --- /dev/null +++ b/rpgsaga/saga/src/game/classes/Knight.ts @@ -0,0 +1,30 @@ +import { Player } from '../abstract/Player'; +import { ISkill } from '../skills/ISkill'; +import { IWeapon } from '../weapon/IWeapon'; + +export class Knight extends Player { + protected _className: string = 'Knight'; + + constructor( + playerHealth: number, + playerStrength: number, + playerName: string, + playerWeapon: IWeapon, + playerSkills: ISkill[], + ) { + super(playerHealth, playerStrength, playerName, playerWeapon, playerSkills); + } + + public takeDamage(damage: number, skill: ISkill | undefined = undefined): number { + let currentDamage: number = damage; + if (skill !== undefined && skill.name === 'ледяные стрелы' && skill.buff) { + currentDamage -= skill.buff.strength; + } + this._health -= currentDamage; + if (this._health <= 0) { + this._health = 0; + this._isAlive = false; + } + return currentDamage; + } +} diff --git a/rpgsaga/saga/src/game/classes/Wizard.ts b/rpgsaga/saga/src/game/classes/Wizard.ts new file mode 100644 index 000000000..39cf6eb9f --- /dev/null +++ b/rpgsaga/saga/src/game/classes/Wizard.ts @@ -0,0 +1,17 @@ +import { Player } from '../abstract/Player'; +import { ISkill } from '../skills/ISkill'; +import { IWeapon } from '../weapon/IWeapon'; + +export class Wizard extends Player { + protected _className: string = 'Wizard'; + + constructor( + playerHealth: number, + playerStrength: number, + playerName: string, + playerWeapon: IWeapon, + playerSkills: ISkill[], + ) { + super(playerHealth, playerStrength, playerName, playerWeapon, playerSkills); + } +} diff --git a/rpgsaga/saga/src/game/classes/index.ts b/rpgsaga/saga/src/game/classes/index.ts new file mode 100644 index 000000000..863f362b3 --- /dev/null +++ b/rpgsaga/saga/src/game/classes/index.ts @@ -0,0 +1,3 @@ +export * from './Archer'; +export * from './Knight'; +export * from './Wizard'; diff --git a/rpgsaga/saga/src/game/fabrics/playersFabrics/ArcherFabric.ts b/rpgsaga/saga/src/game/fabrics/playersFabrics/ArcherFabric.ts new file mode 100644 index 000000000..4250382de --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/playersFabrics/ArcherFabric.ts @@ -0,0 +1,33 @@ +import { Player } from '../../abstract/Player'; +import { Archer } from '../../classes'; +import { ISkill } from '../../skills/ISkill'; +import { getRandomArrayElement } from '../../utils/randomization'; +import { IWeapon } from '../../weapon/IWeapon'; +import { SkillFabric } from '../skillFabric/SkillFabric'; + +export class ArcherFabric { + private skillFabric = new SkillFabric(); + + public createArcher( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + const name: string = getRandomArrayElement(names)!; + const health: number = playerHealth; + const strength: number = playerStrength; + const weapon: IWeapon = playerWeapon; + + if (playerSkills !== null) { + return new Archer(health, strength, name, weapon, playerSkills); + } else { + const firstSkill = this.skillFabric.createSkillFromTemplate('ледяные стрелы')!; + firstSkill.usageCount = 2; + firstSkill.initialSkillUsage = 2; + const skills: ISkill[] = [firstSkill, this.skillFabric.createSkillFromTemplate('огненные стрелы')!]; + return new Archer(health, strength, name, weapon, skills); + } + } +} diff --git a/rpgsaga/saga/src/game/fabrics/playersFabrics/KnightFabric.ts b/rpgsaga/saga/src/game/fabrics/playersFabrics/KnightFabric.ts new file mode 100644 index 000000000..850dc9c52 --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/playersFabrics/KnightFabric.ts @@ -0,0 +1,33 @@ +import { Player } from '../../abstract/Player'; +import { Knight } from '../../classes'; +import { ISkill } from '../../skills/ISkill'; +import { getRandomArrayElement } from '../../utils/randomization'; +import { IWeapon } from '../../weapon/IWeapon'; +import { SkillFabric } from '../skillFabric/SkillFabric'; + +export class KnightFabric { + private skillFabric = new SkillFabric(); + + public createKnight( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + const name: string = getRandomArrayElement(names)!; + const health: number = playerHealth; + const strength: number = playerStrength; + const weapon: IWeapon = playerWeapon; + + if (playerSkills !== null) { + return new Knight(health, strength, name, weapon, playerSkills); + } else { + const skills: ISkill[] = [ + this.skillFabric.createSkillFromTemplate('удар возмездия')!, + this.skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]; + return new Knight(health, strength, name, weapon, skills); + } + } +} diff --git a/rpgsaga/saga/src/game/fabrics/playersFabrics/PlayerFabric.ts b/rpgsaga/saga/src/game/fabrics/playersFabrics/PlayerFabric.ts new file mode 100644 index 000000000..dd9e92f29 --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/playersFabrics/PlayerFabric.ts @@ -0,0 +1,75 @@ +import { Player } from '../../abstract/Player'; +import { ISkill } from '../../skills/ISkill'; +import { getRandomArrayElement, getRandomNumber } from '../../utils/randomization'; +import { IWeapon } from '../../weapon/IWeapon'; +import { WeaponFabric } from '../weaponsFabric/WeaponFabric'; + +import { ArcherFabric } from './ArcherFabric'; +import { KnightFabric } from './KnightFabric'; +import { WizardFabric } from './WizardFabric'; + +export class PlayerFabric { + private weaponFabric = new WeaponFabric(); + private archerFabric = new ArcherFabric(); + private knightFabric = new KnightFabric(); + private wizardFabric = new WizardFabric(); + + public createPlayer( + playerClass: string, + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player | undefined { + const names: string[] = [ + 'Эльдар', + 'Артур', + 'Гэндальф', + 'Вильямс', + 'Агатон', + 'Аполлон', + 'Артемида', + 'Зевс', + 'Персей', + 'Феникс', + 'Элита', + 'Ирида', + 'Медея', + 'Орион', + 'Рафаэль', + 'Себастиан', + 'Эмиль', + 'Аврора', + 'Веста', + 'Лилия', + 'Мира', + ]; + switch (playerClass) { + case 'Knight': + return this.knightFabric.createKnight(names, playerHealth, playerStrength, playerWeapon, playerSkills); + case 'Archer': + return this.archerFabric.createArcher(names, playerHealth, playerStrength, playerWeapon, playerSkills); + case 'Wizard': + return this.wizardFabric.createWizard(names, playerHealth, playerStrength, playerWeapon, playerSkills); + } + } + + createRandomPlayer(): Player { + const playerFabric = new PlayerFabric(); + const classes: string[] = ['Knight', 'Archer', 'Wizard']; + const weapons: string[] = ['bow', 'sword', 'stick']; + const playerClass: string = getRandomArrayElement(classes)!; + const playerWeapon: IWeapon = this.weaponFabric.createRandomWeapon(getRandomArrayElement(weapons)!); + const health: number = getRandomNumber(125, 150); + const strength: number = getRandomNumber(10, 15); + return playerFabric.createPlayer(playerClass, health, strength, playerWeapon)!; + } + + createRandomPlayers(playersCount: number): Player[] { + const players: Player[] = []; + for (let i = 0; i < playersCount; i++) { + players.push(this.createRandomPlayer()); + } + return players; + } +} diff --git a/rpgsaga/saga/src/game/fabrics/playersFabrics/WizardFabric.ts b/rpgsaga/saga/src/game/fabrics/playersFabrics/WizardFabric.ts new file mode 100644 index 000000000..77d6daa61 --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/playersFabrics/WizardFabric.ts @@ -0,0 +1,33 @@ +import { Player } from '../../abstract/Player'; +import { Wizard } from '../../classes'; +import { ISkill } from '../../skills/ISkill'; +import { getRandomArrayElement } from '../../utils/randomization'; +import { IWeapon } from '../../weapon/IWeapon'; +import { SkillFabric } from '../skillFabric/SkillFabric'; + +export class WizardFabric { + private skillFabric = new SkillFabric(); + + public createWizard( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + const name: string = getRandomArrayElement(names)!; + const health: number = playerHealth; + const strength: number = playerStrength; + const weapon: IWeapon = playerWeapon; + + if (playerSkills !== null) { + return new Wizard(health, strength, name, weapon, playerSkills); + } else { + const skills: ISkill[] = [ + this.skillFabric.createSkillFromTemplate('заворожение')!, + this.skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]; + return new Wizard(health, strength, name, weapon, skills); + } + } +} diff --git a/rpgsaga/saga/src/game/fabrics/playersFabrics/index.ts b/rpgsaga/saga/src/game/fabrics/playersFabrics/index.ts new file mode 100644 index 000000000..a3aa5c7a5 --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/playersFabrics/index.ts @@ -0,0 +1,4 @@ +export * from './ArcherFabric'; +export * from './KnightFabric'; +export * from './PlayerFabric'; +export * from './WizardFabric'; diff --git a/rpgsaga/saga/src/game/fabrics/skillFabric/SkillFabric.ts b/rpgsaga/saga/src/game/fabrics/skillFabric/SkillFabric.ts new file mode 100644 index 000000000..d1e3c5e8b --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/skillFabric/SkillFabric.ts @@ -0,0 +1,91 @@ +import { Player } from '../../abstract/Player'; +import { ISkill } from '../../skills/ISkill'; + +export class SkillFabric { + private skillsTemplate: ISkill[] = [ + { + name: 'огненные стрелы', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + buff: { + strength: 2, + }, + }, + { + name: 'ледяные стрелы', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + turns: 3, + initialTurns: 3, + buff: { + strength: 3, + }, + }, + { + name: 'удар возмездия', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + damage: (caster: Player) => caster.strength * 1.3 + caster.weapon.damage, + effect: (caster: Player, opponent: Player) => { + const weaponDamage = caster.weapon ? caster.weapon.damage : 0; + opponent.takeDamage(caster.strength * 1.3 + weaponDamage); + }, + }, + { + name: 'заворожение', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + effect: (caster: Player, opponent: Player) => { + opponent.skipTurns(1); + }, + }, + ]; + + public createSkill( + skillName: string, + skillDamage: (caster: Player) => number | undefined, + isUsedSKill: boolean, + skillUsageCount: number, + skillInitialUsage: number, + skillTurns: number | undefined = undefined, + skillInitialTurns: number | undefined = undefined, + skillEffect: (caster: Player, opponent: Player) => void, + skillBuff: { strength: number } | undefined, + ) { + const skill: ISkill = { + name: skillName, + damage: skillDamage, + isUsed: isUsedSKill, + usageCount: skillUsageCount, + initialSkillUsage: skillInitialUsage, + turns: skillTurns, + initialTurns: skillInitialTurns, + effect: skillEffect, + buff: skillBuff, + }; + return skill; + } + + public createSkillFromTemplate(templateName: string): ISkill | null { + const skillTemplate = this.skillsTemplate.find(skill => skill.name === templateName); + if (!skillTemplate) { + return null; + } + + return this.createSkill( + skillTemplate.name, + skillTemplate.damage!, + skillTemplate.isUsed, + skillTemplate.usageCount, + skillTemplate.initialSkillUsage, + skillTemplate.turns, + skillTemplate.initialTurns, + skillTemplate.effect!, + skillTemplate.buff, + ); + } +} diff --git a/rpgsaga/saga/src/game/fabrics/weaponsFabric/WeaponFabric.ts b/rpgsaga/saga/src/game/fabrics/weaponsFabric/WeaponFabric.ts new file mode 100644 index 000000000..d2884f54d --- /dev/null +++ b/rpgsaga/saga/src/game/fabrics/weaponsFabric/WeaponFabric.ts @@ -0,0 +1,53 @@ +import { getRandomNumber } from '../../utils/randomization'; +import { IWeapon } from '../../weapon/IWeapon'; + +export class WeaponFabric { + private names: object = { + sword: ['Dragonsbane', 'Stormbringer', 'Aethelred'], + stick: ['Oak Staff', 'Elderwood Branch', "Shepherd's Crook"], + bow: ["Hunter's Bow", 'Longbow', 'Shortbow'], + }; + + public createWeapon(weaponType: string, weaponName: string, weaponDamage: number) { + let weapon: IWeapon; + switch (weaponType.toLowerCase()) { + case 'sword': + weapon = { + name: weaponName, + damage: weaponDamage, + }; + break; + case 'stick': + weapon = { + name: weaponName, + damage: weaponDamage, + }; + break; + case 'bow': + weapon = { + name: weaponName, + damage: weaponDamage, + }; + break; + default: + weapon = { + name: 'fists', + damage: 3, + }; + } + return weapon; + } + + public createRandomWeapon(weaponType: string): IWeapon { + const namesArr: string[] = this.names[weaponType.toLowerCase() as keyof typeof this.names]; + + if (!namesArr) { + return this.createWeapon('fists', 'fists', 3); + } + + const name = + this.names[weaponType.toLowerCase() as keyof typeof this.names][Math.floor(Math.random() * namesArr.length)]; + const damage = getRandomNumber(2, 5); + return this.createWeapon(weaponType, name, damage); + } +} diff --git a/rpgsaga/saga/src/game/gameplay/Game.ts b/rpgsaga/saga/src/game/gameplay/Game.ts new file mode 100644 index 000000000..d89b0f1a7 --- /dev/null +++ b/rpgsaga/saga/src/game/gameplay/Game.ts @@ -0,0 +1,96 @@ +import { Player } from '../abstract/Player'; +import { PlayerFabric } from '../fabrics/playersFabrics'; +import { Logger } from '../utils/output/Logger'; + +export class Game { + private playerFabric = new PlayerFabric(); + private _players: Player[] = []; + private logger: Logger; + + constructor(playerCount: number, player: Player | undefined = undefined, logger: Logger) { + this._players = this.playerFabric.createRandomPlayers(playerCount); + this.logger = logger; + if (player !== undefined) { + this._players.push(player); + } + } + + public get players(): Player[] { + return this._players; + } + + public async start() { + this.logger.messageLog('Игра началась!'); + let listOfPlayers = 'Список участников: \n\n'; + listOfPlayers += this._players.map(player => `(${player.className}) ${player.name}`).join('\n\n'); + this.logger.messageLog(listOfPlayers); + await this.tournament(this._players); + this.logger.messageLog(`Победитель: (${this._players[0].className}) ${this._players[0].name}`); + } + + public async tournament(players: Player[]): Promise { + if (players.length === 1) { + return players[0]; + } + + const nextRoundPlayers: Player[] = []; + for (let i = 0; i < players.length; i += 2) { + const player1 = players[i]; + const player2 = players[i + 1]; + const winner = await this.battle([player1, player2]); + winner.reset(); + nextRoundPlayers.push(winner); + } + + return this.tournament(nextRoundPlayers); + } + + public async battle(fighters: Player[]): Promise { + this.logger.messageLog(`(${fighters[0].name}) vs (${fighters[1].name})`); + + let turn = 0; + + while (fighters[0].health > 0 && fighters[1].health > 0) { + const attackerIndex = turn % 2; + const defenderIndex = (turn + 1) % 2; + const attacker = fighters[attackerIndex]; + const defender = fighters[defenderIndex]; + + if (defender.isAlive) { + if (attacker.countOfSkipingTurns === 0) { + this.logger.attackLog(attacker, defender, attacker.attack(defender)); + } else { + attacker.attack(defender); + this.logger.skipTurnLog(attacker, defender); + } + + if (!defender.isAlive) { + this.logger.deathLog(defender); + break; + } + } + + if (Math.random() < 0.4 && attacker.isAlive && defender.isAlive) { + attacker.choseSkill(); + const isUsed: boolean = attacker.useSkill(defender); + if (isUsed) { + this.logger.skillLog(attacker, defender); + } + } + + await this.delay(2); + turn++; + } + + this.updatePlayersArray(); + return fighters.find(player => player.health > 0)!; + } + + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + private updatePlayersArray() { + this._players = this._players.filter(player => player.isAlive); + } +} diff --git a/rpgsaga/saga/src/game/skills/ISkill.ts b/rpgsaga/saga/src/game/skills/ISkill.ts new file mode 100644 index 000000000..04ead1776 --- /dev/null +++ b/rpgsaga/saga/src/game/skills/ISkill.ts @@ -0,0 +1,15 @@ +import { Player } from '../abstract/Player'; + +export interface ISkill { + name: string; + damage?: (caster: Player) => number | undefined; + isUsed: boolean; + usageCount: number; + initialSkillUsage: number; + turns?: number; + initialTurns?: number; + effect?: (caster: Player, opponent: Player) => void; + buff?: { + strength: number; + }; +} diff --git a/rpgsaga/saga/src/game/utils/input/createCharacter.ts b/rpgsaga/saga/src/game/utils/input/createCharacter.ts new file mode 100644 index 000000000..236999fc3 --- /dev/null +++ b/rpgsaga/saga/src/game/utils/input/createCharacter.ts @@ -0,0 +1,118 @@ +import { PlayerFabric } from '../../fabrics/playersFabrics/index'; +import { SkillFabric } from '../../fabrics/skillFabric/SkillFabric'; +import { WeaponFabric } from '../../fabrics/weaponsFabric/WeaponFabric'; +import { Game } from '../../gameplay/Game'; +import { ISkill } from '../../skills/ISkill'; +import { IWeapon } from '../../weapon/IWeapon'; +import { Logger } from '../output/Logger'; +import { readAnswer } from '../question/readAnswer'; + +export async function createCharacter(numberOfPlayers: number): Promise { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const logger = new Logger(); + + let playerType: string; + let playerHealth: number = 0; + let playerStrength: number = 0; + let playerWeapon: IWeapon; + + const playerSkills: ISkill[] = []; + const playerFabric = new PlayerFabric(); + const types: string[] = ['Knight', 'Archer', 'Wizard']; + const weapons: string[] = ['bow', 'sword', 'stick']; + const skillNames: string[] = ['огненные стрелы', 'ледяные стрелы', 'удар возмездия', 'заворожение']; + + async function askForClass(): Promise { + const playerClass: string = await readAnswer('Выберите класс своего героя: 1. Knight, 2. Archer, 3. Wizard: '); + const number: number = parseInt(playerClass); + if (isNaN(number) || number < 1 || number > 3) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForClass(); + } else { + playerType = types[number - 1]; + await askForHealth(); + } + } + + async function askForHealth(): Promise { + const healthInput: string = await readAnswer('Напишите количество здоровья для своего героя (от 125 до 150): '); + const number: number = parseInt(healthInput); + if (isNaN(number) || number < 125 || number > 150) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForHealth(); + } else { + playerHealth = number; + await askForStrength(); + } + } + + async function askForStrength(): Promise { + const strengthInput: string = await readAnswer('Напишите количество силы для своего героя (от 10 до 15): '); + const number: number = parseInt(strengthInput); + if (isNaN(number) || number < 10 || number > 15) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForStrength(); + } else { + playerStrength = number; + await askForWeapon(); + } + } + + async function askForWeapon(): Promise { + const playerClass: string = await readAnswer('Выберите оружие своего героя: 1. меч, 2. лук, 3. посох: '); + const number: number = parseInt(playerClass); + if (isNaN(number) || number < 1 || number > 3) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForWeapon(); + } else { + playerWeapon = weaponFabric.createRandomWeapon(weapons[number - 1]); + await askForSkills(); + } + } + + async function askForSkills(): Promise { + const playerClass: string = await readAnswer( + 'Выберите скиллы своего героя: 1. огненные стрелы, 2. ледяные стрелы, 3. удар возмездия, 4. заворожение. \nДля старта со стандартными навыками класса, напишите 5. Для выхода напишите 6 : ', + ); + const number: number = parseInt(playerClass); + if (isNaN(number) || number < 1 || number > 6) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForSkills(); + } else if (number < 5 && number > 0) { + if (playerSkills.length > 2) { + console.log('У вас уже максимальное количество скиллов'); + } else { + playerSkills.push(skillFabric.createSkillFromTemplate(skillNames[number - 1])!); + } + await askForSkills(); + } else if (number === 6) { + if (playerSkills.length > 0) { + return; + } else { + console.log('Выберите хотя бы один скилл'); + await askForSkills(); + } + } else { + return; + } + } + + await askForClass(); + + if (playerSkills.length !== 0) { + const game = new Game( + numberOfPlayers - 1, + playerFabric.createPlayer(playerType!, playerHealth, playerStrength, playerWeapon!, playerSkills), + logger, + ); + await game.start(); + } else { + const game = new Game( + numberOfPlayers - 1, + playerFabric.createPlayer(playerType!, playerHealth, playerStrength, playerWeapon!), + logger, + ); + await game.start(); + } +} diff --git a/rpgsaga/saga/src/game/utils/input/createGame.ts b/rpgsaga/saga/src/game/utils/input/createGame.ts new file mode 100644 index 000000000..27937a6f3 --- /dev/null +++ b/rpgsaga/saga/src/game/utils/input/createGame.ts @@ -0,0 +1,40 @@ +import { Game } from '../../gameplay/Game'; +import { Logger } from '../output/Logger'; +import { readAnswer } from '../question/readAnswer'; + +import { createCharacter } from './createCharacter'; + +export function createGame(): void { + const logger = new Logger(); + + let number: number; + async function askForPlayers() { + const inputNumber: string = await readAnswer('Введите число игроков (должно делиться на 4): '); + number = parseInt(inputNumber); + if (isNaN(number) || number < 1 || number % 4 !== 0) { + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForPlayers(); + } else { + await askForCreating(); + } + } + + async function askForCreating() { + const inputString: string = await readAnswer('Хотите ли вы создать своего персонажа? (да/нет) '); + const game = new Game(number, undefined, logger); + switch (inputString.toLowerCase()) { + case 'да': + await createCharacter(number); + break; + case 'нет': + await game.start(); + break; + default: + console.log('Некорректный ввод. Пожалуйста, попробуйте снова.'); + await askForCreating(); + break; + } + } + + askForPlayers(); +} diff --git a/rpgsaga/saga/src/game/utils/output/Logger.ts b/rpgsaga/saga/src/game/utils/output/Logger.ts new file mode 100644 index 000000000..e1df7c3a7 --- /dev/null +++ b/rpgsaga/saga/src/game/utils/output/Logger.ts @@ -0,0 +1,41 @@ +import { Player } from '../../abstract/Player'; + +export class Logger { + public messageLog(message: string): void { + const timestamp: string = new Date().toISOString(); + const logEntry: string = `${timestamp}: ${message}\n`; + console.log(logEntry); + } + + public attackLog(attacker: Player, defender: Player, damage: number): void { + const timestamp: string = new Date().toISOString(); + const message: string = `(${attacker.className}) ${attacker.name} наносит урон ${damage} игроку ${defender.name} (${defender.className})`; + const logEntry: string = `${timestamp}: ${message}\n`; + console.log(logEntry); + } + + public skillLog(attacker: Player, defender: Player): void { + const timestamp: string = new Date().toISOString(); + let message: string = ''; + message += `(${attacker.className}) ${attacker.name} использует ${attacker.currentSkill?.name} на ${defender.name} (${defender.className}) `; + if (attacker.currentSkill?.damage) { + message += `и наносит урон ${attacker.currentSkill.damage(attacker)}`; + } + const logEntry: string = `${timestamp}: ${message}\n`; + console.log(logEntry); + } + + public skipTurnLog(attacker: Player, defender: Player): void { + const timestamp: string = new Date().toISOString(); + const message: string = `(${attacker.className}) ${attacker.name} пропускает ход из-за ${defender.currentSkill.name}`; + const logEntry: string = `${timestamp}: ${message}\n`; + console.log(logEntry); + } + + public deathLog(warrior: Player): void { + const timestamp: string = new Date().toISOString(); + const message: string = `(${warrior.className}) ${warrior.name} умирает`; + const logEntry: string = `${timestamp}: ${message}\n`; + console.log(logEntry); + } +} diff --git a/rpgsaga/saga/src/game/utils/question/readAnswer.ts b/rpgsaga/saga/src/game/utils/question/readAnswer.ts new file mode 100644 index 000000000..f311310da --- /dev/null +++ b/rpgsaga/saga/src/game/utils/question/readAnswer.ts @@ -0,0 +1,15 @@ +import * as readline from 'readline'; + +export function readAnswer(query: string): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise(resolve => { + rl.question(query, answer => { + resolve(answer); + rl.close(); + }); + }); +} diff --git a/rpgsaga/saga/src/game/utils/randomization/getRandomArrayElement.ts b/rpgsaga/saga/src/game/utils/randomization/getRandomArrayElement.ts new file mode 100644 index 000000000..c237f711d --- /dev/null +++ b/rpgsaga/saga/src/game/utils/randomization/getRandomArrayElement.ts @@ -0,0 +1,7 @@ +export function getRandomArrayElement(arr: T[]): T | undefined { + if (arr.length === 0) { + return undefined; + } + const randomIndex: number = Math.floor(Math.random() * arr.length); + return arr[randomIndex]; +} diff --git a/rpgsaga/saga/src/game/utils/randomization/getRandomNumber.ts b/rpgsaga/saga/src/game/utils/randomization/getRandomNumber.ts new file mode 100644 index 000000000..83286e56e --- /dev/null +++ b/rpgsaga/saga/src/game/utils/randomization/getRandomNumber.ts @@ -0,0 +1,13 @@ +export function getRandomNumber(min: number, max: number): number { + if (min > max) { + return -1; + } + if (min === max) { + return min; + } + + const range: number = max - min + 1; + const randomNumber: number = Math.floor(Math.random() * range) + min; + + return randomNumber; +} diff --git a/rpgsaga/saga/src/game/utils/randomization/index.ts b/rpgsaga/saga/src/game/utils/randomization/index.ts new file mode 100644 index 000000000..6c9e0a1c1 --- /dev/null +++ b/rpgsaga/saga/src/game/utils/randomization/index.ts @@ -0,0 +1,2 @@ +export * from './getRandomArrayElement'; +export * from './getRandomNumber'; diff --git a/rpgsaga/saga/src/game/weapon/IWeapon.ts b/rpgsaga/saga/src/game/weapon/IWeapon.ts new file mode 100644 index 000000000..92984e90e --- /dev/null +++ b/rpgsaga/saga/src/game/weapon/IWeapon.ts @@ -0,0 +1,4 @@ +export interface IWeapon { + get name(): string; + get damage(): number; +} diff --git a/rpgsaga/saga/src/index.ts b/rpgsaga/saga/src/index.ts index 02588ecdf..860f16e62 100644 --- a/rpgsaga/saga/src/index.ts +++ b/rpgsaga/saga/src/index.ts @@ -1,51 +1,19 @@ -function result(a: number, x: number): number { - return Math.pow(a, Math.pow(x, 2) - 1) - Math.log10(Math.pow(x, 2) - 1) + Math.cbrt(Math.pow(x, 2) - 1); -} +import { createGame } from './game/utils/input/createGame'; +import { taskA, taskB, output } from './funcAndClasses/function/function'; +import { Person } from './funcAndClasses/classes/abstract/Person'; +import { Accountant } from './funcAndClasses/classes/workers/Accountant'; +import { Doctor } from './funcAndClasses/classes/workers/Doctor'; -function taskA(a: number, xBegin: number, xEnd: number, xDelta: number): number[] { - const y: number[] = []; - if (xDelta === 0) { - return []; - } - if (xDelta > 0) { - for (let x = xBegin; x <= xEnd; x += xDelta) { - y.push(result(a, x)); - } - } else { - for (let x = xBegin; x >= xEnd; x += xDelta) { - if (Math.pow(x, 2) - 1 <= 0) { - continue; - } - y.push(result(a, x)); - } - } - return y; -} +console.log(output('A', taskA(1.6, 1.2, 3.7, 0.5))); -function taskB(a: number, values: number[]): number[] { - if (values.length === 0) { - return []; - } - const y: number[] = []; - for (const x of values) { - y.push(result(a, x)); - } - return y; -} +console.log(output('B', taskB(1.6, [1.28, 1.36, 2.47, 3.68, 4.56]))); -function output(nameOfTask: string, results: number[]): string { - let resMsg = `Solutions to task ${nameOfTask}:\n`; +const accountant: Person = new Accountant('John', 34, 'Male', 'Sberbank', 40000); +const doctor: Person = new Doctor('Mary', 25, 'Female', 'LidEnf', 25000); - results.forEach(resultNum => { - resMsg += `${resultNum},\n`; - }); - return resMsg; +const arrOfPersons: Person[] = [accountant, doctor]; +for (const pers of arrOfPersons) { + console.log(pers.workRespons()); } -const taskAResult = taskA(1.6, 2, 10, 0); -console.log(output('A', taskAResult)); - -const taskBResult = taskB(1.6, []); -console.log(output('B', taskBResult)); - -export { taskA, taskB }; +createGame(); diff --git a/rpgsaga/saga/tests/funcAndClassTests/Accountant.spec.ts b/rpgsaga/saga/tests/funcAndClassTests/Accountant.spec.ts new file mode 100644 index 000000000..d8478da2d --- /dev/null +++ b/rpgsaga/saga/tests/funcAndClassTests/Accountant.spec.ts @@ -0,0 +1,85 @@ +import { Accountant } from '../../src/funcAndClasses/classes/workers/Accountant'; + +describe('Person class methods tests', () => { + it('Constructor test', () => { + let newAccountant = new Accountant('TestPerson', 20, 'Male', 'Sberbank', 40000); + expect(newAccountant.age).toEqual(20); + expect(newAccountant.name).toBe('TestPerson'); + expect(newAccountant.sex).toBe('male'); + expect(newAccountant.work).toBe('Sberbank'); + expect(newAccountant.salary).toEqual(40000); + }); + describe('Get methods tests', () => { + let newAccountant = new Accountant('TestPerson', 20, 'Male', 'Sberbank', 40000); + it('Age get test', () => { + expect(newAccountant.age).toEqual(20); + }); + it('Name get test', () => { + expect(newAccountant.name).toBe('TestPerson'); + }); + it('Sex get test', () => { + expect(newAccountant.sex).toBe('male'); + }); + it('Work get test', () => { + expect(newAccountant.work).toBe('Sberbank'); + }); + it('Salary get test', () => { + expect(newAccountant.salary).toEqual(40000); + }); + }); + describe('Set methods tests', () => { + let newAccountant = new Accountant('TestPerson', 20, 'Male', 'Sberbank', 40000); + it('Age basic test', () => { + newAccountant.age = 35; + expect(newAccountant.age).toEqual(35); + }); + it('Age negative test', () => { + expect(() => { + newAccountant.age = -1; + }).toThrow(Error('Недопустимый возраст')); + }); + it('Name test', () => { + newAccountant.name = 'John'; + expect(newAccountant.name).toBe('John'); + }); + it('Sex basic test', () => { + newAccountant.sex = 'Female'; + expect(newAccountant.sex).toBe('female'); + }); + it('Sex uncorrect test', () => { + expect(() => { + newAccountant.sex = 'dog'; + }).toThrow(Error('Недопустимый гендер')); + }); + it('Salary basic test', () => { + newAccountant.salary = 35000; + expect(newAccountant.salary).toEqual(35000); + }); + it('Salary uncorrect test', () => { + expect(() => { + newAccountant.salary = -1; + }).toThrow(Error('Недопустимая зарплата')); + }); + it('Work test', () => { + newAccountant.work = 'AlphaBank'; + expect(newAccountant.work).toBe('AlphaBank'); + }); + }); + describe('Other methods tests', () => { + let newAccountant = new Accountant('John', 20, 'Male', 'Sberbank', 40000); + it('Should return name, sex and work', () => { + expect(newAccountant.toString()).toEqual(`${newAccountant.name} ${newAccountant.sex} ${newAccountant.work}`); + }); + it('Should return age as number', () => { + expect(newAccountant.toNumber()).toEqual(newAccountant.age); + }); + it('Should return all properties', () => { + expect(newAccountant.print()).toEqual( + `name: ${newAccountant.name}\nsex: ${newAccountant.sex}\nage: ${newAccountant.age}`, + ); + }); + it('Should return work', () => { + expect(newAccountant.workRespons()).toEqual(`${newAccountant.name} does paperwork`); + }); + }); +}); diff --git a/rpgsaga/saga/tests/funcAndClassTests/Doctor.spec.ts b/rpgsaga/saga/tests/funcAndClassTests/Doctor.spec.ts new file mode 100644 index 000000000..e828b42e5 --- /dev/null +++ b/rpgsaga/saga/tests/funcAndClassTests/Doctor.spec.ts @@ -0,0 +1,83 @@ +import { Doctor } from '../../src/funcAndClasses/classes/workers/Doctor'; + +describe('Person class methods tests', () => { + it('Constructor test', () => { + let newDoctor = new Doctor('Mary', 25, 'Female', 'LidEnf', 25000); + expect(newDoctor.age).toEqual(25); + expect(newDoctor.name).toBe('Mary'); + expect(newDoctor.sex).toBe('female'); + expect(newDoctor.work).toBe('LidEnf'); + expect(newDoctor.salary).toEqual(25000); + }); + describe('Get methods tests', () => { + let newDoctor = new Doctor('Mary', 25, 'Female', 'LidEnf', 25000); + it('Age get test', () => { + expect(newDoctor.age).toEqual(25); + }); + it('Name get test', () => { + expect(newDoctor.name).toBe('Mary'); + }); + it('Sex get test', () => { + expect(newDoctor.sex).toBe('female'); + }); + it('Work get test', () => { + expect(newDoctor.work).toBe('LidEnf'); + }); + it('Salary get test', () => { + expect(newDoctor.salary).toEqual(25000); + }); + }); + describe('Set methods tests', () => { + let newDoctor = new Doctor('Mary', 25, 'Female', 'LidEnf', 25000); + it('Age basic test', () => { + newDoctor.age = 35; + expect(newDoctor.age).toEqual(35); + }); + it('Age negative test', () => { + expect(() => { + newDoctor.age = -1; + }).toThrow(Error('Недопустимый возраст')); + }); + it('Name test', () => { + newDoctor.name = 'Jess'; + expect(newDoctor.name).toBe('Jess'); + }); + it('Sex basic test', () => { + newDoctor.sex = 'Male'; + expect(newDoctor.sex).toBe('male'); + }); + it('Sex uncorrect test', () => { + expect(() => { + newDoctor.sex = 'dog'; + }).toThrow(Error('Недопустимый гендер')); + }); + it('Salary basic test', () => { + newDoctor.salary = 35000; + expect(newDoctor.salary).toEqual(35000); + }); + it('Salary uncorrect test', () => { + expect(() => { + newDoctor.salary = -1; + }).toThrow(Error('Недопустимая зарплата')); + }); + it('Work test', () => { + newDoctor.work = 'Kranex'; + expect(newDoctor.work).toBe('Kranex'); + }); + }); + describe('Other methods tests', () => { + let newDoctor = new Doctor('Mary', 25, 'Female', 'LidEnf', 25000); + it('Should return name, sex and work', () => { + expect(newDoctor.toString()).toEqual(`${newDoctor.name} ${newDoctor.sex} ${newDoctor.work}`); + }); + it('Should return age as number', () => { + expect(newDoctor.toNumber()).toEqual(newDoctor.age); + }); + it('Should return all properties', () => { + expect(newDoctor.print()).toEqual(`name: ${newDoctor.name}\nsex: ${newDoctor.sex}\nage: ${newDoctor.age}`); + }); + it('Should return work', () => { + expect(newDoctor.workRespons()).toEqual(`${newDoctor.name} heals people`); + }); + }); +}); diff --git a/rpgsaga/saga/tests/example.spec.ts b/rpgsaga/saga/tests/funcAndClassTests/example.spec.ts similarity index 96% rename from rpgsaga/saga/tests/example.spec.ts rename to rpgsaga/saga/tests/funcAndClassTests/example.spec.ts index acbec8ce1..22d235dfa 100644 --- a/rpgsaga/saga/tests/example.spec.ts +++ b/rpgsaga/saga/tests/funcAndClassTests/example.spec.ts @@ -1,4 +1,4 @@ -import { taskA, taskB } from '../src'; +import { taskA, taskB } from '../../src/funcAndClasses/function/function'; describe('Tests taskA', () => { it('should return 6 values', () => { diff --git a/rpgsaga/saga/tests/person.spec.ts b/rpgsaga/saga/tests/person.spec.ts deleted file mode 100644 index 448f42e03..000000000 --- a/rpgsaga/saga/tests/person.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Person } from '../src/person'; - -describe('Person class methods tests', () => { - it('Constructor test', () => { - let newPerson = new Person('TestPerson', 20, 'Male'); - expect(newPerson.age).toEqual(20); - expect(newPerson.name).toBe('TestPerson'); - expect(newPerson.sex).toBe('male'); - }); - describe('Get methods tests', () => { - let newPerson = new Person('TestPerson', 20, 'Male'); - it('Age get test', () => { - expect(newPerson.age).toEqual(20); - }); - it('Name get test', () => { - expect(newPerson.name).toBe('TestPerson'); - }); - it('Sex get test', () => { - expect(newPerson.sex).toBe('male'); - }); - }); - describe('Set methods tests', () => { - let newPerson = new Person('TestPerson', 20, 'Male'); - it('Age basic test', () => { - newPerson.age = 35; - expect(newPerson.age).toEqual(35); - }); - it('Age negative test', () => { - expect(() => { - newPerson.age = -1; - }).toThrow(Error('Недопустимый возраст')); - }); - it('Name test', () => { - newPerson.name = 'John'; - expect(newPerson.name).toBe('John'); - }); - it('Sex basic test', () => { - newPerson.sex = 'Female'; - expect(newPerson.sex).toBe('female'); - }); - it('Sex uncorrect test', () => { - expect(() => { - newPerson.sex = 'dog'; - }).toThrow(Error('Недопустимый гендер')); - }); - }); - describe('Other methods tests', () => { - let newPerson = new Person('John', 20, 'Male'); - it('Should return name and sex', () => { - expect(newPerson.toString()).toEqual(`${newPerson.name} ${newPerson.sex}`); - }); - it('Should return age as number', () => { - expect(newPerson.toNumber()).toEqual(newPerson.age); - }); - it('Should return all properties', () => { - expect(newPerson.print()).toEqual(`first name: ${newPerson.name}\nsex: ${newPerson.sex}\nage: ${newPerson.age}`); - }); - }); -}); diff --git a/rpgsaga/saga/tests/sagaTests/Archer.spec.ts b/rpgsaga/saga/tests/sagaTests/Archer.spec.ts new file mode 100644 index 000000000..6b762e72e --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/Archer.spec.ts @@ -0,0 +1,131 @@ +import { Archer, Wizard } from '../../src/game/classes'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; +import { WeaponFabric } from '../../src/game/fabrics/weaponsFabric/WeaponFabric'; + +describe('Archer class methods tests', () => { + it('Constructor test', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newArcher = new Archer(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('bow'), [ + skillFabric.createSkillFromTemplate('огненные стрелы')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + expect(newArcher).toBeInstanceOf(Archer); + expect(newArcher.health).toBe(75); + expect(newArcher.strength).toBe(25); + expect(newArcher.name).toBe('Ibragim'); + }); + + describe('Get methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newArcher = new Archer(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('bow'), [ + skillFabric.createSkillFromTemplate('огненные стрелы')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Health get test', () => { + expect(newArcher.health).toBe(75); + }); + it('Strength get test', () => { + expect(newArcher.strength).toBe(25); + }); + it('Name get test', () => { + expect(newArcher.name).toBe('Ibragim'); + }); + it('ClassName get test', () => { + expect(newArcher.className).toBe('Archer'); + }); + it('IsAlive get test', () => { + expect(newArcher.isAlive).toBe(true); + }); + it('InitialHealth get test', () => { + expect(newArcher.initialHealth).toBe(75); + }); + it('InitialStrength get test', () => { + expect(newArcher.initialStrength).toBe(25); + }); + it('CountOfSkippingTurns get test', () => { + expect(newArcher.countOfSkipingTurns).toBe(0); + }); + it('CountOfSkippingTurns get test after using skipping spell', () => { + const opponent = new Wizard(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + opponent.useSkill(newArcher, 'заворожение'); + expect(newArcher.countOfSkipingTurns).toBe(1); + }); + }); + + describe('Archer methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newArcher = new Archer(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('bow'), [ + skillFabric.createSkillFromTemplate('огненные стрелы')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + const opponent = new Archer(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('bow'), [ + skillFabric.createSkillFromTemplate('огненные стрелы')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Should return health after an attack whithout using a skill', () => { + newArcher.attack(opponent); + expect(opponent.health).toBe(86 - (newArcher.strength + newArcher.weapon.damage)); + }); + + it('Health should decrease by the number of damage units', () => { + newArcher.takeDamage(45, opponent.currentSkill); + expect(newArcher.health).toBe(75 - 45); + }); + + it('Should change the propertie "skillUsed" to true', () => { + newArcher.choseSkill(); + newArcher.useSkill(opponent); + expect(newArcher.skills).toContain(newArcher.currentSkill); + }); + + it('Health should icnrease', () => { + newArcher.heal(10); + expect(newArcher.health).toBe(40); + }); + + it('Health should be equal initialHealth', () => { + newArcher.heal(100); + expect(newArcher.health).toBe(newArcher.initialHealth); + }); + + it('Ibragim should die.', () => { + newArcher.takeDamage(newArcher.initialHealth, opponent.currentSkill); + expect(newArcher.isAlive).toBe(false); + expect(newArcher.health).toBe(0); + }); + + it('Ibragim health should be equal 0.', () => { + newArcher.takeDamage(1000, opponent.currentSkill); + expect(newArcher.health).toBe(0); + }); + + it('Ibragim should reset.', () => { + newArcher.reset(); + expect(newArcher.health).toBe(newArcher.initialHealth); + expect(newArcher.strength).toBe(newArcher.initialStrength); + expect(newArcher.currentSkill).toBeUndefined(); + newArcher.skills!.forEach(skill => { + expect(skill.usageCount).toBe(skill.initialSkillUsage); + expect(skill.isUsed).toBe(false); + expect(skill.turns).toBe(skill.initialTurns); + }); + }); + + it('Ibragim strength should be equal initialStrength.', () => { + newArcher.useSkill(opponent, 'ледяные стрелы'); + newArcher.attack(opponent); + newArcher.useSkill(opponent, 'огненные стрелы'); + expect(newArcher.attack(opponent)).toBe(newArcher.strength + newArcher.weapon.damage + 5); + newArcher.attack(opponent); + expect(newArcher.attack(opponent)).toBe(newArcher.strength + newArcher.weapon.damage + 2); + }); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/ArcherFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/ArcherFabric.spec.ts new file mode 100644 index 000000000..dd7be64f4 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/ArcherFabric.spec.ts @@ -0,0 +1,73 @@ +import { ArcherFabric } from '../../src/game/fabrics/playersFabrics'; +import { Archer } from '../../src/game/classes'; +import { IWeapon } from '../../src/game/weapon/IWeapon'; +import { ISkill } from '../../src/game/skills/ISkill'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; + +class MockWeapon implements IWeapon { + name: string = 'Mock Weapon'; + typeOfDamage: string = 'Mock type'; + damage: number = 10; +} + +class MockSkill implements ISkill { + name: string = 'Mock Skill'; + description: string = 'Mock description'; + isUsed: boolean = false; + usageCount: number = 0; + initialSkillUsage: number = 0; + effect(): void {} +} + +class MockSkillFabric extends SkillFabric { + createSkillFromTemplate(templateName: string): ISkill | null { + if (templateName === 'ледяные стрелы') { + return new MockSkill(); + } else if (templateName === 'огненные стрелы') { + return new MockSkill(); + } + return null; + } +} + +describe('ArcherFabric tests', () => { + let archerFabric: ArcherFabric; + let mockSkillFabric: MockSkillFabric; + let mockWeapon: MockWeapon; + + beforeEach(() => { + mockSkillFabric = new MockSkillFabric(); + archerFabric = new ArcherFabric(); + mockWeapon = new MockWeapon(); + (archerFabric as any).skillFabric = mockSkillFabric; + }); + + it('Should create an archer with provided skills', () => { + const mockSkills: ISkill[] = [new MockSkill(), new MockSkill()]; + const newArcher = archerFabric.createArcher(['Alice', 'Bob'], 100, 20, mockWeapon, mockSkills); + expect(newArcher).toBeInstanceOf(Archer); + expect(newArcher.health).toBe(100); + expect(newArcher.strength).toBe(20); + expect(newArcher.weapon).toBe(mockWeapon); + expect(newArcher.skills).toBe(mockSkills); + }); + + it('Should create an archer with default skills if no skills are provided', () => { + const newArcher = archerFabric.createArcher(['Alice', 'Bob'], 100, 20, mockWeapon); + expect(newArcher).toBeInstanceOf(Archer); + expect(newArcher.health).toBe(100); + expect(newArcher.strength).toBe(20); + expect(newArcher.weapon).toBe(mockWeapon); + expect(newArcher.skills.length).toBe(2); + expect(newArcher.skills[0].usageCount).toBe(2); + expect(newArcher.skills[0].initialSkillUsage).toBe(2); + }); + + it('Should select a random name from the provided names array', () => { + const names = ['Alice', 'Bob', 'Charlie']; + const newArcher1 = archerFabric.createArcher(names, 100, 20, mockWeapon); + const newArcher2 = archerFabric.createArcher(names, 100, 20, mockWeapon); + expect(names.includes(newArcher1.name)).toBe(true); + expect(names.includes(newArcher2.name)).toBe(true); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/Game.spec.ts b/rpgsaga/saga/tests/sagaTests/Game.spec.ts new file mode 100644 index 000000000..9fd8eaee4 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/Game.spec.ts @@ -0,0 +1,95 @@ +import { Game } from '../../src/game/gameplay/Game'; +import { PlayerFabric } from '../../src/game/fabrics/playersFabrics'; +import { Player } from '../../src/game/abstract/Player'; +import { Logger } from '../../src/game/utils/output/Logger'; + +class MockLogger extends Logger { + messages: string[] = []; + attackLogs: string[] = []; + skillLogs: string[] = []; + deathLogs: string[] = []; + skipTurnLogs: string[] = []; + + messageLog(message: string): void { + this.messages.push(message); + } + attackLog(attacker: Player, defender: Player): void { + this.attackLogs.push(`${attacker.name} атакует ${defender.name}`); + } + skillLog(attacker: Player, defender: Player): void { + this.skillLogs.push(`${attacker.name} использует скилл на ${defender.name}`); + } + deathLog(player: Player): void { + this.deathLogs.push(`${player.name} умер`); + } + skipTurnLog(attacker: Player, defender: Player): void { + this.skipTurnLogs.push(`${attacker.name} пропускает ходы`); + } +} + +describe('Game tests', () => { + let game: Game; + let logger: MockLogger; + const playerFabric = new PlayerFabric(); + + beforeEach(() => { + logger = new MockLogger(); + game = new Game(2, undefined, logger); + }); + + it('Should start a game with two players', async () => { + await game.start(); + expect(logger.messages.length).toBeGreaterThan(0); + expect(logger.messages[0]).toBe('Игра началась!'); + expect(logger.messages[1]).toContain('Список участников'); + expect(logger.messages).toContain(`Победитель: (${game.players[0].className}) ${game.players[0].name}`); + }); + + it('Should handle a tournament with multiple players', async () => { + const players = playerFabric.createRandomPlayers(4); + const result = await game.tournament(players); + expect(result).toBeDefined(); + expect(result.health).toBeGreaterThan(0); + }); + + it('Should simulate a battle between two players', async () => { + const player1 = playerFabric.createRandomPlayer(); + const player2 = playerFabric.createRandomPlayer(); + const winner = await game.battle([player1, player2]); + expect(winner).toBeDefined(); + expect(winner.health).toBeGreaterThan(0); + expect(logger.attackLogs.length).toBeGreaterThan(0); + }); + + it('Should handle a battle where one player is already dead', async () => { + const player1 = playerFabric.createRandomPlayer(); + const player2 = playerFabric.createRandomPlayer(); + player2.takeDamage(player2.health); + const winner = await game.battle([player1, player2]); + expect(winner).toBe(player1); + }); + + it('Should handle a battle with skill usage', async () => { + const player1 = playerFabric.createRandomPlayer(); + const player2 = playerFabric.createRandomPlayer(); + await game.battle([player1, player2]); + expect(logger.skillLogs.length).toBeGreaterThanOrEqual(0); + }); + + it('Should correctly update the players array after a battle', async () => { + const player1 = playerFabric.createRandomPlayer(); + const player2 = playerFabric.createRandomPlayer(); + const winner = await game.battle([player1, player2]); + expect(winner.isAlive).toBe(true); + expect(winner.health).toBeGreaterThanOrEqual(1); + }); + + it('Should handle a game with a single player', async () => { + game = new Game(1, undefined, logger); + await game.start(); + expect(logger.messages.length).toBeGreaterThan(0); + expect(logger.messages[0]).toBe('Игра началась!'); + expect(logger.messages[1]).toContain('Список участников'); + expect(logger.messages[2]).toContain(`Победитель`); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/ISkill.spec.ts b/rpgsaga/saga/tests/sagaTests/ISkill.spec.ts new file mode 100644 index 000000000..bd8e7581d --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/ISkill.spec.ts @@ -0,0 +1,89 @@ +import { Player } from '../../src/game/abstract/Player'; +import { ISkill } from '../../src/game/skills/ISkill'; + +describe('ISkill interface tests', () => { + it('Should create a valid skill object', () => { + const skill: ISkill = { + name: 'Test Skill', + isUsed: false, + usageCount: 3, + initialSkillUsage: 3, + effect: (caster, opponent) => {}, + }; + expect(skill.name).toBe('Test Skill'); + expect(skill.isUsed).toBe(false); + expect(skill.usageCount).toBe(3); + expect(skill.initialSkillUsage).toBe(3); + expect(skill.effect).toBeDefined(); + }); + + it('Should create a valid skill object with damage function', () => { + const skill: ISkill = { + name: 'Damage Skill', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + damage: (caster: Player) => { + return caster.strength * 2; + }, + }; + expect(skill.name).toBe('Damage Skill'); + expect(skill.isUsed).toBe(false); + expect(skill.usageCount).toBe(1); + expect(skill.initialSkillUsage).toBe(1); + expect(skill.damage).toBeDefined(); + }); + + it('Should create a valid skill object with turns', () => { + const skill: ISkill = { + name: 'Timed Skill', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + turns: 3, + initialTurns: 3, + }; + expect(skill.name).toBe('Timed Skill'); + expect(skill.isUsed).toBe(false); + expect(skill.usageCount).toBe(1); + expect(skill.initialSkillUsage).toBe(1); + expect(skill.turns).toBe(3); + expect(skill.initialTurns).toBe(3); + }); + + it('Should create a valid skill object with buff', () => { + const skill: ISkill = { + name: 'Buff Skill', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + buff: { + strength: 5, + }, + }; + expect(skill.name).toBe('Buff Skill'); + expect(skill.isUsed).toBe(false); + expect(skill.usageCount).toBe(1); + expect(skill.initialSkillUsage).toBe(1); + expect(skill.buff).toBeDefined(); + expect(skill.buff!.strength).toBe(5); + }); + + it('Should handle a skill with optional properties', () => { + const skill: ISkill = { + name: 'Basic Skill', + isUsed: false, + usageCount: 1, + initialSkillUsage: 1, + }; + expect(skill.name).toBe('Basic Skill'); + expect(skill.isUsed).toBe(false); + expect(skill.usageCount).toBe(1); + expect(skill.initialSkillUsage).toBe(1); + expect(skill.damage).toBeUndefined(); + expect(skill.effect).toBeUndefined(); + expect(skill.buff).toBeUndefined(); + expect(skill.turns).toBeUndefined(); + expect(skill.initialTurns).toBeUndefined(); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/Knight.spec.ts b/rpgsaga/saga/tests/sagaTests/Knight.spec.ts new file mode 100644 index 000000000..c5e1e9a90 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/Knight.spec.ts @@ -0,0 +1,137 @@ +import { Archer, Knight, Wizard } from '../../src/game/classes'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; +import { WeaponFabric } from '../../src/game/fabrics/weaponsFabric/WeaponFabric'; + +describe('Knight class methods tests', () => { + it('Constructor test', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newKnight = new Knight(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('sword'), [ + skillFabric.createSkillFromTemplate('удар возмездия')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + expect(newKnight).toBeInstanceOf(Knight); + expect(newKnight.health).toBe(75); + expect(newKnight.strength).toBe(25); + expect(newKnight.name).toBe('Ibragim'); + }); + + describe('Get methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newKnight = new Knight(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('sword'), [ + skillFabric.createSkillFromTemplate('удар возмездия')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Health get test', () => { + expect(newKnight.health).toBe(75); + }); + it('Strength get test', () => { + expect(newKnight.strength).toBe(25); + }); + it('Name get test', () => { + expect(newKnight.name).toBe('Ibragim'); + }); + it('ClassName get test', () => { + expect(newKnight.className).toBe('Knight'); + }); + it('IsAlive get test', () => { + expect(newKnight.isAlive).toBe(true); + }); + it('InitialHealth get test', () => { + expect(newKnight.initialHealth).toBe(75); + }); + it('InitialStrength get test', () => { + expect(newKnight.initialStrength).toBe(25); + }); + it('CountOfSkippingTurns get test', () => { + expect(newKnight.countOfSkipingTurns).toBe(0); + }); + it('CountOfSkippingTurns get test after using skipping spell', () => { + const opponent = new Wizard(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + opponent.useSkill(newKnight, 'заворожение'); + expect(newKnight.countOfSkipingTurns).toBe(1); + }); + }); + + describe('Knight methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newKnight = new Knight(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('sword'), [ + skillFabric.createSkillFromTemplate('удар возмездия')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + const opponent = new Archer(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('bow'), [ + skillFabric.createSkillFromTemplate('огненные стрелы')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Should return health after an attack whithout using a skill', () => { + newKnight.attack(opponent); + expect(opponent.health).toBe(86 - (newKnight.strength + newKnight.weapon.damage)); + }); + + it('Health should decrease by the number of damage units', () => { + newKnight.takeDamage(45, opponent.currentSkill); + expect(newKnight.health).toBe(75 - 45); + }); + + it('Health should decrease by the number of damage units without skill buff', () => { + newKnight.heal(100); + opponent.useSkill(newKnight, 'ледяные стрелы'); + opponent.attack(newKnight); + expect(newKnight.health).toBe(75 - (opponent.strength + opponent.weapon.damage)); + }); + + it('Should change the propertie "skillUsed" to true', () => { + newKnight.choseSkill(); + newKnight.useSkill(opponent); + expect(newKnight.skills).toContain(newKnight.currentSkill); + }); + + it('Health should icnrease', () => { + newKnight.heal(10); + expect(newKnight.health).toBe(75 - (opponent.strength + opponent.weapon.damage) + 10); + }); + + it('Health should be equal initialHealth', () => { + newKnight.heal(100); + expect(newKnight.health).toBe(newKnight.initialHealth); + }); + + it('Ibragim should die.', () => { + newKnight.takeDamage(newKnight.initialHealth); + expect(newKnight.isAlive).toBe(false); + expect(newKnight.health).toBe(0); + }); + + it('Ibragim health should be equal 0.', () => { + newKnight.takeDamage(1000, opponent.currentSkill); + expect(newKnight.health).toBe(0); + }); + + it('Ibragim should reset.', () => { + newKnight.reset(); + expect(newKnight.health).toBe(newKnight.initialHealth); + expect(newKnight.strength).toBe(newKnight.initialStrength); + expect(newKnight.currentSkill).toBeUndefined(); + newKnight.skills!.forEach(skill => { + expect(skill.usageCount).toBe(skill.initialSkillUsage); + expect(skill.isUsed).toBe(false); + expect(skill.turns).toBe(skill.initialTurns); + }); + }); + + it('Ibragim strength should be equal initialStrength.', () => { + newKnight.useSkill(opponent, 'ледяные стрелы'); + newKnight.attack(opponent); + newKnight.attack(opponent); + newKnight.attack(opponent); + expect(newKnight.attack(opponent)).toBe(newKnight.strength + newKnight.weapon.damage); + }); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/KnightFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/KnightFabric.spec.ts new file mode 100644 index 000000000..da8277425 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/KnightFabric.spec.ts @@ -0,0 +1,71 @@ +import { KnightFabric } from '../../src/game/fabrics/playersFabrics'; +import { Knight } from '../../src/game/classes'; +import { IWeapon } from '../../src/game/weapon/IWeapon'; +import { ISkill } from '../../src/game/skills/ISkill'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; + +class MockWeapon implements IWeapon { + name: string = 'Mock Weapon'; + typeOfDamage: string = 'Mock type'; + damage: number = 10; +} + +class MockSkill implements ISkill { + name: string = 'Mock Skill'; + description: string = 'Mock description'; + isUsed: boolean = false; + usageCount: number = 0; + initialSkillUsage: number = 0; + effect(): void {} +} + +class MockSkillFabric extends SkillFabric { + createSkillFromTemplate(templateName: string): ISkill | null { + if (templateName === 'ледяные стрелы') { + return new MockSkill(); + } else if (templateName === 'удар возмездия') { + return new MockSkill(); + } + return null; + } +} + +describe('KnightFabric tests', () => { + let knightFabric: KnightFabric; + let mockSkillFabric: MockSkillFabric; + let mockWeapon: MockWeapon; + + beforeEach(() => { + mockSkillFabric = new MockSkillFabric(); + knightFabric = new KnightFabric(); + mockWeapon = new MockWeapon(); + (knightFabric as any).skillFabric = mockSkillFabric; + }); + + it('Should create a knight with provided skills', () => { + const mockSkills: ISkill[] = [new MockSkill(), new MockSkill()]; + const newKnight = knightFabric.createKnight(['Alice', 'Bob'], 100, 20, mockWeapon, mockSkills); + expect(newKnight).toBeInstanceOf(Knight); + expect(newKnight.health).toBe(100); + expect(newKnight.strength).toBe(20); + expect(newKnight.weapon).toBe(mockWeapon); + expect(newKnight.skills).toBe(mockSkills); + }); + + it('Should create an knight with default skills if no skills are provided', () => { + const newKnight = knightFabric.createKnight(['Alice', 'Bob'], 100, 20, mockWeapon); + expect(newKnight).toBeInstanceOf(Knight); + expect(newKnight.health).toBe(100); + expect(newKnight.strength).toBe(20); + expect(newKnight.weapon).toBe(mockWeapon); + expect(newKnight.skills.length).toBe(2); + }); + + it('Should select a random name from the provided names array', () => { + const names = ['Alice', 'Bob', 'Charlie']; + const newKnight1 = knightFabric.createKnight(names, 100, 20, mockWeapon); + const newKnight2 = knightFabric.createKnight(names, 100, 20, mockWeapon); + expect(names.includes(newKnight1.name)).toBe(true); + expect(names.includes(newKnight2.name)).toBe(true); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/PlayerFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/PlayerFabric.spec.ts new file mode 100644 index 000000000..e2f181efc --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/PlayerFabric.spec.ts @@ -0,0 +1,122 @@ +import { PlayerFabric } from '../../src/game/fabrics/playersFabrics'; +import { Player } from '../../src/game/abstract/Player'; +import { IWeapon } from '../../src/game/weapon/IWeapon'; +import { ISkill } from '../../src/game/skills/ISkill'; +import { ArcherFabric } from '../../src/game/fabrics/playersFabrics/ArcherFabric'; +import { KnightFabric } from '../../src/game/fabrics/playersFabrics/KnightFabric'; +import { WizardFabric } from '../../src/game/fabrics/playersFabrics/WizardFabric'; + +class MockWeapon { + name: string; + typeOfDamage: string; + damage: number; + constructor(name: string, typeOfDamage: string, damage: number) { + this.name = name; + this.typeOfDamage = typeOfDamage; + this.damage = damage; + } +} + +class MockArcherFabric extends ArcherFabric { + createArcher( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + return new MockPlayer('Archer', playerHealth, playerStrength, playerWeapon, playerSkills); + } +} + +class MockKnightFabric extends KnightFabric { + createKnight( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + return new MockPlayer('Knight', playerHealth, playerStrength, playerWeapon, playerSkills); + } +} + +class MockWizardFabric extends WizardFabric { + createWizard( + names: string[], + playerHealth: number, + playerStrength: number, + playerWeapon: IWeapon, + playerSkills: ISkill[] | null = null, + ): Player { + return new MockPlayer('Wizard', playerHealth, playerStrength, playerWeapon, playerSkills); + } +} + +class MockPlayer extends Player { + constructor(className: string, health: number, strength: number, weapon: IWeapon, skills: ISkill[] | null = null) { + super(health, strength, '', weapon, skills!); + this._className = className; + } +} + +class MockWeaponFabric { + createRandomWeapon(type: string, damageType: string): MockWeapon { + return new MockWeapon(type, damageType, 10); + } +} + +describe('PlayerFabric tests', () => { + let playerFabric: PlayerFabric; + let mockWeaponFabric: MockWeaponFabric; + let mockArcherFabric: MockArcherFabric; + let mockKnightFabric: MockKnightFabric; + let mockWizardFabric: MockWizardFabric; + + beforeEach(() => { + mockWeaponFabric = new MockWeaponFabric(); + mockArcherFabric = new MockArcherFabric(); + mockKnightFabric = new MockKnightFabric(); + mockWizardFabric = new MockWizardFabric(); + playerFabric = new PlayerFabric(); + (playerFabric as any).weaponFabric = mockWeaponFabric; + (playerFabric as any).archerFabric = mockArcherFabric; + (playerFabric as any).knightFabric = mockKnightFabric; + (playerFabric as any).wizardFabric = mockWizardFabric; + }); + + it('Should create a Knight', () => { + const player = playerFabric.createPlayer('Knight', 100, 20, new MockWeapon('Mock Weapon', 'Mock Type', 10)); + expect(player).toBeInstanceOf(Player); + expect(player?.className).toBe('Knight'); + }); + + it('Should create an Archer', () => { + const player = playerFabric.createPlayer('Archer', 100, 20, new MockWeapon('Mock Weapon', 'Mock Type', 10)); + expect(player).toBeInstanceOf(Player); + expect(player?.className).toBe('Archer'); + }); + + it('Should create a Wizard', () => { + const player = playerFabric.createPlayer('Wizard', 100, 20, new MockWeapon('Mock Weapon', 'Mock Type', 10)); + expect(player).toBeInstanceOf(Player); + expect(player?.className).toBe('Wizard'); + }); + + it('Should return undefined for an invalid class', () => { + const player = playerFabric.createPlayer('InvalidClass', 100, 20, new MockWeapon('Mock Weapon', 'Mock Type', 10)); + expect(player).toBeUndefined(); + }); + + it('Should create a random player', () => { + const player = playerFabric.createRandomPlayer(); + expect(player).toBeInstanceOf(Player); + expect(['Knight', 'Archer', 'Wizard']).toContain(player.className); + }); + + it('Should create random players', () => { + const players = playerFabric.createRandomPlayers(3); + expect(players.length).toBe(3); + players.forEach(player => expect(['Knight', 'Archer', 'Wizard']).toContain(player.className)); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/SkillFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/SkillFabric.spec.ts new file mode 100644 index 000000000..712e7055b --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/SkillFabric.spec.ts @@ -0,0 +1,77 @@ +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; +import { Archer } from '../../src/game/classes'; +import { WeaponFabric } from '../../src/game/fabrics/weaponsFabric/WeaponFabric'; + +describe('SkillFabric tests', () => { + const skillFabric = new SkillFabric(); + const weaponFabric = new WeaponFabric(); + + it('Should create a skill from template', () => { + const skill = skillFabric.createSkillFromTemplate('огненные стрелы'); + expect(skill).toBeDefined(); + expect(skill?.name).toBe('огненные стрелы'); + expect(skill?.usageCount).toBe(1); + expect(skill?.initialSkillUsage).toBe(1); + expect(skill?.effect).toBeUndefined(); + }); + + it('Should create a skill from template with turns', () => { + const skill = skillFabric.createSkillFromTemplate('ледяные стрелы'); + expect(skill).toBeDefined(); + expect(skill?.name).toBe('ледяные стрелы'); + expect(skill?.usageCount).toBe(1); + expect(skill?.initialSkillUsage).toBe(1); + expect(skill?.turns).toBe(3); + expect(skill?.initialTurns).toBe(3); + expect(skill?.effect).toBeUndefined(); + }); + + it('Should create a skill from template with damage calculation', () => { + const skill = skillFabric.createSkillFromTemplate('удар возмездия'); + expect(skill).toBeDefined(); + expect(skill?.name).toBe('удар возмездия'); + expect(skill?.damage).toBeDefined(); + + const player = new Archer(100, 20, '', weaponFabric.createRandomWeapon('bow'), []); + const opponent = new Archer(100, 10, '', weaponFabric.createRandomWeapon('bow'), []); + + skill?.effect!(player, opponent); + expect(opponent.health).toBe(100 - (20 * 1.3 + player.weapon.damage)); + }); + + it('Should create a skill from template with skip turns effect', () => { + const skill = skillFabric.createSkillFromTemplate('заворожение'); + expect(skill).toBeDefined(); + expect(skill?.name).toBe('заворожение'); + + const player = new Archer(100, 20, '', weaponFabric.createRandomWeapon('bow'), []); + const opponent = new Archer(100, 10, '', weaponFabric.createRandomWeapon('bow'), []); + + skill?.effect!(player, opponent); + expect(opponent.countOfSkipingTurns).toBe(1); + }); + + it('Should return null for an invalid template name', () => { + const skill = skillFabric.createSkillFromTemplate('invalidSkillName'); + expect(skill).toBeNull(); + }); + + it('Should correctly apply skill effects', () => { + const player = new Archer(100, 20, '', weaponFabric.createRandomWeapon('bow'), []); + const opponent = new Archer(100, 10, '', weaponFabric.createRandomWeapon('bow'), []); + + const fireArrows = skillFabric.createSkillFromTemplate('огненные стрелы')!; + expect(player.strength + fireArrows.buff.strength).toBe(22); + + const iceArrows = skillFabric.createSkillFromTemplate('ледяные стрелы')!; + expect(player.strength + iceArrows.buff.strength).toBe(23); + + const vengeanceStrike = skillFabric.createSkillFromTemplate('удар возмездия')!; + vengeanceStrike.effect!(player, opponent); + expect(opponent.health).toBeLessThan(100); + + const enchantment = skillFabric.createSkillFromTemplate('заворожение')!; + enchantment.effect!(player, opponent); + expect(opponent.countOfSkipingTurns).toBe(1); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/WeaponFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/WeaponFabric.spec.ts new file mode 100644 index 000000000..ec754c6cd --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/WeaponFabric.spec.ts @@ -0,0 +1,59 @@ +import { WeaponFabric } from '../../src/game/fabrics/weaponsFabric/WeaponFabric'; + +describe('WeaponFabric', () => { + const weaponFabric = new WeaponFabric(); + + it('should create a sword', () => { + const weapon = weaponFabric.createWeapon('sword', 'Dragonsbane', 10); + expect(weapon.name).toBe('Dragonsbane'); + expect(weapon.damage).toBe(10); + }); + + it('should create a stick', () => { + const weapon = weaponFabric.createWeapon('stick', 'Oak Staff', 5); + expect(weapon.name).toBe('Oak Staff'); + expect(weapon.damage).toBe(5); + }); + + it('should create a bow', () => { + const weapon = weaponFabric.createWeapon('bow', 'Longbow', 8); + expect(weapon.name).toBe('Longbow'); + expect(weapon.damage).toBe(8); + }); + + it('should create fists as default', () => { + const weapon = weaponFabric.createWeapon('invalidType', 'invalidName', 0); + expect(weapon.name).toBe('fists'); + expect(weapon.damage).toBe(3); + }); + + it('should create a random sword', () => { + const weapon = weaponFabric.createRandomWeapon('sword'); + expect(weapon.name).toBeDefined(); + expect(weapon.damage).toBeGreaterThanOrEqual(2); + expect(weapon.damage).toBeLessThanOrEqual(10); + expect(['Dragonsbane', 'Stormbringer', 'Aethelred']).toContain(weapon.name); + }); + + it('should create a random stick', () => { + const weapon = weaponFabric.createRandomWeapon('stick'); + expect(weapon.name).toBeDefined(); + expect(weapon.damage).toBeGreaterThanOrEqual(2); + expect(weapon.damage).toBeLessThanOrEqual(10); + expect(['Oak Staff', 'Elderwood Branch', "Shepherd's Crook"]).toContain(weapon.name); + }); + + it('should create a random bow', () => { + const weapon = weaponFabric.createRandomWeapon('bow'); + expect(weapon.name).toBeDefined(); + expect(weapon.damage).toBeGreaterThanOrEqual(2); + expect(weapon.damage).toBeLessThanOrEqual(10); + expect(["Hunter's Bow", 'Longbow', 'Shortbow']).toContain(weapon.name); + }); + + it('should handle invalid weapon type in createRandomWeapon', () => { + const weapon = weaponFabric.createRandomWeapon('invalidType'); + expect(weapon.name).toBe('fists'); + expect(weapon.damage).toBe(3); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/Wizard.spec.ts b/rpgsaga/saga/tests/sagaTests/Wizard.spec.ts new file mode 100644 index 000000000..6eb70dc93 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/Wizard.spec.ts @@ -0,0 +1,130 @@ +import { Wizard } from '../../src/game/classes'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; +import { WeaponFabric } from '../../src/game/fabrics/weaponsFabric/WeaponFabric'; + +describe('Wizard class methods tests', () => { + it('Constructor test', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newWizard = new Wizard(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + expect(newWizard).toBeInstanceOf(Wizard); + expect(newWizard.health).toBe(75); + expect(newWizard.strength).toBe(25); + expect(newWizard.name).toBe('Ibragim'); + }); + + describe('Get methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newWizard = new Wizard(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Health get test', () => { + expect(newWizard.health).toBe(75); + }); + it('Strength get test', () => { + expect(newWizard.strength).toBe(25); + }); + it('Name get test', () => { + expect(newWizard.name).toBe('Ibragim'); + }); + it('ClassName get test', () => { + expect(newWizard.className).toBe('Wizard'); + }); + it('IsAlive get test', () => { + expect(newWizard.isAlive).toBe(true); + }); + it('InitialHealth get test', () => { + expect(newWizard.initialHealth).toBe(75); + }); + it('InitialStrength get test', () => { + expect(newWizard.initialStrength).toBe(25); + }); + it('CountOfSkippingTurns get test', () => { + expect(newWizard.countOfSkipingTurns).toBe(0); + }); + it('CountOfSkippingTurns get test after using skipping spell', () => { + const opponent = new Wizard(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + opponent.useSkill(newWizard, 'заворожение'); + expect(newWizard.countOfSkipingTurns).toBe(1); + }); + }); + + describe('Wizard methods tests', () => { + const weaponFabric = new WeaponFabric(); + const skillFabric = new SkillFabric(); + const newWizard = new Wizard(75, 25, 'Ibragim', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + const opponent = new Wizard(86, 26, 'Mustafa', weaponFabric.createRandomWeapon('stick'), [ + skillFabric.createSkillFromTemplate('заворожение')!, + skillFabric.createSkillFromTemplate('ледяные стрелы')!, + ]); + + it('Should return health after an attack whithout using a skill', () => { + newWizard.attack(opponent); + expect(opponent.health).toBe(86 - (newWizard.strength + newWizard.weapon.damage)); + }); + + it('Health should decrease by the number of damage units', () => { + newWizard.takeDamage(45, opponent.currentSkill); + expect(newWizard.health).toBe(75 - 45); + }); + + it('Should change the propertie "skillUsed" to true', () => { + newWizard.choseSkill(); + newWizard.useSkill(opponent); + expect(newWizard.skills).toContain(newWizard.currentSkill); + }); + + it('Health should icnrease', () => { + newWizard.heal(10); + expect(newWizard.health).toBe(40); + }); + + it('Health should be equal initialHealth', () => { + newWizard.heal(100); + expect(newWizard.health).toBe(newWizard.initialHealth); + }); + + it('Ibragim should die.', () => { + newWizard.takeDamage(newWizard.initialHealth, opponent.currentSkill); + expect(newWizard.isAlive).toBe(false); + expect(newWizard.health).toBe(0); + }); + + it('Ibragim health should be equal 0.', () => { + newWizard.takeDamage(1000, opponent.currentSkill); + expect(newWizard.health).toBe(0); + }); + + it('Ibragim should reset.', () => { + newWizard.reset(); + expect(newWizard.health).toBe(newWizard.initialHealth); + expect(newWizard.strength).toBe(newWizard.initialStrength); + expect(newWizard.currentSkill).toBeUndefined(); + newWizard.skills!.forEach(skill => { + expect(skill.usageCount).toBe(skill.initialSkillUsage); + expect(skill.isUsed).toBe(false); + expect(skill.turns).toBe(skill.initialTurns); + }); + }); + + it('Ibragim strength should be equal initialStrength.', () => { + newWizard.useSkill(opponent, 'ледяные стрелы'); + newWizard.attack(opponent); + newWizard.attack(opponent); + newWizard.attack(opponent); + expect(newWizard.attack(opponent)).toBe(newWizard.strength + newWizard.weapon.damage); + }); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/WizardFabric.spec.ts b/rpgsaga/saga/tests/sagaTests/WizardFabric.spec.ts new file mode 100644 index 000000000..a268d2e90 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/WizardFabric.spec.ts @@ -0,0 +1,71 @@ +import { WizardFabric } from '../../src/game/fabrics/playersFabrics'; +import { Wizard } from '../../src/game/classes'; +import { IWeapon } from '../../src/game/weapon/IWeapon'; +import { ISkill } from '../../src/game/skills/ISkill'; +import { SkillFabric } from '../../src/game/fabrics/skillFabric/SkillFabric'; + +class MockWeapon implements IWeapon { + name: string = 'Mock Weapon'; + typeOfDamage: string = 'Mock type'; + damage: number = 10; +} + +class MockSkill implements ISkill { + name: string = 'Mock Skill'; + description: string = 'Mock description'; + isUsed: boolean = false; + usageCount: number = 0; + initialSkillUsage: number = 0; + effect(): void {} +} + +class MockSkillFabric extends SkillFabric { + createSkillFromTemplate(templateName: string): ISkill | null { + if (templateName === 'ледяные стрелы') { + return new MockSkill(); + } else if (templateName === 'заворожение') { + return new MockSkill(); + } + return null; + } +} + +describe('WizardFabric tests', () => { + let wizardFabric: WizardFabric; + let mockSkillFabric: MockSkillFabric; + let mockWeapon: MockWeapon; + + beforeEach(() => { + mockSkillFabric = new MockSkillFabric(); + wizardFabric = new WizardFabric(); + mockWeapon = new MockWeapon(); + (wizardFabric as any).skillFabric = mockSkillFabric; + }); + + it('Should create a wizard with provided skills', () => { + const mockSkills: ISkill[] = [new MockSkill(), new MockSkill()]; + const newWizard = wizardFabric.createWizard(['Alice', 'Bob'], 100, 20, mockWeapon, mockSkills); + expect(newWizard).toBeInstanceOf(Wizard); + expect(newWizard.health).toBe(100); + expect(newWizard.strength).toBe(20); + expect(newWizard.weapon).toBe(mockWeapon); + expect(newWizard.skills).toBe(mockSkills); + }); + + it('Should create an wizard with default skills if no skills are provided', () => { + const newWizard = wizardFabric.createWizard(['Alice', 'Bob'], 100, 20, mockWeapon); + expect(newWizard).toBeInstanceOf(Wizard); + expect(newWizard.health).toBe(100); + expect(newWizard.strength).toBe(20); + expect(newWizard.weapon).toBe(mockWeapon); + expect(newWizard.skills.length).toBe(2); + }); + + it('Should select a random name from the provided names array', () => { + const names = ['Alice', 'Bob', 'Charlie']; + const newWizard1 = wizardFabric.createWizard(names, 100, 20, mockWeapon); + const newWizard2 = wizardFabric.createWizard(names, 100, 20, mockWeapon); + expect(names.includes(newWizard1.name)).toBe(true); + expect(names.includes(newWizard2.name)).toBe(true); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/getRandomArrayElement.spec.ts b/rpgsaga/saga/tests/sagaTests/getRandomArrayElement.spec.ts new file mode 100644 index 000000000..f4923975a --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/getRandomArrayElement.spec.ts @@ -0,0 +1,38 @@ +import { getRandomArrayElement } from '../../src/game/utils/randomization/getRandomArrayElement'; + +describe('getRandomArrayElement tests', () => { + it('Should return undefined for an empty array', () => { + expect(getRandomArrayElement([])).toBeUndefined(); + }); + + it('Should return a random element from a non-empty array', () => { + const arr = [1, 2, 3, 4, 5]; + const element = getRandomArrayElement(arr); + expect(arr).toContain(element!); + }); + + it('Should return a random element from a non-empty array - multiple tests', () => { + const arr = [1, 2, 3, 4, 5]; + for (let i = 0; i < 100; i++) { + const element = getRandomArrayElement(arr); + expect(arr).toContain(element!); + } + }); + + it('Should handle arrays with only one element', () => { + const arr = [1]; + expect(getRandomArrayElement(arr)).toBe(1); + }); + + it('Should handle arrays with different data types', () => { + const arr = [1, 'hello', true, { name: 'test' }]; + const element = getRandomArrayElement(arr); + expect(arr).toContain(element!); + }); + + it('Should handle large arrays correctly', () => { + const arr = Array.from({ length: 1000 }, (_, i) => i + 1); + const element = getRandomArrayElement(arr); + expect(arr).toContain(element!); + }); +}); diff --git a/rpgsaga/saga/tests/sagaTests/getRandomNumber.spec.ts b/rpgsaga/saga/tests/sagaTests/getRandomNumber.spec.ts new file mode 100644 index 000000000..a278888d6 --- /dev/null +++ b/rpgsaga/saga/tests/sagaTests/getRandomNumber.spec.ts @@ -0,0 +1,61 @@ +import { getRandomNumber } from '../../src/game/utils/randomization/getRandomNumber'; + +describe('getRandomNumber tests', () => { + it('Should return -1 if min is greater than max', () => { + expect(getRandomNumber(10, 5)).toBe(-1); + }); + + it('Should return min if min equals max', () => { + expect(getRandomNumber(5, 5)).toBe(5); + }); + + it('Should return a random number within the range [min, max] (inclusive)', () => { + const min = 1; + const max = 10; + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + }); + + it('Should return a random number within the range [min, max] (inclusive) - multiple tests', () => { + const min = 1; + const max = 10; + for (let i = 0; i < 100; i++) { + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + } + }); + + it('Should handle large ranges correctly', () => { + const min = 1; + const max = 100000; + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + }); + + it('Should handle negative ranges correctly', () => { + const min = -10; + const max = 0; + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + }); + + it('Should handle zero as min correctly', () => { + const min = 0; + const max = 10; + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + }); + + it('Should handle zero as max correctly', () => { + const min = -10; + const max = 0; + const randomNumber = getRandomNumber(min, max); + expect(randomNumber).toBeGreaterThanOrEqual(min); + expect(randomNumber).toBeLessThanOrEqual(max); + }); +});