@bleckert/football-simulator
Advanced tools
Comparing version 0.0.3 to 0.0.4
import { GameEvent } from './types/GameEvent'; | ||
import { Event } from './enums/Event'; | ||
export default class Commentator { | ||
static importantEvents: Event[]; | ||
name: string; | ||
constructor(name?: string); | ||
routeComment(event: GameEvent): string | null; | ||
comment(event: GameEvent): string | null; | ||
@@ -13,2 +12,3 @@ gameStarted(event: GameEvent): string; | ||
defence(event: GameEvent): string; | ||
rebound(comment: string, event: GameEvent): string; | ||
save(event: GameEvent): string; | ||
@@ -15,0 +15,0 @@ block(event: GameEvent): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./enums/Event"); | ||
const importantEvents = [ | ||
Event_1.Event.GameStart, | ||
Event_1.Event.Kickoff, | ||
Event_1.Event.HalfTime, | ||
Event_1.Event.Advance, | ||
Event_1.Event.Save, | ||
Event_1.Event.Block, | ||
Event_1.Event.Goal, | ||
Event_1.Event.GameEnd, | ||
Event_1.Event.Injury, | ||
]; | ||
class Commentator { | ||
@@ -8,7 +19,3 @@ constructor(name = 'Mr. Commentator') { | ||
} | ||
comment(event) { | ||
const importantEvent = this.constructor.importantEvents.indexOf(event.event) > -1; | ||
if (!importantEvent && Math.random() > 0.5) { | ||
return null; | ||
} | ||
routeComment(event) { | ||
switch (event.event) { | ||
@@ -37,2 +44,9 @@ case Event_1.Event.GameStart: | ||
} | ||
comment(event) { | ||
const importantEvent = event.event in importantEvents; | ||
if (!importantEvent && Math.random() > 0.5) { | ||
return null; | ||
} | ||
return this.routeComment(event); | ||
} | ||
gameStarted(event) { | ||
@@ -53,21 +67,13 @@ return `The game between ${event.homeTeam.name} and ${event.awayTeam.name} has started.`; | ||
} | ||
save(event) { | ||
let comment = `${event.attackingPrimaryPlayer.info.name} tries to score but the goalkeeper saves the ball.`; | ||
rebound(comment, event) { | ||
if (event.data === event.attackingTeam) { | ||
comment += ` ${event.attackingTeam.name} gets the ball back.`; | ||
return `${event.attackingTeam.name} gets the ball back.`; | ||
} | ||
else { | ||
comment += ` ${event.attackingTeam.name} can take control over the ball.`; | ||
} | ||
return comment; | ||
return [comment, `${event.attackingTeam.name} can take control over the ball.`].join(' '); | ||
} | ||
save(event) { | ||
return this.rebound(`${event.attackingPrimaryPlayer.info.name} tries to score but the goalkeeper saves the ball.`, event); | ||
} | ||
block(event) { | ||
let comment = `${event.attackingPrimaryPlayer.info.name} tries to score but the ball was blocked by the defence.`; | ||
if (event.data === event.attackingTeam) { | ||
comment += ` ${event.attackingTeam.name} gets the ball back.`; | ||
} | ||
else { | ||
comment += ` ${event.attackingTeam.name} can take control over the ball.`; | ||
} | ||
return comment; | ||
return this.rebound(`${event.attackingPrimaryPlayer.info.name} tries to score but the ball was blocked by the defence.`, event); | ||
} | ||
@@ -81,3 +87,3 @@ goal(event) { | ||
} | ||
else if (event.gameInfo.homeGoals < event.gameInfo.awayGoals) { | ||
if (event.gameInfo.homeGoals < event.gameInfo.awayGoals) { | ||
return `The game has ended! ${event.awayTeam.name} takes 3 points on the road! ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
@@ -88,13 +94,2 @@ } | ||
} | ||
Commentator.importantEvents = [ | ||
Event_1.Event.GameStart, | ||
Event_1.Event.Kickoff, | ||
Event_1.Event.HalfTime, | ||
Event_1.Event.Advance, | ||
Event_1.Event.Save, | ||
Event_1.Event.Block, | ||
Event_1.Event.Goal, | ||
Event_1.Event.GameEnd, | ||
Event_1.Event.Injury, | ||
]; | ||
exports.default = Commentator; |
import { GameEvent } from './types/GameEvent'; | ||
import { Event } from './enums/Event'; | ||
import Team from "./Team"; | ||
import Player from "./Player"; | ||
import Team from './Team'; | ||
import Player from './Player'; | ||
const importantEvents = [ | ||
Event.GameStart, | ||
Event.Kickoff, | ||
Event.HalfTime, | ||
Event.Advance, | ||
Event.Save, | ||
Event.Block, | ||
Event.Goal, | ||
Event.GameEnd, | ||
Event.Injury, | ||
]; | ||
export default class Commentator { | ||
static importantEvents = [ | ||
Event.GameStart, | ||
Event.Kickoff, | ||
Event.HalfTime, | ||
Event.Advance, | ||
Event.Save, | ||
Event.Block, | ||
Event.Goal, | ||
Event.GameEnd, | ||
Event.Injury, | ||
]; | ||
name: string; | ||
@@ -25,9 +25,3 @@ | ||
comment(event: GameEvent): string | null { | ||
const importantEvent = (this.constructor as typeof Commentator).importantEvents.indexOf(event.event) > -1; | ||
if (!importantEvent && Math.random() > 0.5) { | ||
return null; | ||
} | ||
routeComment(event: GameEvent): string | null { | ||
switch (event.event) { | ||
@@ -57,2 +51,12 @@ case Event.GameStart: | ||
comment(event: GameEvent): string | null { | ||
const importantEvent = event.event in importantEvents; | ||
if (!importantEvent && Math.random() > 0.5) { | ||
return null; | ||
} | ||
return this.routeComment(event); | ||
} | ||
gameStarted(event: GameEvent): string { | ||
@@ -78,24 +82,16 @@ return `The game between ${event.homeTeam.name} and ${event.awayTeam.name} has started.`; | ||
save(event: GameEvent): string { | ||
let comment = `${(event.attackingPrimaryPlayer as Player).info.name} tries to score but the goalkeeper saves the ball.`; | ||
rebound(comment: string, event: GameEvent): string { | ||
if (event.data === event.attackingTeam) { | ||
comment += ` ${event.attackingTeam.name} gets the ball back.`; | ||
} else { | ||
comment += ` ${event.attackingTeam.name} can take control over the ball.`; | ||
return `${event.attackingTeam.name} gets the ball back.`; | ||
} | ||
return comment; | ||
return [comment, `${event.attackingTeam.name} can take control over the ball.`].join(' '); | ||
} | ||
save(event: GameEvent): string { | ||
return this.rebound(`${(event.attackingPrimaryPlayer as Player).info.name} tries to score but the goalkeeper saves the ball.`, event); | ||
} | ||
block(event: GameEvent): string { | ||
let comment = `${(event.attackingPrimaryPlayer as Player).info.name} tries to score but the ball was blocked by the defence.`; | ||
if (event.data === event.attackingTeam) { | ||
comment += ` ${event.attackingTeam.name} gets the ball back.`; | ||
} else { | ||
comment += ` ${event.attackingTeam.name} can take control over the ball.`; | ||
} | ||
return comment; | ||
return this.rebound(`${(event.attackingPrimaryPlayer as Player).info.name} tries to score but the ball was blocked by the defence.`, event); | ||
} | ||
@@ -110,3 +106,5 @@ | ||
return `The game has ended! ${event.homeTeam.name} wins ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
} else if (event.gameInfo.homeGoals < event.gameInfo.awayGoals) { | ||
} | ||
if (event.gameInfo.homeGoals < event.gameInfo.awayGoals) { | ||
return `The game has ended! ${event.awayTeam.name} takes 3 points on the road! ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
@@ -113,0 +111,0 @@ } |
@@ -81,2 +81,4 @@ import { Event } from './enums/Event'; | ||
simulate: () => void; | ||
rebound(): boolean; | ||
reverseSide(current: FieldArea): FieldArea; | ||
handleEvent(event: GameEvent): void; | ||
@@ -86,5 +88,11 @@ eventLoop(): IterableIterator<GameEvent>; | ||
random(team: Team): number; | ||
simulateGoalAttempt(attackingTeam: Team, defendingTeam: Team, attacker: Player): Event; | ||
simulatePossession(attackingTeam: Team, defendingTeam: Team, action: Action): Event; | ||
simulateAction(action: Action, attacker: Player): Event; | ||
simulateAssistType(secondaryPlayer: Player): AssistType | null; | ||
simulateGoalType(primaryPlayer: Player, secondaryPlayer: Player): [GoalType, AssistType | null]; | ||
halfTime(): GameEvent; | ||
gameEnd(): GameEvent; | ||
goal(attackingPrimaryPlayer: Player, attackingSecondaryPlayer: Player): [GoalType, AssistType | null]; | ||
simulateEvent(): GameEvent; | ||
} |
183
Engine.js
@@ -92,33 +92,37 @@ "use strict"; | ||
} | ||
rebound() { | ||
return Math.floor(Math.random()) > this.reboundChance; | ||
} | ||
reverseSide(current) { | ||
const map = { | ||
[FieldArea_1.FieldArea.Offense]: FieldArea_1.FieldArea.Defence, | ||
[FieldArea_1.FieldArea.Midfield]: FieldArea_1.FieldArea.Midfield, | ||
[FieldArea_1.FieldArea.Defence]: FieldArea_1.FieldArea.Offense, | ||
}; | ||
return map[current]; | ||
} | ||
handleEvent(event) { | ||
if (event.event === Event_1.Event.Goal) { | ||
this.ballPosition = FieldArea_1.FieldArea.Midfield; | ||
this.ballPossession = this.teamWithoutBall(); | ||
} | ||
if (event.event === Event_1.Event.Save || event.event === Event_1.Event.Block) { | ||
const random = Math.floor(Math.random()); | ||
if (random > this.reboundChance) { | ||
switch (event.event) { | ||
case Event_1.Event.Goal: | ||
this.ballPosition = FieldArea_1.FieldArea.Midfield; | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = FieldArea_1.FieldArea.Defence; | ||
} | ||
break; | ||
case Event_1.Event.Save: | ||
case Event_1.Event.Block: | ||
if (!this.rebound()) { | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = FieldArea_1.FieldArea.Defence; | ||
} | ||
break; | ||
case Event_1.Event.Advance: | ||
this.ballPosition = Math.min(this.ballPosition + 1, FieldArea_1.FieldArea.Offense); | ||
break; | ||
case Event_1.Event.Retreat: | ||
this.ballPosition = Math.max(this.ballPosition - 1, FieldArea_1.FieldArea.Defence); | ||
break; | ||
case Event_1.Event.Defence: | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = this.reverseSide(this.ballPosition); | ||
break; | ||
} | ||
if (event.event === Event_1.Event.Advance) { | ||
if (this.ballPosition !== FieldArea_1.FieldArea.Offense) { | ||
this.ballPosition += 1; | ||
} | ||
} | ||
if (event.event === Event_1.Event.Retreat) { | ||
if (this.ballPosition !== FieldArea_1.FieldArea.Defence) { | ||
this.ballPosition -= 1; | ||
} | ||
} | ||
if (event.event === Event_1.Event.Defence) { | ||
this.ballPossession = this.teamWithoutBall(); | ||
if (this.ballPosition === FieldArea_1.FieldArea.Offense) { | ||
this.ballPosition = FieldArea_1.FieldArea.Defence; | ||
} | ||
else if (this.ballPosition === FieldArea_1.FieldArea.Defence) { | ||
this.ballPosition = FieldArea_1.FieldArea.Offense; | ||
} | ||
} | ||
} | ||
@@ -157,2 +161,20 @@ *eventLoop() { | ||
} | ||
simulateGoalAttempt(attackingTeam, defendingTeam, attacker) { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (attack + (attack * this.extraAttackOnChance) > defence) { | ||
const goalkeeper = defendingTeam.goalkeeperRating() + this.random(defendingTeam); | ||
const attackerRating = attacker.attackRating() + this.random(attackingTeam); | ||
return (attackerRating > goalkeeper) ? Event_1.Event.Goal : Event_1.Event.Save; | ||
} | ||
return Event_1.Event.Block; | ||
} | ||
simulatePossession(attackingTeam, defendingTeam, action) { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const possession = attackingTeam.possessionRating() + this.random(attackingTeam); | ||
if (defence > possession) { | ||
return Event_1.Event.Defence; | ||
} | ||
return (action === Action_1.Action.Retreat) ? Event_1.Event.Retreat : Event_1.Event.Possession; | ||
} | ||
simulateAction(action, attacker) { | ||
@@ -165,34 +187,26 @@ if (!this.ballPossession) { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (action === Action_1.Action.Advance) { | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (attack > defence) { | ||
return Event_1.Event.Advance; | ||
} | ||
return Event_1.Event.Defence; | ||
return (attack > defence) ? Event_1.Event.Advance : Event_1.Event.Defence; | ||
} | ||
if (action === Action_1.Action.GoalAttempt) { | ||
const attack = attacker.attackRating() + this.random(attackingTeam); | ||
if (attack + (attack * this.extraAttackOnChance) > defence) { | ||
const goalkeeper = defendingTeam.goalkeeperRating() + this.random(defendingTeam); | ||
const attackerRating = attacker.attackRating() + this.random(attackingTeam); | ||
if (attackerRating > goalkeeper) { | ||
return Event_1.Event.Goal; | ||
} | ||
return Event_1.Event.Save; | ||
} | ||
return Event_1.Event.Block; | ||
return this.simulateGoalAttempt(attackingTeam, defendingTeam, attacker); | ||
} | ||
const possession = attackingTeam.possessionRating() + this.random(attackingTeam); | ||
if (defence > possession) { | ||
return Event_1.Event.Defence; | ||
return this.simulatePossession(attackingTeam, defendingTeam, action); | ||
} | ||
simulateAssistType(secondaryPlayer) { | ||
const random = Math.random(); | ||
const secondaryPlayerAttributes = secondaryPlayer.attributes; | ||
const secondaryPlayerRating = secondaryPlayer.rating(); | ||
const shooting = secondaryPlayerRating.shooting; | ||
const passing = secondaryPlayerRating.passing; | ||
if (shooting > passing && random > 0.5) { | ||
return (random > 0.5) ? AssistType_1.AssistType.Deflection : AssistType_1.AssistType.Rebound; | ||
} | ||
if (action === Action_1.Action.Retreat) { | ||
return Event_1.Event.Retreat; | ||
if (secondaryPlayerAttributes.passing > secondaryPlayerAttributes.crossing && random > 0.5) { | ||
return AssistType_1.AssistType.Pass; | ||
} | ||
return Event_1.Event.Possession; | ||
return AssistType_1.AssistType.Cross; | ||
} | ||
simulateGoalType(primaryPlayer, secondaryPlayer) { | ||
const primaryPlayerAttributes = primaryPlayer.attributes; | ||
const secondaryPlayerAttributes = secondaryPlayer.attributes; | ||
const secondaryPlayerRating = secondaryPlayer.rating(); | ||
const assist = Math.random() > 0.5; | ||
@@ -202,37 +216,35 @@ if (!assist) { | ||
} | ||
const primaryPlayerAttributes = primaryPlayer.attributes; | ||
const random = Math.random(); | ||
let assistType = null; | ||
if (secondaryPlayerRating.shooting > secondaryPlayerRating.passing && random > 0.5) { | ||
assistType = (random > 0.5) ? AssistType_1.AssistType.Deflection : AssistType_1.AssistType.Rebound; | ||
const assistType = this.simulateAssistType(secondaryPlayer); | ||
return [ | ||
(primaryPlayerAttributes.heading > primaryPlayerAttributes.finishing && random > 0.5) ? GoalType_1.GoalType.Header : [GoalType_1.GoalType.Volley, GoalType_1.GoalType.Shot][Math.floor(Math.random() * 2)], | ||
assistType, | ||
]; | ||
} | ||
halfTime() { | ||
this.ballPossession = this.startedWithBall === this.homeTeam ? this.awayTeam : this.homeTeam; | ||
this.ballPosition = FieldArea_1.FieldArea.Midfield; | ||
return this.gameEvent(Event_1.Event.HalfTime); | ||
} | ||
gameEnd() { | ||
this.gameEnded = true; | ||
return this.gameEvent(Event_1.Event.GameEnd); | ||
} | ||
goal(attackingPrimaryPlayer, attackingSecondaryPlayer) { | ||
if (this.ballPossession === this.homeTeam) { | ||
this.gameInfo.homeGoals += 1; | ||
} | ||
else { | ||
if (secondaryPlayerAttributes.passing > secondaryPlayerAttributes.crossing && random > 0.5) { | ||
assistType = AssistType_1.AssistType.Pass; | ||
} | ||
else { | ||
assistType = AssistType_1.AssistType.Cross; | ||
} | ||
this.gameInfo.awayGoals += 1; | ||
} | ||
let goalType = null; | ||
if (assistType === AssistType_1.AssistType.Pass) { | ||
goalType = GoalType_1.GoalType.Shot; | ||
} | ||
else { | ||
goalType = (primaryPlayerAttributes.heading > primaryPlayerAttributes.finishing && random > 0.5) ? GoalType_1.GoalType.Header : GoalType_1.GoalType.Volley; | ||
} | ||
return [goalType, assistType]; | ||
return this.simulateGoalType(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
} | ||
simulateEvent() { | ||
if (this.gameInfo.matchMinute == this.gameTime / 2) { | ||
this.ballPossession = this.startedWithBall === this.homeTeam ? this.awayTeam : this.homeTeam; | ||
this.ballPosition = FieldArea_1.FieldArea.Midfield; | ||
return this.gameEvent(Event_1.Event.HalfTime); | ||
} | ||
if (this.gameInfo.matchMinute >= this.gameTime) { | ||
this.gameEnded = true; | ||
return this.gameEvent(Event_1.Event.GameEnd); | ||
} | ||
if (!this.ballPossession) { | ||
if (this.gameInfo.matchMinute == this.gameTime / 2) | ||
return this.halfTime(); | ||
if (this.gameInfo.matchMinute >= this.gameTime) | ||
return this.gameEnd(); | ||
if (!this.ballPossession) | ||
return this.gameEvent(Event_1.Event.EventLess); | ||
} | ||
const attackingPrimaryPlayer = this.ballPossession.attacker(this.ballPosition); | ||
@@ -242,3 +254,2 @@ const attackingSecondaryPlayer = this.ballPossession.attacker(this.ballPosition, [attackingPrimaryPlayer]); | ||
const defendingPrimaryPlayer = defendingTeam.defender(this.ballPosition); | ||
const defendingSecondaryPlayer = defendingTeam.defender(this.ballPosition, [defendingPrimaryPlayer]); | ||
const action = this.ballPossession.simulateMove(this.ballPosition, this.gameInfo); | ||
@@ -249,13 +260,7 @@ let goalType = null; | ||
if (event === Event_1.Event.Goal) { | ||
[goalType, assist] = this.simulateGoalType(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
if (this.ballPossession === this.homeTeam) { | ||
this.gameInfo.homeGoals += 1; | ||
} | ||
else { | ||
this.gameInfo.awayGoals += 1; | ||
} | ||
[goalType, assist] = this.goal(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
} | ||
return this.gameEvent(event, null, attackingPrimaryPlayer, attackingSecondaryPlayer, defendingPrimaryPlayer, defendingSecondaryPlayer, goalType, assist); | ||
return this.gameEvent(event, null, attackingPrimaryPlayer, attackingSecondaryPlayer, defendingPrimaryPlayer, defendingTeam.defender(this.ballPosition, [defendingPrimaryPlayer]), goalType, assist); | ||
} | ||
} | ||
exports.default = Engine; |
219
Engine.ts
@@ -1,10 +0,10 @@ | ||
import { Event } from './enums/Event'; | ||
import {Event} from './enums/Event'; | ||
import Team from './Team'; | ||
import {GameEvent} from './types/GameEvent'; | ||
import {GameInfo} from './types/GameInfo'; | ||
import { FieldArea } from "./enums/FieldArea"; | ||
import { Action } from './enums/Action'; | ||
import {FieldArea} from "./enums/FieldArea"; | ||
import {Action} from './enums/Action'; | ||
import Player, {PlayerRating} from "./Player"; | ||
import { GoalType } from "./enums/GoalType"; | ||
import { AssistType } from "./enums/AssistType"; | ||
import {GoalType} from "./enums/GoalType"; | ||
import {AssistType} from "./enums/AssistType"; | ||
@@ -138,37 +138,44 @@ export default class Engine { | ||
handleEvent(event: GameEvent) { | ||
if (event.event === Event.Goal) { | ||
this.ballPosition = FieldArea.Midfield; | ||
this.ballPossession = this.teamWithoutBall(); | ||
} | ||
rebound(): boolean { | ||
return Math.floor(Math.random()) > this.reboundChance; | ||
} | ||
if (event.event === Event.Save || event.event === Event.Block) { | ||
const random = Math.floor(Math.random()); | ||
reverseSide(current: FieldArea): FieldArea { | ||
const map = { | ||
[FieldArea.Offense]: FieldArea.Defence, | ||
[FieldArea.Midfield]: FieldArea.Midfield, | ||
[FieldArea.Defence]: FieldArea.Offense, | ||
}; | ||
if (random > this.reboundChance) { | ||
return map[current]; | ||
} | ||
handleEvent(event: GameEvent) { | ||
switch (event.event) { | ||
case Event.Goal: | ||
this.ballPosition = FieldArea.Midfield; | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = FieldArea.Defence; | ||
} | ||
} | ||
if (event.event === Event.Advance) { | ||
if (this.ballPosition !== FieldArea.Offense) { | ||
this.ballPosition += 1; | ||
} | ||
} | ||
break; | ||
case Event.Save: | ||
case Event.Block: | ||
if (!this.rebound()) { | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = FieldArea.Defence; | ||
} | ||
if (event.event === Event.Retreat) { | ||
if (this.ballPosition !== FieldArea.Defence) { | ||
this.ballPosition -= 1; | ||
} | ||
} | ||
break; | ||
case Event.Advance: | ||
this.ballPosition = Math.min(this.ballPosition + 1, FieldArea.Offense); | ||
if (event.event === Event.Defence) { | ||
this.ballPossession = this.teamWithoutBall(); | ||
break; | ||
case Event.Retreat: | ||
this.ballPosition = Math.max(this.ballPosition - 1, FieldArea.Defence); | ||
if (this.ballPosition === FieldArea.Offense) { | ||
this.ballPosition = FieldArea.Defence; | ||
} else if (this.ballPosition === FieldArea.Defence) { | ||
this.ballPosition = FieldArea.Offense; | ||
} | ||
break; | ||
case Event.Defence: | ||
this.ballPossession = this.teamWithoutBall(); | ||
this.ballPosition = this.reverseSide(this.ballPosition); | ||
break; | ||
} | ||
@@ -223,2 +230,27 @@ } | ||
simulateGoalAttempt(attackingTeam: Team, defendingTeam: Team, attacker: Player): Event { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (attack + (attack * this.extraAttackOnChance) > defence) { | ||
const goalkeeper = defendingTeam.goalkeeperRating() + this.random(defendingTeam); | ||
const attackerRating = attacker.attackRating() + this.random(attackingTeam); | ||
return (attackerRating > goalkeeper) ? Event.Goal : Event.Save; | ||
} | ||
return Event.Block; | ||
} | ||
simulatePossession(attackingTeam: Team, defendingTeam: Team, action: Action): Event { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const possession = attackingTeam.possessionRating() + this.random(attackingTeam); | ||
if (defence > possession) { | ||
return Event.Defence; | ||
} | ||
return (action === Action.Retreat) ? Event.Retreat : Event.Possession; | ||
} | ||
simulateAction(action: Action, attacker: Player): Event { | ||
@@ -232,47 +264,34 @@ if (!this.ballPossession) { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (action === Action.Advance) { | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (attack > defence) { | ||
return Event.Advance; | ||
} | ||
return Event.Defence; | ||
return (attack > defence) ? Event.Advance : Event.Defence; | ||
} | ||
if (action === Action.GoalAttempt) { | ||
const attack = attacker.attackRating() + this.random(attackingTeam); | ||
return this.simulateGoalAttempt(attackingTeam, defendingTeam, attacker); | ||
} | ||
if (attack + (attack * this.extraAttackOnChance) > defence) { | ||
const goalkeeper = defendingTeam.goalkeeperRating() + this.random(defendingTeam); | ||
const attackerRating = attacker.attackRating() + this.random(attackingTeam); | ||
return this.simulatePossession(attackingTeam, defendingTeam, action); | ||
} | ||
if (attackerRating > goalkeeper) { | ||
return Event.Goal; | ||
} | ||
simulateAssistType(secondaryPlayer: Player): AssistType | null { | ||
const random = Math.random(); | ||
const secondaryPlayerAttributes = secondaryPlayer.attributes; | ||
const secondaryPlayerRating = secondaryPlayer.rating(); | ||
const shooting = (secondaryPlayerRating as PlayerRating).shooting; | ||
const passing = (secondaryPlayerRating as PlayerRating).passing; | ||
return Event.Save; | ||
} | ||
return Event.Block; | ||
if (shooting > passing && random > 0.5) { | ||
return (random > 0.5) ? AssistType.Deflection : AssistType.Rebound; | ||
} | ||
const possession = attackingTeam.possessionRating() + this.random(attackingTeam); | ||
if (defence > possession) { | ||
return Event.Defence; | ||
if (secondaryPlayerAttributes.passing > secondaryPlayerAttributes.crossing && random > 0.5) { | ||
return AssistType.Pass; | ||
} | ||
if (action === Action.Retreat) { | ||
return Event.Retreat; | ||
} | ||
return Event.Possession; | ||
return AssistType.Cross; | ||
} | ||
simulateGoalType(primaryPlayer: Player, secondaryPlayer: Player): [GoalType, AssistType | null] { | ||
const primaryPlayerAttributes = primaryPlayer.attributes; | ||
const secondaryPlayerAttributes = secondaryPlayer.attributes; | ||
const secondaryPlayerRating = secondaryPlayer.rating(); | ||
const assist = Math.random() > 0.5; | ||
@@ -284,44 +303,40 @@ | ||
const primaryPlayerAttributes = primaryPlayer.attributes; | ||
const random = Math.random(); | ||
let assistType = null; | ||
const assistType = this.simulateAssistType(secondaryPlayer); | ||
if ((secondaryPlayerRating as PlayerRating).shooting > (secondaryPlayerRating as PlayerRating).passing && random > 0.5) { | ||
assistType = (random > 0.5) ? AssistType.Deflection : AssistType.Rebound; | ||
} else { | ||
if (secondaryPlayerAttributes.passing > secondaryPlayerAttributes.crossing && random > 0.5) { | ||
assistType = AssistType.Pass; | ||
} else { | ||
assistType = AssistType.Cross; | ||
} | ||
} | ||
return [ | ||
(primaryPlayerAttributes.heading > primaryPlayerAttributes.finishing && random > 0.5) ? GoalType.Header : [GoalType.Volley, GoalType.Shot][Math.floor(Math.random() * 2)], | ||
assistType, | ||
]; | ||
} | ||
let goalType = null; | ||
halfTime(): GameEvent { | ||
this.ballPossession = this.startedWithBall === this.homeTeam ? this.awayTeam : this.homeTeam; | ||
this.ballPosition = FieldArea.Midfield; | ||
if (assistType === AssistType.Pass) { | ||
goalType = GoalType.Shot; | ||
return this.gameEvent(Event.HalfTime); | ||
} | ||
gameEnd(): GameEvent { | ||
this.gameEnded = true; | ||
return this.gameEvent(Event.GameEnd); | ||
} | ||
goal(attackingPrimaryPlayer: Player, attackingSecondaryPlayer: Player): [GoalType, AssistType | null] { | ||
if (this.ballPossession === this.homeTeam) { | ||
this.gameInfo.homeGoals += 1; | ||
} else { | ||
goalType = (primaryPlayerAttributes.heading > primaryPlayerAttributes.finishing && random > 0.5) ? GoalType.Header : GoalType.Volley; | ||
this.gameInfo.awayGoals += 1; | ||
} | ||
return [goalType, assistType]; | ||
return this.simulateGoalType(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
} | ||
simulateEvent(): GameEvent { | ||
if (this.gameInfo.matchMinute == this.gameTime / 2) { | ||
this.ballPossession = this.startedWithBall === this.homeTeam ? this.awayTeam : this.homeTeam; | ||
this.ballPosition = FieldArea.Midfield; | ||
if (this.gameInfo.matchMinute == this.gameTime / 2) return this.halfTime(); | ||
if (this.gameInfo.matchMinute >= this.gameTime) return this.gameEnd(); | ||
if (!this.ballPossession) return this.gameEvent(Event.EventLess); | ||
return this.gameEvent(Event.HalfTime); | ||
} | ||
if (this.gameInfo.matchMinute >= this.gameTime) { | ||
this.gameEnded = true; | ||
return this.gameEvent(Event.GameEnd); | ||
} | ||
if (!this.ballPossession) { | ||
return this.gameEvent(Event.EventLess); | ||
} | ||
const attackingPrimaryPlayer = this.ballPossession.attacker(this.ballPosition); | ||
@@ -331,19 +346,9 @@ const attackingSecondaryPlayer = this.ballPossession.attacker(this.ballPosition, [attackingPrimaryPlayer]); | ||
const defendingPrimaryPlayer = defendingTeam.defender(this.ballPosition); | ||
const defendingSecondaryPlayer = defendingTeam.defender(this.ballPosition, [defendingPrimaryPlayer]); | ||
const action = this.ballPossession.simulateMove(this.ballPosition, this.gameInfo); | ||
let goalType = null; | ||
let assist = null; | ||
const event = this.simulateAction( | ||
action, | ||
attackingPrimaryPlayer, | ||
); | ||
const event = this.simulateAction(action, attackingPrimaryPlayer); | ||
if (event === Event.Goal) { | ||
[goalType, assist] = this.simulateGoalType(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
if (this.ballPossession === this.homeTeam) { | ||
this.gameInfo.homeGoals += 1; | ||
} else { | ||
this.gameInfo.awayGoals += 1; | ||
} | ||
[goalType, assist] = this.goal(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
} | ||
@@ -357,3 +362,3 @@ | ||
defendingPrimaryPlayer, | ||
defendingSecondaryPlayer, | ||
defendingTeam.defender(this.ballPosition, [defendingPrimaryPlayer]), | ||
goalType, | ||
@@ -360,0 +365,0 @@ assist, |
@@ -32,3 +32,3 @@ /// <reference types="node" /> | ||
events: GameEvent[]; | ||
constructor(homeTeam: Team, awayTeam: Team); | ||
constructor(homeTeam: Team, awayTeam: Team, commentator: Commentator); | ||
start(): void; | ||
@@ -35,0 +35,0 @@ simulate(): void; |
@@ -6,3 +6,2 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Commentator_1 = __importDefault(require("./Commentator")); | ||
const events_1 = require("events"); | ||
@@ -12,3 +11,3 @@ const Engine_1 = __importDefault(require("./Engine")); | ||
class Game extends events_1.EventEmitter { | ||
constructor(homeTeam, awayTeam) { | ||
constructor(homeTeam, awayTeam, commentator) { | ||
super(); | ||
@@ -38,3 +37,3 @@ /** | ||
this.awayTeam = awayTeam; | ||
this.commentator = new Commentator_1.default(); | ||
this.commentator = commentator; | ||
this.engine = new Engine_1.default(this.homeTeam, this.awayTeam); | ||
@@ -41,0 +40,0 @@ } |
@@ -39,3 +39,3 @@ import Team from './Team'; | ||
constructor(homeTeam: Team, awayTeam: Team) { | ||
constructor(homeTeam: Team, awayTeam: Team, commentator: Commentator) { | ||
super(); | ||
@@ -45,3 +45,3 @@ | ||
this.awayTeam = awayTeam; | ||
this.commentator = new Commentator(); | ||
this.commentator = commentator; | ||
this.engine = new Engine(this.homeTeam, this.awayTeam); | ||
@@ -48,0 +48,0 @@ } |
{ | ||
"name": "@bleckert/football-simulator", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"scripts": { | ||
@@ -16,4 +16,5 @@ "start": "ts-node test/index.ts", | ||
"dependencies": { | ||
"events": "^3.3.0", | ||
"ws": "^7.1.2" | ||
} | ||
} |
@@ -95,4 +95,7 @@ import { Position } from './enums/Position'; | ||
rating(): PlayerRating | GoalkeeperRating; | ||
averageRating(ratings: number[]): number; | ||
defenceRating(): number; | ||
possessionRating(): number; | ||
attackRating(): number; | ||
attributesAverage(...attributes: number[]): number; | ||
} |
@@ -35,9 +35,30 @@ "use strict"; | ||
} | ||
averageRating(ratings) { | ||
return ratings.reduce((acc, curr) => acc + curr) / ratings.length; | ||
} | ||
defenceRating() { | ||
const rating = this.rating(); | ||
return this.averageRating([ | ||
rating.defending, | ||
rating.physique, | ||
rating.pace, | ||
]); | ||
} | ||
possessionRating() { | ||
const rating = this.rating(); | ||
return this.averageRating([ | ||
rating.dribbling, | ||
rating.passing, | ||
rating.physique, | ||
]); | ||
} | ||
attackRating() { | ||
const rating = this.rating(); | ||
return (rating.dribbling | ||
+ rating.pace | ||
+ rating.passing | ||
+ rating.shooting | ||
+ rating.physique) / 5; | ||
return this.averageRating([ | ||
rating.dribbling, | ||
rating.pace, | ||
rating.passing, | ||
rating.shooting, | ||
rating.physique, | ||
]); | ||
} | ||
@@ -44,0 +65,0 @@ attributesAverage(...attributes) { |
@@ -229,12 +229,36 @@ import {Position} from './enums/Position'; | ||
averageRating(ratings: number[]): number { | ||
return ratings.reduce((acc, curr) => acc + curr) / ratings.length; | ||
} | ||
defenceRating(): number { | ||
const rating = this.rating(); | ||
return this.averageRating([ | ||
(rating as PlayerRating).defending, | ||
(rating as PlayerRating).physique, | ||
(rating as PlayerRating).pace, | ||
]); | ||
} | ||
possessionRating(): number { | ||
const rating = this.rating(); | ||
return this.averageRating([ | ||
(rating as PlayerRating).dribbling, | ||
(rating as PlayerRating).passing, | ||
(rating as PlayerRating).physique, | ||
]); | ||
} | ||
attackRating(): number { | ||
const rating = this.rating(); | ||
return ( | ||
(rating as PlayerRating).dribbling | ||
+ (rating as PlayerRating).pace | ||
+ (rating as PlayerRating).passing | ||
+ (rating as PlayerRating).shooting | ||
+ (rating as PlayerRating).physique | ||
) / 5; | ||
return this.averageRating([ | ||
(rating as PlayerRating).dribbling, | ||
(rating as PlayerRating).pace, | ||
(rating as PlayerRating).passing, | ||
(rating as PlayerRating).shooting, | ||
(rating as PlayerRating).physique, | ||
]); | ||
} | ||
@@ -241,0 +265,0 @@ |
import { GameEvent } from './types/GameEvent'; | ||
import Player from "./Player"; | ||
import Team from "./Team"; | ||
export default class Reporter { | ||
gameEvents: GameEvent[]; | ||
home: { | ||
goals: number; | ||
possession: number; | ||
shots: number; | ||
shotsOnGoal: number; | ||
}; | ||
away: { | ||
goals: number; | ||
possession: number; | ||
shots: number; | ||
shotsOnGoal: number; | ||
}; | ||
scoreSheet: { | ||
matchMinute: number; | ||
goalScorer: Player | null; | ||
assist: Player | false; | ||
team: Team; | ||
}[]; | ||
constructor(gameEvents: GameEvent[]); | ||
registerEvent: (gameEvent: GameEvent) => void; | ||
getReport(): { | ||
homeGoals: number; | ||
awayGoals: number; | ||
homePossession: number; | ||
awayPossession: number; | ||
homeShots: number; | ||
homeShotsOnGoal: number; | ||
awayShots: number; | ||
awayShotsOnGoal: number; | ||
home: { | ||
possession: number; | ||
goals: number; | ||
shots: number; | ||
shotsOnGoal: number; | ||
}; | ||
away: { | ||
possession: number; | ||
goals: number; | ||
shots: number; | ||
shotsOnGoal: number; | ||
}; | ||
scoreSheet: { | ||
matchMinute: number; | ||
goalScorer: string | null; | ||
assist: string | false; | ||
team: string; | ||
goalScorer: Player | null; | ||
assist: false | Player; | ||
team: Team; | ||
}[]; | ||
}; | ||
} |
@@ -6,65 +6,43 @@ "use strict"; | ||
constructor(gameEvents) { | ||
this.gameEvents = gameEvents; | ||
} | ||
getReport() { | ||
let homeGoals = 0; | ||
let awayGoals = 0; | ||
let homePossession = 0; | ||
let awayPossession = 0; | ||
let homeShots = 0; | ||
let awayShots = 0; | ||
let homeShotsOnGoal = 0; | ||
let awayShotsOnGoal = 0; | ||
const scoreSheet = []; | ||
this.gameEvents.forEach(gameEvent => { | ||
if (gameEvent.attackingTeam && gameEvent.attackingTeam.home) { | ||
homePossession += 1; | ||
this.home = { | ||
goals: 0, | ||
possession: 0, | ||
shots: 0, | ||
shotsOnGoal: 0, | ||
}; | ||
this.away = { | ||
goals: 0, | ||
possession: 0, | ||
shots: 0, | ||
shotsOnGoal: 0, | ||
}; | ||
this.scoreSheet = []; | ||
this.registerEvent = (gameEvent) => { | ||
const side = (gameEvent.attackingTeam && gameEvent.attackingTeam.home) ? 'home' : 'away'; | ||
this[side].possession += 1; | ||
if ([Event_1.Event.Save, Event_1.Event.Goal, Event_1.Event.Block].includes(gameEvent.event)) { | ||
this[side].shots += 1; | ||
} | ||
else { | ||
awayPossession += 1; | ||
if ([Event_1.Event.Save, Event_1.Event.Goal].includes(gameEvent.event)) { | ||
this[side].shotsOnGoal += 1; | ||
} | ||
if (gameEvent.event === Event_1.Event.Goal || gameEvent.event === Event_1.Event.Save) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeShots += 1; | ||
homeShotsOnGoal += 1; | ||
} | ||
else { | ||
awayShots += 1; | ||
awayShotsOnGoal += 1; | ||
} | ||
} | ||
if (gameEvent.event === Event_1.Event.Block) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeShots += 1; | ||
} | ||
else { | ||
awayShots += 1; | ||
} | ||
} | ||
if (gameEvent.event === Event_1.Event.Goal) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeGoals += 1; | ||
} | ||
else { | ||
awayGoals += 1; | ||
} | ||
scoreSheet.push({ | ||
this[side].goals += 1; | ||
this.scoreSheet.push({ | ||
matchMinute: gameEvent.gameInfo.matchMinute, | ||
goalScorer: (gameEvent.attackingPrimaryPlayer) ? `${gameEvent.attackingPrimaryPlayer.info.number}. ${gameEvent.attackingPrimaryPlayer.info.name}` : null, | ||
assist: (gameEvent.assistType && gameEvent.attackingSecondaryPlayer) ? gameEvent.attackingSecondaryPlayer.info.name : false, | ||
team: gameEvent.attackingTeam.name, | ||
goalScorer: gameEvent.attackingPrimaryPlayer, | ||
assist: (gameEvent.assistType && gameEvent.attackingSecondaryPlayer) ? gameEvent.attackingSecondaryPlayer : false, | ||
team: gameEvent.attackingTeam, | ||
}); | ||
} | ||
}); | ||
const totalPossession = homePossession + awayPossession; | ||
}; | ||
this.gameEvents = gameEvents; | ||
} | ||
getReport() { | ||
this.gameEvents.forEach(this.registerEvent); | ||
const totalPossession = this.home.possession + this.away.possession; | ||
return { | ||
homeGoals, | ||
awayGoals, | ||
homePossession: homePossession / totalPossession, | ||
awayPossession: awayPossession / totalPossession, | ||
homeShots, | ||
homeShotsOnGoal, | ||
awayShots, | ||
awayShotsOnGoal, | ||
scoreSheet, | ||
home: { ...this.home, possession: this.home.possession / totalPossession }, | ||
away: { ...this.away, possession: this.away.possession / totalPossession }, | ||
scoreSheet: this.scoreSheet, | ||
}; | ||
@@ -71,0 +49,0 @@ } |
103
Reporter.ts
@@ -1,7 +0,24 @@ | ||
import { GameEvent } from './types/GameEvent'; | ||
import { Event } from './enums/Event'; | ||
import {GameEvent} from './types/GameEvent'; | ||
import {Event} from './enums/Event'; | ||
import Player from "./Player"; | ||
import Team from "./Team"; | ||
export default class Reporter { | ||
gameEvents: GameEvent[]; | ||
home = { | ||
goals: 0, | ||
possession: 0, | ||
shots: 0, | ||
shotsOnGoal: 0, | ||
}; | ||
away = { | ||
goals: 0, | ||
possession: 0, | ||
shots: 0, | ||
shotsOnGoal: 0, | ||
}; | ||
scoreSheet: { matchMinute: number, goalScorer: Player | null, assist: Player | false, team: Team }[] = []; | ||
constructor(gameEvents: GameEvent[]) { | ||
@@ -11,68 +28,38 @@ this.gameEvents = gameEvents; | ||
getReport() { | ||
let homeGoals = 0; | ||
let awayGoals = 0; | ||
let homePossession = 0; | ||
let awayPossession = 0; | ||
let homeShots = 0; | ||
let awayShots = 0; | ||
let homeShotsOnGoal = 0; | ||
let awayShotsOnGoal = 0; | ||
const scoreSheet: { matchMinute: number, goalScorer: string | null, assist: string | false, team: string }[] = []; | ||
registerEvent = (gameEvent: GameEvent): void => { | ||
const side = (gameEvent.attackingTeam && gameEvent.attackingTeam.home) ? 'home' : 'away'; | ||
this.gameEvents.forEach(gameEvent => { | ||
if (gameEvent.attackingTeam && gameEvent.attackingTeam.home) { | ||
homePossession += 1; | ||
} else { | ||
awayPossession += 1; | ||
} | ||
this[side].possession += 1; | ||
if (gameEvent.event === Event.Goal || gameEvent.event === Event.Save) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeShots += 1; | ||
homeShotsOnGoal += 1; | ||
} else { | ||
awayShots += 1; | ||
awayShotsOnGoal += 1; | ||
} | ||
} | ||
if ([Event.Save, Event.Goal, Event.Block].includes(gameEvent.event)) { | ||
this[side].shots += 1; | ||
} | ||
if (gameEvent.event === Event.Block) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeShots += 1; | ||
} else { | ||
awayShots += 1; | ||
} | ||
} | ||
if ([Event.Save, Event.Goal].includes(gameEvent.event)) { | ||
this[side].shotsOnGoal += 1; | ||
} | ||
if (gameEvent.event === Event.Goal) { | ||
if (gameEvent.attackingTeam.home) { | ||
homeGoals += 1; | ||
} else { | ||
awayGoals += 1; | ||
} | ||
if (gameEvent.event === Event.Goal) { | ||
this[side].goals += 1; | ||
scoreSheet.push({ | ||
matchMinute: gameEvent.gameInfo.matchMinute, | ||
goalScorer: (gameEvent.attackingPrimaryPlayer) ? `${gameEvent.attackingPrimaryPlayer.info.number}. ${gameEvent.attackingPrimaryPlayer.info.name}` : null, | ||
assist: (gameEvent.assistType && gameEvent.attackingSecondaryPlayer) ? gameEvent.attackingSecondaryPlayer.info.name : false, | ||
team: gameEvent.attackingTeam.name, | ||
}); | ||
} | ||
}); | ||
this.scoreSheet.push({ | ||
matchMinute: gameEvent.gameInfo.matchMinute, | ||
goalScorer: gameEvent.attackingPrimaryPlayer, | ||
assist: (gameEvent.assistType && gameEvent.attackingSecondaryPlayer) ? gameEvent.attackingSecondaryPlayer : false, | ||
team: gameEvent.attackingTeam, | ||
}); | ||
} | ||
}; | ||
const totalPossession = homePossession + awayPossession; | ||
getReport() { | ||
this.gameEvents.forEach(this.registerEvent); | ||
const totalPossession = this.home.possession + this.away.possession; | ||
return { | ||
homeGoals, | ||
awayGoals, | ||
homePossession: homePossession / totalPossession, | ||
awayPossession: awayPossession / totalPossession, | ||
homeShots, | ||
homeShotsOnGoal, | ||
awayShots, | ||
awayShotsOnGoal, | ||
scoreSheet, | ||
home: { ...this.home, possession: this.home.possession / totalPossession }, | ||
away: { ...this.away, possession: this.away.possession / totalPossession }, | ||
scoreSheet: this.scoreSheet, | ||
}; | ||
} | ||
} |
@@ -8,9 +8,2 @@ import Player from './Player'; | ||
} | ||
interface Weights { | ||
[x: number]: { | ||
defenders: number; | ||
midfielders: number; | ||
attackers: number; | ||
}; | ||
} | ||
export default class Team implements TeamInterface { | ||
@@ -28,2 +21,3 @@ players: Player[]; | ||
getFieldPlayers(exclude?: Player[]): Player[]; | ||
averageRating(map: (player: Player) => number, players?: Player[] | null): number; | ||
goalkeeperRating(): number; | ||
@@ -34,6 +28,5 @@ defenceRating(): number; | ||
simulateMove(ballPosition: FieldArea, gameInfo: GameInfo): Action; | ||
getProbablePlayer(fieldPosition: FieldArea, weights: Weights, exclude?: Player[]): Player; | ||
getProbablePlayer(fieldPosition: FieldArea, attacker: boolean, exclude?: Player[]): Player; | ||
attacker(fieldPosition: FieldArea, exclude?: Player[]): Player; | ||
defender(fieldPosition: FieldArea, exclude?: Player[]): Player; | ||
} | ||
export {}; |
112
Team.js
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -6,2 +9,22 @@ const Position_1 = require("./enums/Position"); | ||
const Action_1 = require("./enums/Action"); | ||
const getRandomElement_1 = __importDefault(require("./lib/getRandomElement")); | ||
function createWeights(attacker = false) { | ||
return { | ||
[FieldArea_1.FieldArea.Defence]: { | ||
defenders: (attacker) ? 0.6 : 0.1, | ||
midfielders: 0.3, | ||
attackers: (attacker) ? 0.1 : 0.6, | ||
}, | ||
[FieldArea_1.FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea_1.FieldArea.Offense]: { | ||
defenders: (attacker) ? 0.1 : 0.6, | ||
midfielders: 0.3, | ||
attackers: (attacker) ? 0.6 : 0.1, | ||
} | ||
}; | ||
} | ||
class Team { | ||
@@ -26,51 +49,28 @@ constructor(home, name, players) { | ||
} | ||
averageRating(map, players = null) { | ||
const playersList = (players) ? players : this.getFieldPlayers(); | ||
return playersList.map(map).reduce((a, b) => a + b) / playersList.length; | ||
} | ||
goalkeeperRating() { | ||
const goalkeepers = this.getGoalkeepers(); | ||
if (!goalkeepers.length) { | ||
return 0; | ||
} | ||
return goalkeepers.map(player => player.ratingAverage()).reduce((a, b) => a + b) / goalkeepers.length; | ||
return this.averageRating(player => player.ratingAverage(), this.getGoalkeepers()); | ||
} | ||
defenceRating() { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => { | ||
const rating = player.rating(); | ||
return (rating.defending | ||
+ rating.physique | ||
+ rating.pace) / 3; | ||
}).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.defenceRating()); | ||
} | ||
possessionRating() { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => { | ||
const rating = player.rating(); | ||
return (rating.dribbling | ||
+ rating.passing | ||
+ rating.physique) / 3; | ||
}).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.possessionRating()); | ||
} | ||
attackRating() { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => player.attackRating()).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.attackRating()); | ||
} | ||
simulateMove(ballPosition, gameInfo) { | ||
const random = Math.floor(Math.random() * 11); | ||
if (ballPosition === FieldArea_1.FieldArea.Offense) { | ||
if (random > 5) { | ||
return Action_1.Action.GoalAttempt; | ||
} | ||
if (random > 3) { | ||
return Action_1.Action.Stay; | ||
} | ||
return Action_1.Action.Retreat; | ||
const options = [[Action_1.Action.GoalAttempt, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
return getRandomElement_1.default(options); | ||
} | ||
if (random > 6) { | ||
return Action_1.Action.Advance; | ||
} | ||
if (random > 3) { | ||
return Action_1.Action.Stay; | ||
} | ||
return Action_1.Action.Retreat; | ||
const options = [[Action_1.Action.Advance, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
return getRandomElement_1.default(options); | ||
} | ||
getProbablePlayer(fieldPosition, weights, exclude = []) { | ||
getProbablePlayer(fieldPosition, attacker, exclude = []) { | ||
const weights = createWeights(attacker); | ||
const players = []; | ||
@@ -93,42 +93,8 @@ this.getFieldPlayers(exclude).forEach(player => { | ||
attacker(fieldPosition, exclude = []) { | ||
const weights = { | ||
[FieldArea_1.FieldArea.Defence]: { | ||
defenders: 0.6, | ||
midfielders: 0.3, | ||
attackers: 0.1, | ||
}, | ||
[FieldArea_1.FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea_1.FieldArea.Offense]: { | ||
defenders: 0.1, | ||
midfielders: 0.3, | ||
attackers: 0.6, | ||
} | ||
}; | ||
return this.getProbablePlayer(fieldPosition, weights, exclude); | ||
return this.getProbablePlayer(fieldPosition, true, exclude); | ||
} | ||
defender(fieldPosition, exclude = []) { | ||
const weights = { | ||
[FieldArea_1.FieldArea.Defence]: { | ||
defenders: 0.1, | ||
midfielders: 0.3, | ||
attackers: 0.6, | ||
}, | ||
[FieldArea_1.FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea_1.FieldArea.Offense]: { | ||
defenders: 0.6, | ||
midfielders: 0.3, | ||
attackers: 0.1, | ||
} | ||
}; | ||
return this.getProbablePlayer(fieldPosition, weights, exclude); | ||
return this.getProbablePlayer(fieldPosition, false, exclude); | ||
} | ||
} | ||
exports.default = Team; |
137
Team.ts
@@ -1,6 +0,7 @@ | ||
import Player, {PlayerRating} from './Player'; | ||
import {defencePositions, midfieldPositions, Position} from './enums/Position'; | ||
import {FieldArea} from "./enums/FieldArea"; | ||
import {GameInfo} from "./types/GameInfo"; | ||
import {Action} from "./enums/Action"; | ||
import Player, { PlayerRating } from './Player'; | ||
import { defencePositions, midfieldPositions, Position } from './enums/Position'; | ||
import { FieldArea } from "./enums/FieldArea"; | ||
import { GameInfo } from "./types/GameInfo"; | ||
import { Action } from "./enums/Action"; | ||
import getRandomElement from "./lib/getRandomElement"; | ||
@@ -19,2 +20,22 @@ export interface TeamInterface { | ||
function createWeights(attacker = false): Weights { | ||
return { | ||
[FieldArea.Defence]: { | ||
defenders: (attacker) ? 0.6 : 0.1, | ||
midfielders: 0.3, | ||
attackers: (attacker) ? 0.1 : 0.6, | ||
}, | ||
[FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea.Offense]: { | ||
defenders: (attacker) ? 0.1 : 0.6, | ||
midfielders: 0.3, | ||
attackers: (attacker) ? 0.6 : 0.1, | ||
} | ||
}; | ||
} | ||
export default class Team implements TeamInterface { | ||
@@ -47,73 +68,37 @@ players: Player[]; | ||
averageRating(map: (player: Player) => number, players: Player[]|null = null): number { | ||
const playersList = (players) ? players : this.getFieldPlayers(); | ||
return playersList.map(map).reduce((a, b) => a + b) / playersList.length; | ||
} | ||
goalkeeperRating(): number { | ||
const goalkeepers = this.getGoalkeepers(); | ||
if (!goalkeepers.length) { | ||
return 0; | ||
} | ||
return goalkeepers.map(player => player.ratingAverage()).reduce((a, b) => a + b) / goalkeepers.length; | ||
return this.averageRating(player => player.ratingAverage(), this.getGoalkeepers()); | ||
} | ||
defenceRating(): number { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => { | ||
const rating = player.rating(); | ||
return ( | ||
(rating as PlayerRating).defending | ||
+ (rating as PlayerRating).physique | ||
+ (rating as PlayerRating).pace | ||
) / 3; | ||
}).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.defenceRating()); | ||
} | ||
possessionRating(): number { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => { | ||
const rating = player.rating(); | ||
return ( | ||
(rating as PlayerRating).dribbling | ||
+ (rating as PlayerRating).passing | ||
+ (rating as PlayerRating).physique | ||
) / 3; | ||
}).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.possessionRating()); | ||
} | ||
attackRating(): number { | ||
const players = this.getFieldPlayers(); | ||
return players.map(player => player.attackRating()).reduce((a, b) => a + b) / players.length; | ||
return this.averageRating(player => player.attackRating()); | ||
} | ||
simulateMove(ballPosition: FieldArea, gameInfo: GameInfo): Action { | ||
const random = Math.floor(Math.random() * 11); | ||
if (ballPosition === FieldArea.Offense) { | ||
if (random > 5) { | ||
return Action.GoalAttempt; | ||
} | ||
const options = [[Action.GoalAttempt, 50], [Action.Stay, 35], [Action.Retreat, 15]]; | ||
if (random > 3) { | ||
return Action.Stay; | ||
} | ||
return Action.Retreat; | ||
return getRandomElement(options); | ||
} | ||
if (random > 6) { | ||
return Action.Advance; | ||
} | ||
const options = [[Action.Advance, 50], [Action.Stay, 35], [Action.Retreat, 15]]; | ||
if (random > 3) { | ||
return Action.Stay; | ||
} | ||
return Action.Retreat; | ||
return getRandomElement(options); | ||
} | ||
getProbablePlayer(fieldPosition: FieldArea, weights: Weights, exclude: Player[] = []): Player { | ||
getProbablePlayer(fieldPosition: FieldArea, attacker: boolean, exclude: Player[] = []): Player { | ||
const weights = createWeights(attacker); | ||
const players: { weight: number, player: Player }[] = []; | ||
@@ -138,44 +123,8 @@ | ||
attacker(fieldPosition: FieldArea, exclude: Player[] = []): Player { | ||
const weights: Weights = { | ||
[FieldArea.Defence]: { | ||
defenders: 0.6, | ||
midfielders: 0.3, | ||
attackers: 0.1, | ||
}, | ||
[FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea.Offense]: { | ||
defenders: 0.1, | ||
midfielders: 0.3, | ||
attackers: 0.6, | ||
} | ||
}; | ||
return this.getProbablePlayer(fieldPosition, weights, exclude); | ||
return this.getProbablePlayer(fieldPosition, true, exclude); | ||
} | ||
defender(fieldPosition: FieldArea, exclude: Player[] = []): Player { | ||
const weights = { | ||
[FieldArea.Defence]: { | ||
defenders: 0.1, | ||
midfielders: 0.3, | ||
attackers: 0.6, | ||
}, | ||
[FieldArea.Midfield]: { | ||
defenders: 0.25, | ||
midfielders: 0.5, | ||
attackers: 0.25, | ||
}, | ||
[FieldArea.Offense]: { | ||
defenders: 0.6, | ||
midfielders: 0.3, | ||
attackers: 0.1, | ||
} | ||
}; | ||
return this.getProbablePlayer(fieldPosition, weights, exclude); | ||
return this.getProbablePlayer(fieldPosition, false, exclude); | ||
} | ||
} |
76950
50
2
2179
+ Addedevents@^3.3.0
+ Addedevents@3.3.0(transitive)