diff --git a/index.html b/index.html index 26d3120..9ba6862 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,8 @@ + + diff --git a/src/Animals/Predator.ts b/src/Animals/Predator.ts new file mode 100644 index 0000000..49eed2f --- /dev/null +++ b/src/Animals/Predator.ts @@ -0,0 +1,37 @@ +import { AnimalNames } from '../Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../Enums/AnimalRolesEnum'; +import { AttackHerdInterface } from '../Interfaces/AttackHerdInterface'; +import { Animal } from './Animal'; + +export class Predator extends Animal implements AttackHerdInterface { + protected _kills: AnimalNames[]; + protected _isChasedAwayBy: AnimalNames; + protected _exclamation: string; + constructor( + name: AnimalNames, + imagePath: string, + roles: AnimalRoles = AnimalRoles.PREDATOR, + kills: AnimalNames[], + isChasedAwayBy: AnimalNames, + exclamation: string, + ) { + super(name, imagePath, undefined, roles); + this._kills = kills; + this._isChasedAwayBy = isChasedAwayBy; + this._exclamation = exclamation; + } + + get kills(): AnimalNames[] { + return this._kills; + } + get isChasedAwayBy(): AnimalNames { + return this._isChasedAwayBy; + } + get exclamation(): string { + return this._exclamation; + } + + attackHerd(): string { + return this._exclamation; + } +} diff --git a/src/Animals/Protector.ts b/src/Animals/Protector.ts new file mode 100644 index 0000000..61280fe --- /dev/null +++ b/src/Animals/Protector.ts @@ -0,0 +1,37 @@ +import { AnimalNames } from '../Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../Enums/AnimalRolesEnum'; +import { ProtectHerdInterface } from '../Interfaces/ProtectHerdInterface'; +import { Animal, Value } from './Animal'; + +export class Protector + extends Animal + implements ProtectHerdInterface { + protected _chasesAway: AnimalNames; + protected _exclamation: string; + constructor( + name: AnimalNames, + imagePath: string, + role: AnimalRoles = AnimalRoles.PREDATOR, + value: Value, + chasesAway: AnimalNames, + exclamation: string, + ) { + super(name, imagePath, value, role); + this._chasesAway = chasesAway; + this._exclamation = exclamation; + } + + get chasesAway(): AnimalNames { + if (!this._chasesAway) throw new Error('nothing to chase away'); + return this._chasesAway; + } + get exclamation(): string { + if (!this._exclamation) return ''; + return this._exclamation; + } + + protectHerd(): string { + if (!this._exclamation) return ''; + return this._exclamation; + } +} diff --git a/src/Interfaces/AttackHerdInterface.ts b/src/Interfaces/AttackHerdInterface.ts index 4ac3c53..40a283c 100644 --- a/src/Interfaces/AttackHerdInterface.ts +++ b/src/Interfaces/AttackHerdInterface.ts @@ -1,4 +1,4 @@ export interface AttackHerdInterface { - // TODO: tweak when herd is ready + // TODO: DEFINE THE ACTION FOR ATTACK attackHerd(): string; } diff --git a/src/Interfaces/GameConfigInterface.ts b/src/Interfaces/GameConfigInterface.ts index f124104..e56a7b9 100644 --- a/src/Interfaces/GameConfigInterface.ts +++ b/src/Interfaces/GameConfigInterface.ts @@ -1,5 +1,6 @@ import { GameModes } from '../Enums/GameModeEnums'; import { LivestockConfigInterface } from './LivestockConfigInterface'; +import { PlayerDTO } from './PlayerDTOInterface'; import { PredatorsConfigInterface } from './PredatorsConfigInterface'; import { ProtectorsConfigInterface } from './ProtectorsConfigInterface'; @@ -7,7 +8,7 @@ export interface GameConfigInterface { mode: GameModes; roundTimeInSeconds: number; totalGameTimeInSeconds?: number; - playersConfig: { name: string; path: string; color: string }[]; + playersConfig: PlayerDTO[]; livestockConfig: LivestockConfigInterface[]; protectorsConfig: ProtectorsConfigInterface[]; predatorsConfig: PredatorsConfigInterface[]; diff --git a/src/Interfaces/HerdConfigInterface.ts b/src/Interfaces/HerdConfigInterface.ts index 4939e12..0305541 100644 --- a/src/Interfaces/HerdConfigInterface.ts +++ b/src/Interfaces/HerdConfigInterface.ts @@ -9,4 +9,5 @@ export interface HerdConfigInterface { path: string; inStock: number; chasesAway?: AnimalNames; + exclamation?: string; } diff --git a/src/Interfaces/PredatorsConfigInterface.ts b/src/Interfaces/PredatorsConfigInterface.ts index a5ef595..e362d54 100644 --- a/src/Interfaces/PredatorsConfigInterface.ts +++ b/src/Interfaces/PredatorsConfigInterface.ts @@ -1,9 +1,12 @@ +import { AnimalRoles } from '../Enums/AnimalRolesEnum'; import { AnimalNames } from '../Enums/AnimalNamesEnum'; export interface PredatorsConfigInterface { name: AnimalNames; path: string; + roles: AnimalRoles; kills: AnimalNames[]; - isChasedAwayBy: AnimalNames[]; + isChasedAwayBy: AnimalNames; + exclamation: string; dice?: { diceNumber: number; probability: number }[]; } diff --git a/src/Interfaces/ProtectorsConfigInterface.ts b/src/Interfaces/ProtectorsConfigInterface.ts index ec7d913..232cebe 100644 --- a/src/Interfaces/ProtectorsConfigInterface.ts +++ b/src/Interfaces/ProtectorsConfigInterface.ts @@ -4,4 +4,5 @@ import { LivestockConfigInterface } from './LivestockConfigInterface'; export interface ProtectorsConfigInterface extends LivestockConfigInterface { chasesAway: AnimalNames; + exclamation: string; } diff --git a/src/Interfaces/DiceInterface.ts b/src/Interfaces/RandomAnimalInterface.ts similarity index 69% rename from src/Interfaces/DiceInterface.ts rename to src/Interfaces/RandomAnimalInterface.ts index 5efe9e1..55e0baa 100644 --- a/src/Interfaces/DiceInterface.ts +++ b/src/Interfaces/RandomAnimalInterface.ts @@ -1,5 +1,5 @@ import { AnimalNames } from '../Enums/AnimalNamesEnum'; -export interface GetRandomValue { +export interface RandomAnimalInterface { getRandomValue(): AnimalNames; } diff --git a/src/Player.ts b/src/Player.ts index 3948423..c5ad28a 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -1,5 +1,6 @@ +import { defaultPlayerHerdConfig } from './app/logic/defaultHerdConfig'; import { Herd } from './app/logic/Herd'; -import { mockHerdConfig } from './app/logic/mockHerdConfig'; +// import { mockHerdConfig } from './app/logic/defaultHerdConfig'; import { HerdConfigInterface } from './Interfaces/HerdConfigInterface'; export class Player { @@ -8,12 +9,11 @@ export class Player { protected herd: Herd; protected color: string; - // TODO: set path to default avatar when it's available constructor( name: string, avatar = 'path to default avatar', color = 'green', - herdConfig: HerdConfigInterface[] = mockHerdConfig, + herdConfig: HerdConfigInterface[] = defaultPlayerHerdConfig, ) { this.name = name; this.herd = new Herd(herdConfig); diff --git a/src/app/BreedProcessor.ts b/src/app/BreedProcessor.ts index 3cd4088..9d8b191 100644 --- a/src/app/BreedProcessor.ts +++ b/src/app/BreedProcessor.ts @@ -1,15 +1,12 @@ -import { GetRandomValue } from '../Interfaces/DiceInterface'; -import { FirstDice } from './FirstDice'; -import { SecondDice } from './SecondDice'; +import { RandomAnimalInterface } from '../Interfaces/RandomAnimalInterface'; import { Player } from '../Player'; import { AnimalNames } from '../Enums/AnimalNamesEnum'; -import { Fox } from '../Animals/Fox'; -import { Wolf } from '../Animals/Wolf'; import { Herd } from './logic/Herd'; -import { add, divide, floor, min } from 'lodash'; -import { AnimalRoles } from '../Enums/AnimalRolesEnum'; +import { add, divide, floor, min, remove } from 'lodash'; import { Bank } from './logic/Bank'; -import { ConvertAnimalName } from './utils/ConvertAnimalName'; +import { PredatorsConfigInterface } from '../Interfaces/PredatorsConfigInterface'; +import { GameModes } from '../Enums/GameModeEnums'; +import { Predator } from '../../src/Animals/Predator'; export type RollResult = { rollResult: AnimalNames[]; @@ -17,48 +14,70 @@ export type RollResult = { }; export class BreedProcessor { - randomResultInterfaceWolf: GetRandomValue; - randomResultInterfaceFox: GetRandomValue; + predators: Predator[]; - constructor(private bank: Bank) { - this.randomResultInterfaceWolf = new SecondDice(); - this.randomResultInterfaceFox = new FirstDice(); + constructor( + private bank: Bank, + private firstDice: RandomAnimalInterface, + private secondDice: RandomAnimalInterface, + predatorConfig: PredatorsConfigInterface[], + private mode: GameModes, + ) { + this.predators = predatorConfig.map( + ({ name, path, roles, kills, isChasedAwayBy, exclamation }) => { + return new Predator( + name, + path, + roles, + kills, + isChasedAwayBy, + exclamation, + ); + }, + ); + } + + getPredatorByName(predatorName: AnimalNames): Predator { + const attackingPredator = this.predators.find( + (predator) => predator.theName === predatorName, + ); + if (!attackingPredator) throw new Error('No such predator'); + return attackingPredator; } processBreedPhase({ theHerd }: Player): RollResult { - const wolfDiceResult = this.randomResultInterfaceWolf.getRandomValue(); - const foxDiceResult = this.randomResultInterfaceFox.getRandomValue(); - const equalResult = foxDiceResult === wolfDiceResult; - const roll = [wolfDiceResult, foxDiceResult]; - if (equalResult) { - const count = this.breedAnimals(foxDiceResult, theHerd, true); - return { rollResult: roll, gain: [[foxDiceResult, count]] }; + const roll = [ + this.firstDice.getRandomValue(), + this.secondDice.getRandomValue(), + ]; + const [firstDice, secondDice] = roll; + if (firstDice === secondDice) { + const count = this.breedAnimals(firstDice, theHerd, true); + return { + rollResult: roll, + gain: count ? [[firstDice, count]] : [], + }; } const gain: [AnimalNames, number][] = []; - if (foxDiceResult === AnimalNames.FOX) { - const fox: Fox = ConvertAnimalName.toAnimalObject( - foxDiceResult, - ) as Fox; - this.returnToBank(fox, theHerd); - theHerd.cullAnimals(fox); - } else { - gain.push([ - foxDiceResult, - this.breedAnimals(foxDiceResult, theHerd), - ]); - } - if (wolfDiceResult === AnimalNames.WOLF) { - const wolf = ConvertAnimalName.toAnimalObject( - wolfDiceResult, - ) as Wolf; - this.returnToBank(wolf, theHerd); - theHerd.cullAnimals(wolf); - } else { - gain.push([ - wolfDiceResult, - this.breedAnimals(wolfDiceResult, theHerd), - ]); - } + roll + .filter((animal) => !this.isPredator(animal)) + .forEach((animal) => { + const grow = this.breedAnimals(animal, theHerd); + if (grow) { + gain.push([animal, grow]); + } + }); + roll + .filter((animal) => this.isPredator(animal)) + .forEach((animal) => { + const predator = this.getPredatorByName(animal); + const isHerdCulled = this.returnToBank(predator, theHerd); + if (isHerdCulled && gain.length > 0) { + this.reduceGain(predator, gain); + } + predator.attackHerd(); + theHerd.cullAnimals(predator, this.mode); + }); return { rollResult: roll, gain: gain }; } @@ -83,28 +102,24 @@ export class BreedProcessor { return herdGrow; } - private returnToBank(predator: Wolf | Fox, herd: Herd): void { - if (predator instanceof Fox) { - if (herd.getAnimalNumber(AnimalNames.SMALL_DOG) > 0) { - this.bank.theHerd.addAnimalsToHerd(AnimalNames.SMALL_DOG, 1); - return; - } - const quantity = herd.getAnimalNumber(AnimalNames.RABBIT); - this.bank.theHerd.addAnimalsToHerd( - AnimalNames.RABBIT, - quantity, - ); - return; - } - if (herd.getAnimalNumber(AnimalNames.BIG_DOG) > 0) { - this.bank.theHerd.addAnimalsToHerd(AnimalNames.BIG_DOG, 1); - return; + private isPredator(animal: AnimalNames): boolean { + return this.predators.some(({ theName }) => theName === animal); + } + + private returnToBank(predator: Predator, herd: Herd): boolean { + const animalsToCull = predator.kills; + const protector = predator.isChasedAwayBy; + const hasProtector = herd.getAnimalNumber(protector); + const isDynamicMode = this.mode === GameModes.DYNAMIC; + const killsRabbits = animalsToCull.includes(AnimalNames.RABBIT); + if (hasProtector) { + this.bank.theHerd.addAnimalsToHerd(protector, 1); + return false; } + herd.theAnimals - .filter( - ([animal]) => - animal.hasRole(AnimalRoles.LIVESTOCK) && - animal.theName !== AnimalNames.HORSE, + .filter(([animal]) => + animalsToCull.includes(animal.theName as AnimalNames), ) .forEach(([animal, count]) => this.bank.theHerd.addAnimalsToHerd( @@ -112,6 +127,10 @@ export class BreedProcessor { count, ), ); + if (isDynamicMode && killsRabbits) { + this.bank.theHerd.removeAnimalsFromHerd(AnimalNames.RABBIT, 1); + } + return true; } private calculateHerdGrow( @@ -130,4 +149,13 @@ export class BreedProcessor { ); return min([herdMaxGrow, bankContains]) as number; } + + private reduceGain( + predator: Predator, + animalsGain: [AnimalNames, number][], + ): void { + remove(animalsGain, ([animal]) => { + return predator.kills.includes(animal); + }); + } } diff --git a/src/app/Dice.ts b/src/app/Dice.ts index 039501f..91418d0 100644 --- a/src/app/Dice.ts +++ b/src/app/Dice.ts @@ -1,10 +1,28 @@ -import { GetRandomValue } from '../Interfaces/DiceInterface'; +import { RandomAnimalInterface } from '../Interfaces/RandomAnimalInterface'; import { AnimalNames } from '../Enums/AnimalNamesEnum'; -import { sample } from 'lodash'; +import { sample, shuffle } from 'lodash'; -export class Dice implements GetRandomValue { - constructor(private diceSides: AnimalNames[]) {} +export class Dice implements RandomAnimalInterface { + private dice: AnimalNames[] = []; + + /** + * adds animals names to dice sides in quantity of probability + * @param animal accepts AnimalNames with desired animal on side + * @param probability accepts number with number of sides with this animal + */ + addSide(animal: AnimalNames, probability: number): void { + if (probability === 0) { + return; + } + this.dice.push(animal); + this.addSide(animal, probability - 1); + } + + /** + * Returns random animal + */ getRandomValue(): AnimalNames { - return sample(this.diceSides) as AnimalNames; + this.dice = shuffle(this.dice); + return sample(this.dice) as AnimalNames; } } diff --git a/src/app/DiceBuilder.ts b/src/app/DiceBuilder.ts new file mode 100644 index 0000000..26e022a --- /dev/null +++ b/src/app/DiceBuilder.ts @@ -0,0 +1,41 @@ +import { LivestockConfigInterface } from '../Interfaces/LivestockConfigInterface'; +import { PredatorsConfigInterface } from '../Interfaces/PredatorsConfigInterface'; +import { ProtectorsConfigInterface } from '../Interfaces/ProtectorsConfigInterface'; +import { Dice } from './Dice'; + +/** + * Builds two dice, depends on configuration. + * Usage: DiceBuilder.build() + */ +export class DiceBuilder { + static build( + livestockConfig: LivestockConfigInterface[], + predatorsConfig: PredatorsConfigInterface[], + protectorsConfig: ProtectorsConfigInterface[], + ): Dice[] { + const firstDice = new Dice(); + const secondDice = new Dice(); + for (const animal of [ + ...livestockConfig, + ...predatorsConfig, + ...protectorsConfig, + ]) { + if (!animal.dice) { + continue; + } + animal.dice.forEach(({ diceNumber, probability }) => { + switch (diceNumber) { + case 1: + firstDice.addSide(animal.name, probability); + break; + case 2: + secondDice.addSide(animal.name, probability); + break; + default: + throw Error(`Error: Unknown dice number: ${diceNumber}`); + } + }); + } + return [firstDice, secondDice]; + } +} diff --git a/src/app/FirstDice.ts b/src/app/FirstDice.ts deleted file mode 100644 index c8b5a1a..0000000 --- a/src/app/FirstDice.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Dice } from './Dice'; -import { AnimalNames } from '../Enums/AnimalNamesEnum'; - -export class FirstDice extends Dice { - constructor() { - super([ - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.SHEEP, - AnimalNames.SHEEP, - AnimalNames.PIG, - AnimalNames.PIG, - AnimalNames.HORSE, - AnimalNames.FOX, - ]); - } -} diff --git a/src/app/GameController.ts b/src/app/GameController.ts index 985441e..55f8f81 100644 --- a/src/app/GameController.ts +++ b/src/app/GameController.ts @@ -1,15 +1,15 @@ import { GameProcessor } from './logic/GameProcessor'; import { ViewController } from './ViewController'; import { Game } from './logic/Game'; -import { defaultGameConfiguration } from './logic/defaultGameConfiguration'; import { Bank } from './logic/Bank'; +import { Configuration } from './logic/Configuration'; export class GameController { private game: Game; private gameProcessor: GameProcessor; constructor( private view: ViewController, - private config = defaultGameConfiguration, + private config: Configuration, ) { this.game = new Game(config); this.gameProcessor = new GameProcessor(this.game, this); @@ -42,7 +42,6 @@ export class GameController { private isGameWon(): void { if (this.gameProcessor.checkWin()) { - this.gameProcessor.stopTurn(); this.view.displayWinModal(this.game.theCurrentPlayer); } } diff --git a/src/app/GameView.ts b/src/app/GameView.ts index 5360178..cbe1076 100644 --- a/src/app/GameView.ts +++ b/src/app/GameView.ts @@ -9,7 +9,7 @@ import { ViewController } from './ViewController'; export class GameView { private playerPanel: PlayerPanel; - + // TODO: FIND OUT WHY THIS CREATES INSTANCE OF PLAYER PANEL IN CONTRUCTOR constructor(private view: ViewController) { this.playerPanel = new PlayerPanel(this); } diff --git a/src/app/MenuView.ts b/src/app/MenuView.ts index b76d120..040ce4e 100644 --- a/src/app/MenuView.ts +++ b/src/app/MenuView.ts @@ -6,107 +6,74 @@ import { ViewController } from './ViewController'; export class MenuView extends EmptyView { private modeModal: ModeView; + private menuViewContent: HTMLElement; constructor(private viewController: ViewController) { super(false); const backCallback = () => this.show(); const submitCallback = ( isDynamic: boolean, players: PlayerDTO[], - ) => this.viewController.launchGame(players); + ) => { + Render.removeAllChildren('#sf-app'); + this.viewController.launchGame(players, isDynamic); + }; this.modeModal = new ModeView(backCallback, submitCallback); - Render.render('body', this.modeModal.theModeView); + this.menuViewContent = Render.elementFactory( + 'div', + { className: 'menu-window' }, + this.createHeading(), + this.createPageContent(), + this.createStartButton(), + this.createFooter(), + ); + this.viewContainer.appendChild(this.menuViewContent); } displayMenu(): void { Render.removeAllChildren('#sf-app'); - Render.childrenInjector( - this.viewContainer, - this.createLandingPage(), - this.createFooter(), - ); + this.show(); Render.render('#sf-app', this.view); } - private createLandingPage(): HTMLElement { - return Render.elementFactory( - 'div', - { - className: 'page__container menu-window', - }, - this.createHeading(), - this.createPageContent(), - ); - } - private createHeading(): HTMLElement { return Render.elementFactory( - 'header', + 'h1', { className: 'menu-window__header' }, - Render.elementFactory( - 'h1', - { className: 'menu-window__heading heading' }, - 'FARM TYCOON', - ), - Render.elementFactory( - 'h2', - { className: 'menu-window__description text' }, - 'Breed animals, Do your math, Be quick, Plan ahead, Protect your herd, Predators are there to get your animals! Become a farmer and be the first to gather all the animals!', - ), + 'SUPERFARMER', ); } private createPageContent(): HTMLElement { return Render.elementFactory( - 'div', + 'nav', { className: 'menu' }, - Render.elementFactory('div', { - className: 'menu__graphic-container', - }), - Render.elementFactory( - 'div', - { className: 'menu__buttons' }, - ...this.createMenuButtons(), - ), - Render.elementFactory('div', { - className: 'menu__graphic-container', - }), - Render.elementFactory( - 'button', - { - className: 'menu__button--rules button', - }, - 'i', - ), + ...this.createMenuButtons(), ); } private createMenuButtons(): HTMLElement[] { + const namesButtons: string[] = ['GAME RULES', 'AUTHORS']; + const menuButtons = namesButtons.map((button) => { + return Render.elementFactory( + 'button', + { className: 'menu__button' }, + button, + ); + }); + return menuButtons; + } + private createStartButton(): HTMLElement { const startGameButton = Render.elementFactory( 'button', - { className: 'button' }, - 'NEW GAME', + { className: 'button--start' }, + 'START', ); startGameButton.addEventListener('click', () => { this.hide(); + Render.render('#sf-app', this.modeModal.theModeView); this.modeModal.show(); }); - const settingsButton = Render.elementFactory( - 'button', - { - className: 'button', - }, - 'SETTINGS', - ); - settingsButton.setAttribute('disabled', 'true'); - const authorsButton = Render.elementFactory( - 'button', - { - className: 'button', - }, - 'AUTHORS', - ); - authorsButton.setAttribute('disabled', 'true'); - return [startGameButton, settingsButton, authorsButton]; + return startGameButton; } private createFooter(): HTMLElement { diff --git a/src/app/ModeView.ts b/src/app/ModeView.ts index 25fbe98..975f0d2 100644 --- a/src/app/ModeView.ts +++ b/src/app/ModeView.ts @@ -12,7 +12,7 @@ export class ModeView extends EmptyView { private modeForm: HTMLFormElement; private addPlayerButton: HTMLElement; private removePlayerButton: HTMLElement; - private playerInputsWrapper: HTMLElement; + private addPanelsWrapper: HTMLElement; private backButton!: HTMLElement; private playButton!: HTMLElement; private submitCallback: CallbackTwoParam; @@ -45,7 +45,7 @@ export class ModeView extends EmptyView { }, 'remove player', ); - this.playerInputsWrapper = Render.elementFactory('div', { + this.addPanelsWrapper = Render.elementFactory('div', { className: 'mode-players-wrapper', }); this.modeForm = this.createForm(); @@ -89,7 +89,7 @@ export class ModeView extends EmptyView { Render.elementFactory( 'label', { - className: 'mode-form__mode-input', + className: 'mode-form__mode-label', for: 'mode', }, 'dynamic mode', @@ -105,9 +105,8 @@ export class ModeView extends EmptyView { }, heading, mode, - this.playerInputsWrapper, + this.addPanelsWrapper, this.addPlayerButton, - this.removePlayerButton, ); this.addPlayer(); @@ -131,7 +130,7 @@ export class ModeView extends EmptyView { } private addPlayer(): void { - const numberOfPlayers = this.playerInputsWrapper.children.length; + const numberOfPlayers = this.addPanelsWrapper.children.length; if (numberOfPlayers >= 4) return; const playerInputRow = this.generateAddPlayerFields( @@ -141,21 +140,21 @@ export class ModeView extends EmptyView { if (numberOfPlayers >= 3) { this.addPlayerButton.classList.add('hidden'); } - Render.childrenInjector(this.playerInputsWrapper, playerInputRow); + Render.childrenInjector(this.addPanelsWrapper, playerInputRow); if (numberOfPlayers === 1) { this.removePlayerButton.classList.remove('hidden'); } } private removePlayer(): void { - const numberOfPlayers = this.playerInputsWrapper.children.length; + const numberOfPlayers = this.addPanelsWrapper.children.length; if (numberOfPlayers <= 1) return; if (numberOfPlayers === 4) { this.addPlayerButton.classList.remove('hidden'); } - (this.playerInputsWrapper.lastElementChild as Element).remove(); + (this.addPanelsWrapper.lastElementChild as Element).remove(); if (numberOfPlayers === 2) this.removePlayerButton.classList.add('hidden'); @@ -181,7 +180,7 @@ export class ModeView extends EmptyView { const buttonsWrapper = Render.elementFactory( 'div', { - className: 'modal__buttons', + className: 'mode__buttons', }, this.backButton, this.playButton, @@ -329,8 +328,10 @@ export class ModeView extends EmptyView { this.generateColorInput(numberOfPlayer), ); if (numberOfPlayer > 1) { - fieldsWrapper.appendChild( + Render.childrenInjector( + fieldsWrapper, this.generateAICheckbox(numberOfPlayer), + this.removePlayerButton, ); } return fieldsWrapper; @@ -341,10 +342,8 @@ export class ModeView extends EmptyView { ): { isDynamic: boolean; players: PlayerDTO[] } { const players: PlayerDTO[] = []; let isDynamic = false; - for (const [formKey, formValue] of formData.entries()) { - // TODO: remove before merge - console.log(formKey, formValue); + for (const [formKey, formValue] of formData.entries()) { const value = formValue.toString(); const [key, numberOfPlayer] = formKey.split('_'); const index = +numberOfPlayer - 1; @@ -380,8 +379,6 @@ export class ModeView extends EmptyView { } } } - // TODO: remove before merge - console.log({ isDynamic, players }); return { isDynamic, players }; } @@ -403,6 +400,9 @@ export class ModeView extends EmptyView { private handleClickRemovePlayer = (): void => { this.removePlayer(); + this.addPanelsWrapper.lastChild?.appendChild( + this.removePlayerButton, + ); }; private handleClickBackButton = (): void => { diff --git a/src/app/SecondDice.ts b/src/app/SecondDice.ts deleted file mode 100644 index 837cc57..0000000 --- a/src/app/SecondDice.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Dice } from './Dice'; -import { AnimalNames } from '../Enums/AnimalNamesEnum'; - -export class SecondDice extends Dice { - constructor() { - super([ - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.RABBIT, - AnimalNames.SHEEP, - AnimalNames.SHEEP, - AnimalNames.SHEEP, - AnimalNames.PIG, - AnimalNames.COW, - AnimalNames.WOLF, - ]); - } -} diff --git a/src/app/ViewController.ts b/src/app/ViewController.ts index a1cc354..483489a 100644 --- a/src/app/ViewController.ts +++ b/src/app/ViewController.ts @@ -10,6 +10,10 @@ import { Trade } from './Trade'; import { TradeModal } from './components/TradeModal'; import { defaultGameConfiguration } from './logic/defaultGameConfiguration'; import { PlayerDTO } from '../Interfaces/PlayerDTOInterface'; +import { Configuration } from './logic/Configuration'; +import { dynamicGameConfiguration } from './logic/dynamicGameConfiguration'; +import { AnimalNames } from '../Enums/AnimalNamesEnum'; +import { cloneDeep } from 'lodash'; export class ViewController { private menuView: MenuView; @@ -33,8 +37,23 @@ export class ViewController { this.menuView.displayMenu(); } - launchGame(players: PlayerDTO[]): void { - const config = defaultGameConfiguration; + /*TODO: CHECK IF AI NEEDED, CONNECT WITH CALLBACK THAT PASSES PLAYERS*/ + /* TODO: CONSIDER USING DEFAULT CONFIG ALWAYS, JUST CHANGE ALREADY CREATED CONFIG IN CASE ITS A DYNAMIC MODE*/ + launchGame(players: PlayerDTO[], isModeDynamic?: boolean): void { + const config: Configuration = + isModeDynamic === true + ? new Configuration(cloneDeep(dynamicGameConfiguration)) + : new Configuration(defaultGameConfiguration); + if (isModeDynamic) { + const numberOfPlayers = players.length; + config.livestockConfig = config.livestockConfig.map( + (animal) => { + if (animal.name === AnimalNames.RABBIT) + animal.bankInitialStock -= numberOfPlayers; + return animal; + }, + ); + } config.playersConfig = players; this.gameController = new GameController(this, config); this.startGame( diff --git a/src/app/components/Alert.ts b/src/app/components/Alert.ts index b6aeb0d..c4d7213 100644 --- a/src/app/components/Alert.ts +++ b/src/app/components/Alert.ts @@ -31,16 +31,16 @@ export class Alert { Render.removeAllChildren(this.alert); switch (alertType) { case AlertType.INFO: - this.alert.style.borderColor = 'blue'; + this.alert.style.backgroundColor = '#3E8Ed0'; break; case AlertType.WARN: - this.alert.style.borderColor = 'yellow'; + this.alert.style.backgroundColor = '#FFE08A'; break; case AlertType.CRITICAL: - this.alert.style.borderColor = 'red'; + this.alert.style.backgroundColor = '#F14668'; break; default: - this.alert.style.borderColor = 'white'; + this.alert.style.backgroundColor = 'transparent'; break; } const icon = Render.elementFactory('img', { diff --git a/src/app/components/PlayerPanel.ts b/src/app/components/PlayerPanel.ts index 5db22a7..ccc6bd6 100644 --- a/src/app/components/PlayerPanel.ts +++ b/src/app/components/PlayerPanel.ts @@ -13,6 +13,7 @@ export class PlayerPanel { * @param view accepts instance of View componenet */ constructor(private view: GameView) { + // TODO: FIND OUT WHY IS THIS PANEL CREATED TOGETHER WITH THE LANDING PAGE this.player = new Player('', '', ''); } diff --git a/src/app/components/WinModal.ts b/src/app/components/WinModal.ts index af1c8bb..8928019 100644 --- a/src/app/components/WinModal.ts +++ b/src/app/components/WinModal.ts @@ -16,7 +16,10 @@ export class WinModal extends EmptyModal { ); const image = Render.elementFactory( 'div', - { className: 'modal__image--win-container' }, + { + className: 'modal__image--win-container', + style: `border-color: ${player.theColor};`, + }, Render.elementFactory('img', { className: 'modal__image--win-avatar', src: player.theAvatar, @@ -30,7 +33,10 @@ export class WinModal extends EmptyModal { ); const text = Render.elementFactory( 'div', - { className: 'modal__text--win' }, + { + className: 'modal__text--win', + style: `color: ${player.theColor};`, + }, `${player.theName} wins`, ); const button = Render.elementFactory( @@ -38,9 +44,10 @@ export class WinModal extends EmptyModal { { type: 'button', className: 'modal__button--win' }, 'MENU', ); - button.addEventListener('click', () => - this.view.displayMenuView(), - ); + button.addEventListener('click', () => { + this.view.endGame(); + this.hideModal(); + }); Render.childrenInjector( this.modalContainer, heading, diff --git a/src/app/logic/Configuration.ts b/src/app/logic/Configuration.ts index 3823f13..9360982 100644 --- a/src/app/logic/Configuration.ts +++ b/src/app/logic/Configuration.ts @@ -3,16 +3,13 @@ import { PredatorsConfigInterface } from '../../Interfaces/PredatorsConfigInterf import { ProtectorsConfigInterface } from '../../Interfaces/ProtectorsConfigInterface'; import { GameModes } from '../../Enums/GameModeEnums'; import { GameConfigInterface } from '../../Interfaces/GameConfigInterface'; -import { AnimalNames } from '~src/Enums/AnimalNamesEnum'; +import { AnimalNames } from '../../Enums/AnimalNamesEnum'; +import { PlayerDTO } from '../../Interfaces/PlayerDTOInterface'; export class Configuration implements GameConfigInterface { protected _mode: GameModes; protected _roundTimeInSeconds: number; - protected _playersConfig: { - name: string; - path: string; - color: string; - }[]; + protected _playersConfig: PlayerDTO[]; protected _livestockConfig: LivestockConfigInterface[]; protected _protectorsConfig: ProtectorsConfigInterface[]; protected _predatorsConfig: PredatorsConfigInterface[]; @@ -43,26 +40,15 @@ export class Configuration implements GameConfigInterface { return this._mode; } set roundTimeInSeconds(numberOfSeconds: number) { - this.roundTimeInSeconds = numberOfSeconds; + this._roundTimeInSeconds = numberOfSeconds; } get roundTimeInSeconds(): number { - return this.roundTimeInSeconds; - } - //TODO: CREATE PLAYER CONFIG INTERFACE IF NECESSARY - set playersConfig( - newPlayersConfig: { - name: string; - path: string; - color: string; - }[], - ) { + return this._roundTimeInSeconds; + } + set playersConfig(newPlayersConfig: PlayerDTO[]) { this._playersConfig = newPlayersConfig; } - get playersConfig(): { - name: string; - path: string; - color: string; - }[] { + get playersConfig(): PlayerDTO[] { return this._playersConfig; } @@ -96,11 +82,7 @@ export class Configuration implements GameConfigInterface { return this._predatorsConfig; } - addNewPlayer(newPlayerConfig: { - name: string; - path: string; - color: string; - }): void { + addNewPlayer(newPlayerConfig: PlayerDTO): void { this._playersConfig.concat(newPlayerConfig); } removeLastPlayer(): void { @@ -116,6 +98,4 @@ export class Configuration implements GameConfigInterface { (animal) => animal.name !== animaname, ); } - - // TODO: PREDATOR, PROTECTOR ETC WHEN GAME SETTINGS APPEAR. } diff --git a/src/app/logic/Game.ts b/src/app/logic/Game.ts index fcd1bde..875cf75 100644 --- a/src/app/logic/Game.ts +++ b/src/app/logic/Game.ts @@ -3,27 +3,22 @@ import { GameModes } from '../../Enums/GameModeEnums'; import { GameConfigInterface } from '../../Interfaces/GameConfigInterface'; import { Player } from '../../Player'; import { BreedProcessor } from '../BreedProcessor'; -import { Dice } from '../Dice'; -import { FirstDice } from '../FirstDice'; -import { SecondDice } from '../SecondDice'; import { Timer } from '../Timer'; import { Trade } from '../Trade'; import { Bank } from './Bank'; -import { defaultGameConfiguration } from './defaultGameConfiguration'; import { LivestockConfigInterface } from '../../Interfaces/LivestockConfigInterface'; import { ProtectorsConfigInterface } from '../../Interfaces/ProtectorsConfigInterface'; import { HerdOwners } from '../../Enums/HerdOwnerEnum'; +import { DiceBuilder } from '../DiceBuilder'; export class Game { mode: GameModes; roundTimeInSeconds: number; - // totalGameTimeInSeconds: number; playersConfig: { name: string; path: string }[]; playersHerdConfig: HerdConfigInterface[]; banksHerdConfig: HerdConfigInterface[]; players: Player[]; bank: Bank; - dice: Dice[]; timer: Timer; breedProcessor: BreedProcessor; trade: Trade; @@ -34,8 +29,8 @@ export class Game { playersConfig, livestockConfig, protectorsConfig, - }: // predatorsConfig, - GameConfigInterface = defaultGameConfiguration) { + predatorsConfig, + }: GameConfigInterface) { this.mode = mode; this.roundTimeInSeconds = roundTimeInSeconds; this.playersConfig = playersConfig; @@ -60,12 +55,19 @@ export class Game { ); this.currentPlayerNumber = 0; this.bank = new Bank(this.banksHerdConfig); - // TODO: GET DICE DATA FROM CONFIG AFTER/ IF DICE REFACTOR - // TODO: CHECK IF NEEDED SINCE THEY ARE CALLED IN BREEDPROCESSOR - this.dice = [new FirstDice(), new SecondDice()]; + const [firstDice, secondDice] = DiceBuilder.build( + livestockConfig, + predatorsConfig, + protectorsConfig, + ); this.timer = new Timer(roundTimeInSeconds); - // TO CHECK: SHOULD BREED PROCESSOR CREATE DICE INSTANCES? - this.breedProcessor = new BreedProcessor(this.bank); + this.breedProcessor = new BreedProcessor( + this.bank, + firstDice, + secondDice, + predatorsConfig, + this.mode, + ); this.trade = new Trade( this.bank, livestockConfig, @@ -94,9 +96,6 @@ export class Game { return this.bank; } - get theDice(): Dice[] { - return this.dice; - } get theTimer(): Timer { return this.timer; } @@ -109,7 +108,6 @@ export class Game { return this.trade; } - // TODO: REFACTOR! preparePlayersHerdConfig( livestockConfig: LivestockConfigInterface[], protectorsConfig: ProtectorsConfigInterface[], @@ -145,6 +143,7 @@ export class Game { playersInitialStock, bankInitialStock, chasesAway, + exclamation, }) => { return { name, @@ -152,6 +151,7 @@ export class Game { role, path, chasesAway, + exclamation, inStock: owner === HerdOwners.PLAYER ? playersInitialStock diff --git a/src/app/logic/GameProcessor.ts b/src/app/logic/GameProcessor.ts index 0fc94d9..5a0dbf1 100644 --- a/src/app/logic/GameProcessor.ts +++ b/src/app/logic/GameProcessor.ts @@ -32,7 +32,7 @@ export class GameProcessor { this.gameController.updateTimeRemaining( Math.round(this.game.theTimer.theTurnTimeLeft), ); - }, 100); + }, 50); } pauseTurn(): void { @@ -53,6 +53,7 @@ export class GameProcessor { /** * @returns true if current player wins the game, false otherwise */ + // TODO: Consider moving win conditions to configuration checkWin(): boolean { const animalsRequiredToWin: AnimalNames[] = [ AnimalNames.RABBIT, diff --git a/src/app/logic/Herd.ts b/src/app/logic/Herd.ts index 8405de4..5de3f21 100644 --- a/src/app/logic/Herd.ts +++ b/src/app/logic/Herd.ts @@ -1,18 +1,37 @@ -import _ from 'lodash'; +import { add, subtract } from 'lodash'; +import { Predator } from '../../Animals/Predator'; import { Animal } from '../../Animals/Animal'; -import { Fox } from '../../Animals/Fox'; -import { Wolf } from '../../Animals/Wolf'; import { AnimalNames } from '../../Enums/AnimalNamesEnum'; import { HerdConfigInterface } from '../../Interfaces/HerdConfigInterface'; -import { mockHerdConfig } from './mockHerdConfig'; - +import { GameModes } from '../../Enums/GameModeEnums'; +import { Protector } from '../../Animals/Protector'; +import { AnimalRoles } from '../../Enums/AnimalRolesEnum'; export class Herd { protected animals: [Animal, number][]; - constructor( - playersHerdConfig: HerdConfigInterface[] = mockHerdConfig, - ) { + constructor(playersHerdConfig: HerdConfigInterface[]) { this.animals = playersHerdConfig.map( - ({ name, tradeValue, role, path, inStock }) => { + ({ + name, + tradeValue, + role, + path, + inStock, + chasesAway, + exclamation, + }) => { + if (role === AnimalRoles.GUARDIAN) { + if (chasesAway && exclamation) { + const newAnimal = new Protector( + name, + path, + role, + tradeValue, + chasesAway, + exclamation, + ); + return [newAnimal, inStock]; + } + } const newAnimal = new Animal(name, path, tradeValue, role); return [newAnimal, inStock]; }, @@ -28,7 +47,7 @@ export class Herd { if (animal.theName === animalName) return true; }); if (indexOfAnimal === -1) - throw new Error(`Animal: ${animalName} not found`); + console.log(`Animal: ${animalName} not found`); return indexOfAnimal; } @@ -57,7 +76,7 @@ export class Herd { ): void { const animalIndex = this.findAnimalTupleIndex(animalName); const animalTuple = this.animals[animalIndex]; - const newNumber = _.add(animalTuple[1], numberToAdd); + const newNumber = add(animalTuple[1], numberToAdd); this.updateNumberOfAnimals(animalIndex, newNumber); } @@ -75,8 +94,8 @@ export class Herd { const animalIndex = this.findAnimalTupleIndex(animalName); const animalTuple = this.animals[animalIndex]; if (animalTuple[1] < numberToSubstract) - alert('not enough animals'); - const newNumber = _.subtract(animalTuple[1], numberToSubstract); + console.log('not enough animals: ', animalName); + const newNumber = subtract(animalTuple[1], numberToSubstract); this.updateNumberOfAnimals(animalIndex, newNumber); } @@ -124,40 +143,26 @@ export class Herd { /** * Depending on the attacking animal, it checks if there is a herd protector for the given type of attacker, * then reduces to zero the number of the animals in the herd or removes the protector, as is defined by game configuration. - * @param { Fox | Wolf } attackingAnimal The animal that is attacking the herd. + * @param { Predator } attackingAnimal The animal that is attacking the herd. */ - // TODO: Check parameteres type. Create classes for protectors and predators if needed. - // TODO: Modify to use config? Define at refactor - cullAnimals(attackingAnimal: Fox | Wolf): void { - switch (attackingAnimal.theName) { - case AnimalNames.FOX: { - const hasSmallDog = - this.getAnimalNumber(AnimalNames.SMALL_DOG) > 0; - if (!hasSmallDog) { - attackingAnimal.attackHerd(); - this.cullAllAnimalsOfOneType(AnimalNames.RABBIT); - return; - } - this.removeAnimalsFromHerd(AnimalNames.SMALL_DOG, 1); - // (this.animals[5][0] as SmallDog).protectHerd(); - break; - } - case AnimalNames.WOLF: { - const hasBigDog = - this.getAnimalNumber(AnimalNames.BIG_DOG) > 0; - if (!hasBigDog) { - attackingAnimal.attackHerd(); - this.cullAllAnimalsOfGivenTypes([ - AnimalNames.COW, - AnimalNames.PIG, - AnimalNames.RABBIT, - AnimalNames.SHEEP, - ]); - return; - } - this.removeAnimalsFromHerd(AnimalNames.BIG_DOG, 1); - // (this.animals[6][0] as BigDog).protectHerd(); + cullAnimals(attackingAnimal: Predator, mode: GameModes): void { + const animalsToCull = attackingAnimal.kills; + const protector = attackingAnimal.isChasedAwayBy; + const hasProtector = this.getAnimalNumber(protector) > 0; + if (!hasProtector) { + const isDynamicMode = mode === GameModes.DYNAMIC; + const killsRabbits = animalsToCull.includes(AnimalNames.RABBIT); + this.cullAllAnimalsOfGivenTypes(animalsToCull); + if (isDynamicMode && killsRabbits) { + this.addAnimalsToHerd(AnimalNames.RABBIT, 1); } + attackingAnimal.attackHerd(); + } else { + this.removeAnimalsFromHerd(protector, 1); + const protectorsIndex = this.findAnimalTupleIndex(protector); + const protectorsObject = this.theAnimals[protectorsIndex][0]; + if (protectorsObject instanceof Protector) + protectorsObject.protectHerd(); } } } diff --git a/src/app/logic/defaultBankConfig.ts b/src/app/logic/defaultBankConfig.ts index a9c5dbe..aa00c6d 100644 --- a/src/app/logic/defaultBankConfig.ts +++ b/src/app/logic/defaultBankConfig.ts @@ -45,12 +45,14 @@ export const defaultBankConfig: HerdConfigInterface[] = [ role: AnimalRoles.GUARDIAN, inStock: 4, chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, }, { name: AnimalNames.BIG_DOG, tradeValue: 36, path: './static/images/avatars/big_dog.svg', role: AnimalRoles.GUARDIAN, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, inStock: 2, chasesAway: AnimalNames.WOLF, }, diff --git a/src/app/logic/defaultGameConfiguration.ts b/src/app/logic/defaultGameConfiguration.ts index d78cc3b..7ac030e 100644 --- a/src/app/logic/defaultGameConfiguration.ts +++ b/src/app/logic/defaultGameConfiguration.ts @@ -11,11 +11,7 @@ export const defaultGameConfiguration: GameConfigInterface = { name: 'Carlos Santanos', path: '../../static/images/avatars/small_dog.svg', color: 'blue', - }, - { - name: 'Pablo Escofarmo', - path: '../../static/images/avatars/cow.svg', - color: 'green', + isAI: false, }, ], @@ -84,27 +80,33 @@ export const defaultGameConfiguration: GameConfigInterface = { playersInitialStock: 0, bankInitialStock: 4, chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, }, { name: AnimalNames.BIG_DOG, tradeValue: 36, - path: './static/images/avatars/big_dog.svg', // TODO: CHANGE TO BIG/SMALL DOG + path: './static/images/avatars/big_dog.svg', role: AnimalRoles.GUARDIAN, playersInitialStock: 0, bankInitialStock: 2, chasesAway: AnimalNames.WOLF, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, }, ], predatorsConfig: [ { name: AnimalNames.FOX, path: './static/images/avatars/fox.svg', + roles: AnimalRoles.PREDATOR, kills: [AnimalNames.RABBIT], - isChasedAwayBy: [AnimalNames.SMALL_DOG], + isChasedAwayBy: AnimalNames.SMALL_DOG, + exclamation: + 'Ring-ding-ding-ding-dingeringeding! Wa-pa-pa-pa-pa-pa-pow!', dice: [{ diceNumber: 1, probability: 1 }], }, { name: AnimalNames.WOLF, + roles: AnimalRoles.PREDATOR, path: './static/images/avatars/wolf.svg', kills: [ AnimalNames.RABBIT, @@ -112,7 +114,8 @@ export const defaultGameConfiguration: GameConfigInterface = { AnimalNames.PIG, AnimalNames.COW, ], - isChasedAwayBy: [AnimalNames.BIG_DOG], + isChasedAwayBy: AnimalNames.BIG_DOG, + exclamation: 'Auuuuuu!Grrrrr!', dice: [{ diceNumber: 2, probability: 1 }], }, ], diff --git a/src/app/logic/mockHerdConfig.ts b/src/app/logic/defaultHerdConfig.ts similarity index 85% rename from src/app/logic/mockHerdConfig.ts rename to src/app/logic/defaultHerdConfig.ts index 1989535..a458b15 100644 --- a/src/app/logic/mockHerdConfig.ts +++ b/src/app/logic/defaultHerdConfig.ts @@ -2,7 +2,7 @@ import { HerdConfigInterface } from '../../Interfaces/HerdConfigInterface'; import { AnimalNames } from '../../Enums/AnimalNamesEnum'; import { AnimalRoles } from '../../Enums/AnimalRolesEnum'; -export const mockHerdConfig: HerdConfigInterface[] = [ +export const defaultPlayerHerdConfig: HerdConfigInterface[] = [ { name: AnimalNames.RABBIT, tradeValue: 1, @@ -45,6 +45,7 @@ export const mockHerdConfig: HerdConfigInterface[] = [ role: AnimalRoles.GUARDIAN, inStock: 0, chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, }, { name: AnimalNames.BIG_DOG, @@ -53,5 +54,6 @@ export const mockHerdConfig: HerdConfigInterface[] = [ role: AnimalRoles.GUARDIAN, inStock: 0, chasesAway: AnimalNames.WOLF, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, }, ]; diff --git a/src/app/logic/dynamicGameConfiguration.ts b/src/app/logic/dynamicGameConfiguration.ts new file mode 100644 index 0000000..2760a7c --- /dev/null +++ b/src/app/logic/dynamicGameConfiguration.ts @@ -0,0 +1,117 @@ +import { AnimalNames } from '../../Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../../Enums/AnimalRolesEnum'; +import { GameModes } from '../../Enums/GameModeEnums'; +import { GameConfigInterface } from '../../Interfaces/GameConfigInterface'; + +export const dynamicGameConfiguration: GameConfigInterface = { + mode: GameModes.DYNAMIC, + roundTimeInSeconds: 15, + playersConfig: [ + { + name: 'Carlos Santanos', + path: '../../static/images/avatars/small_dog.svg', + color: 'blue', + isAI: false, + }, + ], + + livestockConfig: [ + { + name: AnimalNames.RABBIT, + tradeValue: 1, + path: './static/images/avatars/rabbit.svg', + role: AnimalRoles.LIVESTOCK, + playersInitialStock: 1, + bankInitialStock: 60, + dice: [ + { diceNumber: 1, probability: 6 }, + { diceNumber: 2, probability: 6 }, + ], + }, + { + name: AnimalNames.SHEEP, + tradeValue: 6, + path: './static/images/avatars/sheep.svg', + role: AnimalRoles.LIVESTOCK, + playersInitialStock: 0, + bankInitialStock: 24, + dice: [ + { diceNumber: 1, probability: 2 }, + { diceNumber: 2, probability: 3 }, + ], + }, + { + name: AnimalNames.PIG, + tradeValue: 12, + path: './static/images/avatars/pig.svg', + role: AnimalRoles.LIVESTOCK, + playersInitialStock: 0, + bankInitialStock: 20, + dice: [ + { diceNumber: 1, probability: 2 }, + { diceNumber: 2, probability: 1 }, + ], + }, + { + name: AnimalNames.COW, + tradeValue: 36, + path: './static/images/avatars/cow.svg', + role: AnimalRoles.LIVESTOCK, + playersInitialStock: 0, + bankInitialStock: 12, + dice: [{ diceNumber: 2, probability: 1 }], + }, + { + name: AnimalNames.HORSE, + tradeValue: 72, + path: './static/images/avatars/horse.svg', + role: AnimalRoles.LIVESTOCK, + playersInitialStock: 0, + bankInitialStock: 4, + dice: [{ diceNumber: 1, probability: 1 }], + }, + ], + protectorsConfig: [ + { + name: AnimalNames.SMALL_DOG, + tradeValue: 6, + path: './static/images/avatars/small_dog.svg', + role: AnimalRoles.GUARDIAN, + playersInitialStock: 0, + bankInitialStock: 4, + chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, + }, + { + name: AnimalNames.BIG_DOG, + tradeValue: 36, + path: './static/images/avatars/big_dog.svg', + role: AnimalRoles.GUARDIAN, + playersInitialStock: 0, + bankInitialStock: 2, + chasesAway: AnimalNames.WOLF, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, + }, + ], + predatorsConfig: [ + { + name: AnimalNames.FOX, + path: './static/images/avatars/fox.svg', + roles: AnimalRoles.PREDATOR, + kills: [AnimalNames.RABBIT], + isChasedAwayBy: AnimalNames.SMALL_DOG, + exclamation: + 'Ring-ding-ding-ding-dingeringeding! Wa-pa-pa-pa-pa-pa-pow!', + dice: [{ diceNumber: 1, probability: 1 }], + }, + { + name: AnimalNames.WOLF, + path: './static/images/avatars/wolf.svg', + roles: AnimalRoles.PREDATOR, + kills: [AnimalNames.SHEEP, AnimalNames.PIG, AnimalNames.COW], + isChasedAwayBy: AnimalNames.BIG_DOG, + exclamation: 'Auuuuuu!Grrrrr!', + dice: [{ diceNumber: 2, probability: 1 }], + }, + ], +}; diff --git a/src/app/manuals/BreedPhaseDemo.ts b/src/app/manuals/BreedPhaseDemo.ts index d13eaa3..b3c9e53 100644 --- a/src/app/manuals/BreedPhaseDemo.ts +++ b/src/app/manuals/BreedPhaseDemo.ts @@ -1,6 +1,10 @@ import { Player } from '../../Player'; import { AnimalNames } from '../../Enums/AnimalNamesEnum'; import { BreedProcessor } from '../BreedProcessor'; +import { defaultGameConfiguration } from '../logic/defaultGameConfiguration'; +import { DiceBuilder } from '../DiceBuilder'; +import { mockPredatorConfig } from './mockPredatorConfig'; +import { GameModes } from '../../Enums/GameModeEnums'; export class BreedPhaseDemo { static playDemo(): void { @@ -12,7 +16,18 @@ export class BreedPhaseDemo { bank.theHerd.addAnimalsToHerd(AnimalNames.HORSE, 4); bank.theHerd.addAnimalsToHerd(AnimalNames.SMALL_DOG, 4); bank.theHerd.addAnimalsToHerd(AnimalNames.BIG_DOG, 2); - const bp = new BreedProcessor(bank); + const [firstDice, secondDice] = DiceBuilder.build( + defaultGameConfiguration.livestockConfig, + defaultGameConfiguration.predatorsConfig, + defaultGameConfiguration.protectorsConfig, + ); + const bp = new BreedProcessor( + bank, + firstDice, + secondDice, + mockPredatorConfig, + GameModes.STATIC, + ); const player = new Player('player'); for (let i = 0; i < 10; i++) { bp.processBreedPhase(player); diff --git a/src/app/manuals/WinModalDemo.ts b/src/app/manuals/WinModalDemo.ts index f8e995b..be780a3 100644 --- a/src/app/manuals/WinModalDemo.ts +++ b/src/app/manuals/WinModalDemo.ts @@ -10,6 +10,7 @@ export class WinModalDemo { const player = new Player( 'player', './static/images/avatars/small_dog.svg', + `#338254`, ); Render.render('#sf-app', modal.createWinModal(player)); } diff --git a/src/app/manuals/dynamicModeManual.ts b/src/app/manuals/dynamicModeManual.ts new file mode 100644 index 0000000..a569979 --- /dev/null +++ b/src/app/manuals/dynamicModeManual.ts @@ -0,0 +1,28 @@ +// in the play in the dynamic mode, until mode modal has option to choose +// dynamic mode +// in the file: src/app/ViewController +// in the lines: 41-63 (the launchGame method) +// UNCOMMENT AND INSERT INSTEAD: + +// launchGame(players: PlayerDTO[], isModeDynamic?: boolean): void { +// const config: Configuration = new Configuration( +// dynamicGameConfiguration, +// ); +// if (true) { +// const numberOfPlayers = players.length; +// config.livestockConfig = config.livestockConfig.map( +// (animal) => { +// if (animal.name === AnimalNames.RABBIT) +// animal.bankInitialStock -= numberOfPlayers; +// return animal; +// }, +// ); +// } +// config.playersConfig = players; +// this.gameController = new GameController(this, config); +// this.startGame( +// this.gameController.theGame.thePlayers, +// this.gameController.theGame.theCurrentPlayer, +// this.gameController.theGame.theBank, +// ); +// } diff --git a/src/app/manuals/gameManual.ts b/src/app/manuals/gameManual.ts index 4336411..7acf849 100644 --- a/src/app/manuals/gameManual.ts +++ b/src/app/manuals/gameManual.ts @@ -1,6 +1,7 @@ +import { defaultGameConfiguration } from '../logic/defaultGameConfiguration'; import { Game } from '../logic/Game'; // TO LOG THE GAME OBJECT ADD logGameObject() in APP.TS export function logGameObject(): void { - const newGame = new Game(); + const newGame = new Game(defaultGameConfiguration); console.log(newGame); } diff --git a/src/app/manuals/herdManual.ts b/src/app/manuals/herdManual.ts index 0ca3c21..3de349d 100644 --- a/src/app/manuals/herdManual.ts +++ b/src/app/manuals/herdManual.ts @@ -47,6 +47,7 @@ export function herdDemo(): void { role: AnimalRoles.GUARDIAN, inStock: 0, chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, }, { name: AnimalNames.BIG_DOG, @@ -55,6 +56,7 @@ export function herdDemo(): void { role: AnimalRoles.GUARDIAN, inStock: 0, chasesAway: AnimalNames.WOLF, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, }, ]; diff --git a/src/app/manuals/mockPredatorConfig.ts b/src/app/manuals/mockPredatorConfig.ts new file mode 100644 index 0000000..bc76bf5 --- /dev/null +++ b/src/app/manuals/mockPredatorConfig.ts @@ -0,0 +1,30 @@ +import { AnimalRoles } from '../../Enums/AnimalRolesEnum'; +import { AnimalNames } from '../../Enums/AnimalNamesEnum'; +import { PredatorsConfigInterface } from '../../Interfaces/PredatorsConfigInterface'; + +export const mockPredatorConfig: PredatorsConfigInterface[] = [ + { + name: AnimalNames.FOX, + path: '/static/images/avatars/fox.png', + roles: AnimalRoles.PREDATOR, + kills: [AnimalNames.RABBIT], + isChasedAwayBy: AnimalNames.SMALL_DOG, + exclamation: + 'Ring-ding-ding-ding-dingeringeding! Wa-pa-pa-pa-pa-pa-pow!', + dice: [{ diceNumber: 1, probability: 1 }], + }, + { + name: AnimalNames.WOLF, + path: '/static/images/avatars/wolf.png', + roles: AnimalRoles.PREDATOR, + kills: [ + AnimalNames.RABBIT, + AnimalNames.SHEEP, + AnimalNames.PIG, + AnimalNames.COW, + ], + isChasedAwayBy: AnimalNames.BIG_DOG, + exclamation: 'Auuuuuu!Grrrrr!', + dice: [{ diceNumber: 2, probability: 1 }], + }, +]; diff --git a/static/images/ui/background.png b/static/images/ui/background.png new file mode 100644 index 0000000..0743729 Binary files /dev/null and b/static/images/ui/background.png differ diff --git a/styles/App.scss b/styles/App.scss index fc92d01..63c980a 100644 --- a/styles/App.scss +++ b/styles/App.scss @@ -1,2 +1,3 @@ +@import './variables'; @import './components/components'; @import './global'; diff --git a/styles/_global.scss b/styles/_global.scss index cca14bd..df55874 100644 --- a/styles/_global.scss +++ b/styles/_global.scss @@ -4,6 +4,11 @@ margin: 0; font-family: 'Roboto', sans-serif; } +#sf-app { + height: 100vh; + background: url(/static/images/ui/background.png) no-repeat; + background-size: cover; +} .hidden { display: none; diff --git a/styles/_variables.scss b/styles/_variables.scss new file mode 100644 index 0000000..ca30efd --- /dev/null +++ b/styles/_variables.scss @@ -0,0 +1,11 @@ +//Fonts +$font-family: 'Roboto', sans-serif; +$font-size: 24px; +$font-weight: 700; + +//Colors +$color-primary: #fefefe; +$color-hover: #f00; + +//Border +$border-radius: 16px; diff --git a/styles/components/_alert.scss b/styles/components/_alert.scss new file mode 100644 index 0000000..293aa7e --- /dev/null +++ b/styles/components/_alert.scss @@ -0,0 +1,18 @@ +.alert { + display: flex; + border-radius: 0.25rem; + transition: all 0.7s ease-in-out; + + &__icon { + width: auto; + height: 1.75rem; + margin: 0.5rem 1rem; + } + + &__text { + display: inline-flex; + margin: 0.5rem; + font-size: 1rem; + line-height: 2rem; + } +} diff --git a/styles/components/_button.scss b/styles/components/_button.scss deleted file mode 100644 index f54e640..0000000 --- a/styles/components/_button.scss +++ /dev/null @@ -1,2 +0,0 @@ -.button { -} diff --git a/styles/components/_components.scss b/styles/components/_components.scss index 8c817fc..2e0f444 100644 --- a/styles/components/_components.scss +++ b/styles/components/_components.scss @@ -1,10 +1,10 @@ @import './view'; @import './playersBoard'; -@import './components/button'; -@import './components/modal'; +@import './modal'; @import './menu'; @import './modeView'; @import './tradeModal'; @import './playerPanel'; @import './bankBoard'; @import './emptyView'; +@import './alert'; diff --git a/styles/components/_emptyView.scss b/styles/components/_emptyView.scss index 0f6a33b..423d518 100644 --- a/styles/components/_emptyView.scss +++ b/styles/components/_emptyView.scss @@ -1,4 +1,6 @@ .view { + height: 100vh; &__container { + height: 100vh; } } diff --git a/styles/components/_menu.scss b/styles/components/_menu.scss index 4344432..e845be6 100644 --- a/styles/components/_menu.scss +++ b/styles/components/_menu.scss @@ -1,60 +1,53 @@ -.page__container { - width: 100vw; - height: calc(100vh - 2rem); - background-color: white; - text-align: center; -} - .menu-window { display: flex; + height: 100%; flex-direction: column; - justify-content: space-between; + padding: 80px; &__header { - position: relative; - display: flex; - flex-direction: column; - padding: 32px; - } - &__heading { - } - &__description { + font-family: $font-family; } } .menu { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 32px; - &__graphic { - &-container { - width: 30vw; - height: 50vh; - background-color: cadetblue; - } - } - &__buttons { - display: flex; - flex-direction: column; - } + flex-grow: 1; &__button { - &--rules { - position: absolute; - right: 2rem; - bottom: 0; - width: 2rem; - height: 2rem; + display: block; + border: none; + margin-top: 50px; + background: none; + font-family: $font-family; + font-size: $font-size; + font-weight: $font-weight; + outline: none; + &:hover { + color: $color-hover; + cursor: pointer; } } } +.button--start { + min-width: 350px; + align-self: center; + border: none; + background: #203739; + border-radius: $border-radius; + color: $color-primary; + font-family: $font-family; + font-size: 30px; + font-weight: $font-weight; + letter-spacing: 0.3rem; + line-height: 8rem; + outline: none; + &:hover { + background-color: $color-hover; + cursor: pointer; + } +} .footer { - position: sticky; - bottom: 0; - width: 100vw; - margin: auto; - background-color: white; - line-height: 2em; - text-align: center; + margin-top: 35px; + color: $color-primary; + font-family: $font-family; + font-size: $font-size; + font-weight: $font-weight; } diff --git a/test/herd.spec.ts b/test/herd.spec.ts index 1575265..0e958bf 100644 --- a/test/herd.spec.ts +++ b/test/herd.spec.ts @@ -1,8 +1,9 @@ -import { Fox } from '../src/Animals/Fox'; -import { Wolf } from '../src/Animals/Wolf'; import { AnimalNames } from '../src/Enums/AnimalNamesEnum'; import { Herd } from '../src/app/logic/Herd'; -import { mockHerdConfig } from '../src/app/logic/mockHerdConfig'; +import { mockHerdConfig } from './mock/mockHerdConfig'; +import { Predator } from '../src/Animals/Predator'; +import { mockFox, mockWolf } from './mock/mockPredatorsConfig'; +import { GameModes } from '../src/Enums/GameModeEnums'; describe('Herds method', () => { describe('addAnimal, given mock data', () => { @@ -16,7 +17,17 @@ describe('Herds method', () => { describe('cullAnimals, given mock data', () => { const testedHerd = new Herd(mockHerdConfig); testedHerd.addAnimalsToHerd(AnimalNames.RABBIT, 10); - testedHerd.cullAnimals(new Fox()); + testedHerd.cullAnimals( + new Predator( + mockWolf.name, + mockWolf.path, + undefined, + mockWolf.kills, + mockWolf.isChasedAwayBy, + mockWolf.exclamation, + ), + GameModes.STATIC, + ); it('should modify the number of specific animal', () => { expect(testedHerd.theAnimals[0][1]).toBe(0); }); @@ -26,7 +37,17 @@ describe('Herds method', () => { const testedHerd = new Herd(mockHerdConfig); testedHerd.addAnimalsToHerd(AnimalNames.SMALL_DOG, 1); testedHerd.addAnimalsToHerd(AnimalNames.RABBIT, 10); - testedHerd.cullAnimals(new Fox()); + testedHerd.cullAnimals( + new Predator( + mockFox.name, + mockFox.path, + mockWolf.roles, + mockFox.kills, + mockFox.isChasedAwayBy, + mockFox.exclamation, + ), + GameModes.STATIC, + ); it('should modify the number of specific animal', () => { expect(testedHerd.theAnimals[5][1]).toBe(0); expect(testedHerd.theAnimals[0][1]).toBe(10); @@ -37,7 +58,17 @@ describe('Herds method', () => { const testedHerd = new Herd(mockHerdConfig); testedHerd.addAnimalsToHerd(AnimalNames.BIG_DOG, 1); testedHerd.addAnimalsToHerd(AnimalNames.SHEEP, 10); - testedHerd.cullAnimals(new Wolf()); + testedHerd.cullAnimals( + new Predator( + mockWolf.name, + mockWolf.path, + mockWolf.roles, + mockWolf.kills, + mockWolf.isChasedAwayBy, + mockWolf.exclamation, + ), + GameModes.STATIC, + ); it('should modify the number of specific animal', () => { expect(testedHerd.theAnimals[6][1]).toBe(0); expect(testedHerd.theAnimals[1][1]).toBe(10); diff --git a/src/app/logic/mockGameConfiguration.ts b/test/mock/mockGameConfiguration.ts similarity index 82% rename from src/app/logic/mockGameConfiguration.ts rename to test/mock/mockGameConfiguration.ts index f200adf..a308ebf 100644 --- a/src/app/logic/mockGameConfiguration.ts +++ b/test/mock/mockGameConfiguration.ts @@ -1,7 +1,7 @@ -import { AnimalNames } from '../../Enums/AnimalNamesEnum'; -import { AnimalRoles } from '../../Enums/AnimalRolesEnum'; -import { GameModes } from '../../Enums/GameModeEnums'; -import { GameConfigInterface } from '../../Interfaces/GameConfigInterface'; +import { AnimalNames } from '../../src/Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../../src/Enums/AnimalRolesEnum'; +import { GameModes } from '../../src/Enums/GameModeEnums'; +import { GameConfigInterface } from '../../src/Interfaces/GameConfigInterface'; export const defaultGameConfiguration: GameConfigInterface = { mode: GameModes.STATIC, @@ -84,6 +84,7 @@ export const defaultGameConfiguration: GameConfigInterface = { playersInitialStock: 0, bankInitialStock: 4, chasesAway: AnimalNames.FOX, + exclamation: `Woof! Woof! I'm protecting all rabbits in the herd! Woof! Woof!`, }, { name: AnimalNames.BIG_DOG, @@ -93,18 +94,23 @@ export const defaultGameConfiguration: GameConfigInterface = { playersInitialStock: 0, bankInitialStock: 2, chasesAway: AnimalNames.WOLF, + exclamation: `WOOF! WOOF! I'm protecting the whole herd! WOOF! WOOF!`, }, ], predatorsConfig: [ { name: AnimalNames.FOX, + roles: AnimalRoles.PREDATOR, path: './static/images/avatars/fox.svg', kills: [AnimalNames.RABBIT], - isChasedAwayBy: [AnimalNames.SMALL_DOG], + isChasedAwayBy: AnimalNames.SMALL_DOG, + exclamation: + 'Ring-ding-ding-ding-dingeringeding! Wa-pa-pa-pa-pa-pa-pow!', dice: [{ diceNumber: 1, probability: 1 }], }, { name: AnimalNames.WOLF, + roles: AnimalRoles.PREDATOR, path: './static/images/avatars/wolf.svg', kills: [ AnimalNames.RABBIT, @@ -112,7 +118,8 @@ export const defaultGameConfiguration: GameConfigInterface = { AnimalNames.PIG, AnimalNames.COW, ], - isChasedAwayBy: [AnimalNames.BIG_DOG], + isChasedAwayBy: AnimalNames.BIG_DOG, + exclamation: 'Auuuuuu!Grrrrr!', dice: [{ diceNumber: 2, probability: 1 }], }, ], diff --git a/test/mock/mockHerdConfig.ts b/test/mock/mockHerdConfig.ts new file mode 100644 index 0000000..2058a90 --- /dev/null +++ b/test/mock/mockHerdConfig.ts @@ -0,0 +1,57 @@ +import { HerdConfigInterface } from '../../src/Interfaces/HerdConfigInterface'; +import { AnimalNames } from '../../src/Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../../src/Enums/AnimalRolesEnum'; + +export const mockHerdConfig: HerdConfigInterface[] = [ + { + name: AnimalNames.RABBIT, + tradeValue: 1, + path: '/static/images/avatars/rabbit.png', + role: AnimalRoles.LIVESTOCK, + inStock: 0, + }, + { + name: AnimalNames.SHEEP, + tradeValue: 6, + path: '/static/images/avatars/sheep.png', + role: AnimalRoles.LIVESTOCK, + inStock: 0, + }, + { + name: AnimalNames.PIG, + tradeValue: 12, + path: '/static/images/avatars/pig.png', + role: AnimalRoles.LIVESTOCK, + inStock: 0, + }, + { + name: AnimalNames.COW, + tradeValue: 36, + path: '/static/images/avatars/cow.png', + role: AnimalRoles.LIVESTOCK, + inStock: 0, + }, + { + name: AnimalNames.HORSE, + tradeValue: 72, + path: '/static/images/avatars/horse.png', + role: AnimalRoles.LIVESTOCK, + inStock: 0, + }, + { + name: AnimalNames.SMALL_DOG, + tradeValue: 6, + path: '/static/images/avatars/dog.png', + role: AnimalRoles.GUARDIAN, + inStock: 0, + chasesAway: AnimalNames.FOX, + }, + { + name: AnimalNames.BIG_DOG, + tradeValue: 36, + path: '/static/images/avatars/dog.png', + role: AnimalRoles.GUARDIAN, + inStock: 0, + chasesAway: AnimalNames.WOLF, + }, +]; diff --git a/test/mock/mockPredatorsConfig.ts b/test/mock/mockPredatorsConfig.ts new file mode 100644 index 0000000..c0732e4 --- /dev/null +++ b/test/mock/mockPredatorsConfig.ts @@ -0,0 +1,27 @@ +import { AnimalNames } from '../../src/Enums/AnimalNamesEnum'; +import { AnimalRoles } from '../../src/Enums/AnimalRolesEnum'; +import { PredatorsConfigInterface } from '../../src/Interfaces/PredatorsConfigInterface'; +export const mockWolf: PredatorsConfigInterface = { + name: AnimalNames.WOLF, + path: '/static/images/avatars/wolf.png', + roles: AnimalRoles.PREDATOR, + kills: [ + AnimalNames.RABBIT, + AnimalNames.SHEEP, + AnimalNames.PIG, + AnimalNames.COW, + ], + isChasedAwayBy: AnimalNames.BIG_DOG, + exclamation: 'Auuuuuu!Grrrrr!', + // dice: [{ diceNumber: 2, probability: 1 }], +}; + +export const mockFox: PredatorsConfigInterface = { + name: AnimalNames.FOX, + path: '/static/images/avatars/fox.png', + roles: AnimalRoles.PREDATOR, + kills: [AnimalNames.RABBIT], + isChasedAwayBy: AnimalNames.SMALL_DOG, + exclamation: 'Ringangngnignign', + // dice: [{ diceNumber: 2, probability: 1 }], +};