@bleckert/football-simulator
Advanced tools
Comparing version 0.0.4 to 0.0.5
@@ -1,2 +0,2 @@ | ||
import { GameEvent } from './types/GameEvent'; | ||
import type { GameEvent } from './types/GameEvent'; | ||
export default class Commentator { | ||
@@ -3,0 +3,0 @@ name: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./enums/Event"); | ||
const importantEvents = [ | ||
var Event_1 = require("./enums/Event"); | ||
var importantEvents = [ | ||
Event_1.Event.GameStart, | ||
@@ -15,7 +15,8 @@ Event_1.Event.Kickoff, | ||
]; | ||
class Commentator { | ||
constructor(name = 'Mr. Commentator') { | ||
var Commentator = /** @class */ (function () { | ||
function Commentator(name) { | ||
if (name === void 0) { name = 'Mr. Commentator'; } | ||
this.name = name; | ||
} | ||
routeComment(event) { | ||
Commentator.prototype.routeComment = function (event) { | ||
switch (event.event) { | ||
@@ -43,5 +44,5 @@ case Event_1.Event.GameStart: | ||
} | ||
} | ||
comment(event) { | ||
const importantEvent = event.event in importantEvents; | ||
}; | ||
Commentator.prototype.comment = function (event) { | ||
var importantEvent = event.event in importantEvents; | ||
if (!importantEvent && Math.random() > 0.5) { | ||
@@ -51,43 +52,44 @@ return null; | ||
return this.routeComment(event); | ||
} | ||
gameStarted(event) { | ||
return `The game between ${event.homeTeam.name} and ${event.awayTeam.name} has started.`; | ||
} | ||
kickoff(event) { | ||
return `${event.data.name} with the kickoff.`; | ||
} | ||
halfTime(event) { | ||
return `It's half time! The score is ${event.gameInfo.homeGoals} - ${event.gameInfo.awayGoals}`; | ||
} | ||
advance(event) { | ||
return `${event.attackingTeam.name} advances with the ball.`; | ||
} | ||
defence(event) { | ||
return `${event.defendingTeam.name} tries to advance but good defence by ${event.attackingTeam.name} that steals the ball.`; | ||
} | ||
rebound(comment, event) { | ||
}; | ||
Commentator.prototype.gameStarted = function (event) { | ||
return "The game between " + event.homeTeam.name + " and " + event.awayTeam.name + " has started."; | ||
}; | ||
Commentator.prototype.kickoff = function (event) { | ||
return event.data.name + " with the kickoff."; | ||
}; | ||
Commentator.prototype.halfTime = function (event) { | ||
return "It's half time! The score is " + event.gameInfo.homeGoals + " - " + event.gameInfo.awayGoals; | ||
}; | ||
Commentator.prototype.advance = function (event) { | ||
return event.attackingTeam.name + " advances with the ball."; | ||
}; | ||
Commentator.prototype.defence = function (event) { | ||
return event.defendingTeam.name + " tries to advance but good defence by " + event.attackingTeam.name + " that steals the ball."; | ||
}; | ||
Commentator.prototype.rebound = function (comment, event) { | ||
if (event.data === event.attackingTeam) { | ||
return `${event.attackingTeam.name} gets the ball back.`; | ||
return event.attackingTeam.name + " gets the ball back."; | ||
} | ||
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) { | ||
return this.rebound(`${event.attackingPrimaryPlayer.info.name} tries to score but the ball was blocked by the defence.`, event); | ||
} | ||
goal(event) { | ||
return `${event.attackingPrimaryPlayer.info.name} shoots and he scores! ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
} | ||
gameEnded(event) { | ||
return [comment, event.attackingTeam.name + " can take control over the ball."].join(' '); | ||
}; | ||
Commentator.prototype.save = function (event) { | ||
return this.rebound(event.attackingPrimaryPlayer.info.name + " tries to score but the goalkeeper saves the ball.", event); | ||
}; | ||
Commentator.prototype.block = function (event) { | ||
return this.rebound(event.attackingPrimaryPlayer.info.name + " tries to score but the ball was blocked by the defence.", event); | ||
}; | ||
Commentator.prototype.goal = function (event) { | ||
return event.attackingPrimaryPlayer.info.name + " shoots and he scores! " + event.gameInfo.homeGoals + "-" + event.gameInfo.awayGoals; | ||
}; | ||
Commentator.prototype.gameEnded = function (event) { | ||
if (event.gameInfo.homeGoals > event.gameInfo.awayGoals) { | ||
return `The game has ended! ${event.homeTeam.name} wins ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
return "The game has ended! " + event.homeTeam.name + " wins " + 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}`; | ||
return "The game has ended! " + event.awayTeam.name + " takes 3 points on the road! " + event.gameInfo.homeGoals + "-" + event.gameInfo.awayGoals; | ||
} | ||
return `The game ends with a draw! Final score ${event.gameInfo.homeGoals}-${event.gameInfo.awayGoals}`; | ||
} | ||
} | ||
return "The game ends with a draw! Final score " + event.gameInfo.homeGoals + "-" + event.gameInfo.awayGoals; | ||
}; | ||
return Commentator; | ||
}()); | ||
exports.default = Commentator; |
@@ -1,5 +0,5 @@ | ||
import { GameEvent } from './types/GameEvent'; | ||
import type { GameEvent } from './types/GameEvent'; | ||
import { Event } from './enums/Event'; | ||
import Team from './Team'; | ||
import Player from './Player'; | ||
import type Team from './Team'; | ||
import type Player from './Player'; | ||
@@ -6,0 +6,0 @@ const importantEvents = [ |
import { Event } from './enums/Event'; | ||
import Team from './Team'; | ||
import { GameEvent } from './types/GameEvent'; | ||
import { GameInfo } from './types/GameInfo'; | ||
import type Team from './Team'; | ||
import type { GameEvent } from './types/GameEvent'; | ||
import type { GameInfo } from './types/GameInfo'; | ||
import { FieldArea } from "./enums/FieldArea"; | ||
import { Action } from './enums/Action'; | ||
import Player from "./Player"; | ||
import type Player from "./Player"; | ||
import { GoalType } from "./enums/GoalType"; | ||
@@ -84,3 +84,3 @@ import { AssistType } from "./enums/AssistType"; | ||
handleEvent(event: GameEvent): void; | ||
eventLoop(): IterableIterator<GameEvent>; | ||
eventLoop(): Generator<GameEvent, void, unknown>; | ||
gameEvent(event: Event, data?: any, attackingPrimaryPlayer?: Player | null, attackingSecondaryPlayer?: Player | null, defendingPrimaryPlayer?: Player | null, defendingSecondaryPlayer?: Player | null, goalType?: GoalType | null, assistType?: AssistType | null): GameEvent; | ||
@@ -87,0 +87,0 @@ random(team: Team): number; |
246
Engine.js
"use strict"; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./enums/Event"); | ||
const FieldArea_1 = require("./enums/FieldArea"); | ||
const Action_1 = require("./enums/Action"); | ||
const GoalType_1 = require("./enums/GoalType"); | ||
const AssistType_1 = require("./enums/AssistType"); | ||
class Engine { | ||
constructor(homeTeam, awayTeam) { | ||
var Event_1 = require("./enums/Event"); | ||
var FieldArea_1 = require("./enums/FieldArea"); | ||
var Action_1 = require("./enums/Action"); | ||
var GoalType_1 = require("./enums/GoalType"); | ||
var AssistType_1 = require("./enums/AssistType"); | ||
var Engine = /** @class */ (function () { | ||
function Engine(homeTeam, awayTeam) { | ||
var _this = this; | ||
/** | ||
@@ -60,13 +88,13 @@ * Has the game started? | ||
this.gameEvents = []; | ||
this.simulate = () => { | ||
if (!this.gameStarted) { | ||
this.start(); | ||
this.simulate = function () { | ||
if (!_this.gameStarted) { | ||
_this.start(); | ||
} | ||
const event = this.gameLoop.next(); | ||
var event = _this.gameLoop.next(); | ||
if (event.done) { | ||
return; | ||
} | ||
this.gameEvents.push(event.value); | ||
this.handleEvent(event.value); | ||
this.simulate(); | ||
_this.gameEvents.push(event.value); | ||
_this.handleEvent(event.value); | ||
_this.simulate(); | ||
}; | ||
@@ -82,4 +110,4 @@ this.homeTeam = homeTeam; | ||
} | ||
start() { | ||
const coinflip = Math.floor(Math.random() * 2) == 0; | ||
Engine.prototype.start = function () { | ||
var coinflip = Math.floor(Math.random() * 2) == 0; | ||
this.ballPossession = (coinflip) ? this.homeTeam : this.awayTeam; | ||
@@ -90,18 +118,19 @@ this.startedWithBall = this.ballPossession; | ||
this.gameStarted = true; | ||
} | ||
teamWithoutBall() { | ||
}; | ||
Engine.prototype.teamWithoutBall = function () { | ||
return (this.ballPossession === this.homeTeam) ? this.awayTeam : this.homeTeam; | ||
} | ||
rebound() { | ||
}; | ||
Engine.prototype.rebound = function () { | ||
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, | ||
}; | ||
}; | ||
Engine.prototype.reverseSide = function (current) { | ||
var _a; | ||
var map = (_a = {}, | ||
_a[FieldArea_1.FieldArea.Offense] = FieldArea_1.FieldArea.Defence, | ||
_a[FieldArea_1.FieldArea.Midfield] = FieldArea_1.FieldArea.Midfield, | ||
_a[FieldArea_1.FieldArea.Defence] = FieldArea_1.FieldArea.Offense, | ||
_a); | ||
return map[current]; | ||
} | ||
handleEvent(event) { | ||
}; | ||
Engine.prototype.handleEvent = function (event) { | ||
switch (event.event) { | ||
@@ -130,16 +159,37 @@ case Event_1.Event.Goal: | ||
} | ||
} | ||
*eventLoop() { | ||
for (this.gameInfo.matchMinute; this.gameInfo.matchMinute <= this.gameTime; this.gameInfo.matchMinute += 1 / this.eventsPerMinute) { | ||
yield this.simulateEvent(); | ||
} | ||
} | ||
gameEvent(event, data = null, attackingPrimaryPlayer = null, attackingSecondaryPlayer = null, defendingPrimaryPlayer = null, defendingSecondaryPlayer = null, goalType = null, assistType = null) { | ||
}; | ||
Engine.prototype.eventLoop = function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
this.gameInfo.matchMinute; | ||
_a.label = 1; | ||
case 1: | ||
if (!(this.gameInfo.matchMinute <= this.gameTime)) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, this.simulateEvent()]; | ||
case 2: | ||
_a.sent(); | ||
_a.label = 3; | ||
case 3: | ||
this.gameInfo.matchMinute += 1 / this.eventsPerMinute; | ||
return [3 /*break*/, 1]; | ||
case 4: return [2 /*return*/]; | ||
} | ||
}); | ||
}; | ||
Engine.prototype.gameEvent = function (event, data, attackingPrimaryPlayer, attackingSecondaryPlayer, defendingPrimaryPlayer, defendingSecondaryPlayer, goalType, assistType) { | ||
if (data === void 0) { data = null; } | ||
if (attackingPrimaryPlayer === void 0) { attackingPrimaryPlayer = null; } | ||
if (attackingSecondaryPlayer === void 0) { attackingSecondaryPlayer = null; } | ||
if (defendingPrimaryPlayer === void 0) { defendingPrimaryPlayer = null; } | ||
if (defendingSecondaryPlayer === void 0) { defendingSecondaryPlayer = null; } | ||
if (goalType === void 0) { goalType = null; } | ||
if (assistType === void 0) { assistType = null; } | ||
return { | ||
event, | ||
data, | ||
attackingPrimaryPlayer, | ||
attackingSecondaryPlayer, | ||
defendingPrimaryPlayer, | ||
defendingSecondaryPlayer, | ||
event: event, | ||
data: data, | ||
attackingPrimaryPlayer: attackingPrimaryPlayer, | ||
attackingSecondaryPlayer: attackingSecondaryPlayer, | ||
defendingPrimaryPlayer: defendingPrimaryPlayer, | ||
defendingSecondaryPlayer: defendingSecondaryPlayer, | ||
gameInfo: Object.assign({}, this.gameInfo), | ||
@@ -151,10 +201,10 @@ homeTeam: this.homeTeam, | ||
fieldPosition: this.ballPosition, | ||
goalType, | ||
assistType, | ||
goalType: goalType, | ||
assistType: assistType, | ||
}; | ||
} | ||
random(team) { | ||
const min = -this.randomEffect; | ||
const max = this.randomEffect; | ||
let random = Math.floor(Math.random() * (max - min + 1) + min); | ||
}; | ||
Engine.prototype.random = function (team) { | ||
var min = -this.randomEffect; | ||
var max = this.randomEffect; | ||
var random = Math.floor(Math.random() * (max - min + 1) + min); | ||
if (team === this.homeTeam) { | ||
@@ -164,16 +214,16 @@ random += this.homeTeamAdvantage; | ||
return random; | ||
} | ||
simulateGoalAttempt(attackingTeam, defendingTeam, attacker) { | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
}; | ||
Engine.prototype.simulateGoalAttempt = function (attackingTeam, defendingTeam, attacker) { | ||
var defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
var 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); | ||
var goalkeeper = defendingTeam.goalkeeperRating() + this.random(defendingTeam); | ||
var 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); | ||
}; | ||
Engine.prototype.simulatePossession = function (attackingTeam, defendingTeam, action) { | ||
var defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
var possession = attackingTeam.possessionRating() + this.random(attackingTeam); | ||
if (defence > possession) { | ||
@@ -183,11 +233,11 @@ return Event_1.Event.Defence; | ||
return (action === Action_1.Action.Retreat) ? Event_1.Event.Retreat : Event_1.Event.Possession; | ||
} | ||
simulateAction(action, attacker) { | ||
}; | ||
Engine.prototype.simulateAction = function (action, attacker) { | ||
if (!this.ballPossession) { | ||
return Event_1.Event.EventLess; | ||
} | ||
const attackingTeam = this.ballPossession; | ||
const defendingTeam = this.teamWithoutBall(); | ||
const defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
const attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
var attackingTeam = this.ballPossession; | ||
var defendingTeam = this.teamWithoutBall(); | ||
var defence = defendingTeam.defenceRating() + this.random(defendingTeam); | ||
var attack = attackingTeam.attackRating() + this.random(attackingTeam); | ||
if (action === Action_1.Action.Advance) { | ||
@@ -200,9 +250,9 @@ return (attack > defence) ? Event_1.Event.Advance : 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; | ||
}; | ||
Engine.prototype.simulateAssistType = function (secondaryPlayer) { | ||
var random = Math.random(); | ||
var secondaryPlayerAttributes = secondaryPlayer.attributes; | ||
var secondaryPlayerRating = secondaryPlayer.rating(); | ||
var shooting = secondaryPlayerRating.shooting; | ||
var passing = secondaryPlayerRating.passing; | ||
if (shooting > passing && random > 0.5) { | ||
@@ -215,11 +265,11 @@ return (random > 0.5) ? AssistType_1.AssistType.Deflection : AssistType_1.AssistType.Rebound; | ||
return AssistType_1.AssistType.Cross; | ||
} | ||
simulateGoalType(primaryPlayer, secondaryPlayer) { | ||
const assist = Math.random() > 0.5; | ||
}; | ||
Engine.prototype.simulateGoalType = function (primaryPlayer, secondaryPlayer) { | ||
var assist = Math.random() > 0.5; | ||
if (!assist) { | ||
return [GoalType_1.GoalType.Shot, null]; | ||
} | ||
const primaryPlayerAttributes = primaryPlayer.attributes; | ||
const random = Math.random(); | ||
const assistType = this.simulateAssistType(secondaryPlayer); | ||
var primaryPlayerAttributes = primaryPlayer.attributes; | ||
var random = Math.random(); | ||
var assistType = this.simulateAssistType(secondaryPlayer); | ||
return [ | ||
@@ -229,13 +279,13 @@ (primaryPlayerAttributes.heading > primaryPlayerAttributes.finishing && random > 0.5) ? GoalType_1.GoalType.Header : [GoalType_1.GoalType.Volley, GoalType_1.GoalType.Shot][Math.floor(Math.random() * 2)], | ||
]; | ||
} | ||
halfTime() { | ||
}; | ||
Engine.prototype.halfTime = function () { | ||
this.ballPossession = this.startedWithBall === this.homeTeam ? this.awayTeam : this.homeTeam; | ||
this.ballPosition = FieldArea_1.FieldArea.Midfield; | ||
return this.gameEvent(Event_1.Event.HalfTime); | ||
} | ||
gameEnd() { | ||
}; | ||
Engine.prototype.gameEnd = function () { | ||
this.gameEnded = true; | ||
return this.gameEvent(Event_1.Event.GameEnd); | ||
} | ||
goal(attackingPrimaryPlayer, attackingSecondaryPlayer) { | ||
}; | ||
Engine.prototype.goal = function (attackingPrimaryPlayer, attackingSecondaryPlayer) { | ||
if (this.ballPossession === this.homeTeam) { | ||
@@ -248,4 +298,5 @@ this.gameInfo.homeGoals += 1; | ||
return this.simulateGoalType(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
} | ||
simulateEvent() { | ||
}; | ||
Engine.prototype.simulateEvent = function () { | ||
var _a; | ||
if (this.gameInfo.matchMinute == this.gameTime / 2) | ||
@@ -257,16 +308,17 @@ return this.halfTime(); | ||
return this.gameEvent(Event_1.Event.EventLess); | ||
const attackingPrimaryPlayer = this.ballPossession.attacker(this.ballPosition); | ||
const attackingSecondaryPlayer = this.ballPossession.attacker(this.ballPosition, [attackingPrimaryPlayer]); | ||
const defendingTeam = this.teamWithoutBall(); | ||
const defendingPrimaryPlayer = defendingTeam.defender(this.ballPosition); | ||
const action = this.ballPossession.simulateMove(this.ballPosition, this.gameInfo); | ||
let goalType = null; | ||
let assist = null; | ||
const event = this.simulateAction(action, attackingPrimaryPlayer); | ||
var attackingPrimaryPlayer = this.ballPossession.attacker(this.ballPosition); | ||
var attackingSecondaryPlayer = this.ballPossession.attacker(this.ballPosition, [attackingPrimaryPlayer]); | ||
var defendingTeam = this.teamWithoutBall(); | ||
var defendingPrimaryPlayer = defendingTeam.defender(this.ballPosition); | ||
var action = this.ballPossession.simulateMove(this.ballPosition, this.gameInfo); | ||
var goalType = null; | ||
var assist = null; | ||
var event = this.simulateAction(action, attackingPrimaryPlayer); | ||
if (event === Event_1.Event.Goal) { | ||
[goalType, assist] = this.goal(attackingPrimaryPlayer, attackingSecondaryPlayer); | ||
_a = this.goal(attackingPrimaryPlayer, attackingSecondaryPlayer), goalType = _a[0], assist = _a[1]; | ||
} | ||
return this.gameEvent(event, null, attackingPrimaryPlayer, attackingSecondaryPlayer, defendingPrimaryPlayer, defendingTeam.defender(this.ballPosition, [defendingPrimaryPlayer]), goalType, assist); | ||
} | ||
} | ||
}; | ||
return Engine; | ||
}()); | ||
exports.default = Engine; |
import {Event} from './enums/Event'; | ||
import Team from './Team'; | ||
import {GameEvent} from './types/GameEvent'; | ||
import {GameInfo} from './types/GameInfo'; | ||
import type Team from './Team'; | ||
import type {GameEvent} from './types/GameEvent'; | ||
import type {GameInfo} from './types/GameInfo'; | ||
import {FieldArea} from "./enums/FieldArea"; | ||
import {Action} from './enums/Action'; | ||
import Player, {PlayerRating} from "./Player"; | ||
import type Player from "./Player"; | ||
import type {PlayerRating} from "./Player"; | ||
import {GoalType} from "./enums/GoalType"; | ||
@@ -9,0 +10,0 @@ import {AssistType} from "./enums/AssistType"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Action = void 0; | ||
var Action; | ||
@@ -4,0 +5,0 @@ (function (Action) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.AssistType = void 0; | ||
var AssistType; | ||
@@ -4,0 +5,0 @@ (function (AssistType) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DefenceType = void 0; | ||
var DefenceType; | ||
@@ -4,0 +5,0 @@ (function (DefenceType) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Event = void 0; | ||
var Event; | ||
@@ -4,0 +5,0 @@ (function (Event) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FieldArea = void 0; | ||
var FieldArea; | ||
@@ -4,0 +5,0 @@ (function (FieldArea) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.GoalType = void 0; | ||
var GoalType; | ||
@@ -4,0 +5,0 @@ (function (GoalType) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.attackPositions = exports.midfieldPositions = exports.defencePositions = exports.Position = void 0; | ||
var Position; | ||
@@ -4,0 +5,0 @@ (function (Position) { |
/// <reference types="node" /> | ||
import Team from './Team'; | ||
import { GameEvent } from './types/GameEvent'; | ||
import Commentator from './Commentator'; | ||
import type Team from './Team'; | ||
import type { GameEvent } from './types/GameEvent'; | ||
import type Commentator from './Commentator'; | ||
import { EventEmitter } from 'events'; | ||
@@ -6,0 +6,0 @@ import Engine from './Engine'; |
84
Game.js
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
if (typeof b !== "function" && b !== null) | ||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const events_1 = require("events"); | ||
const Engine_1 = __importDefault(require("./Engine")); | ||
const Reporter_1 = __importDefault(require("./Reporter")); | ||
class Game extends events_1.EventEmitter { | ||
constructor(homeTeam, awayTeam, commentator) { | ||
super(); | ||
var events_1 = require("events"); | ||
var Engine_1 = require("./Engine"); | ||
var Reporter_1 = require("./Reporter"); | ||
var Game = /** @class */ (function (_super) { | ||
__extends(Game, _super); | ||
function Game(homeTeam, awayTeam, commentator) { | ||
var _this = _super.call(this) || this; | ||
/** | ||
* Milliseconds between each simulation | ||
*/ | ||
this.gameSpeed = 500; | ||
_this.gameSpeed = 500; | ||
/** | ||
* Events copy | ||
*/ | ||
this.events = []; | ||
this.loop = () => { | ||
const event = this.events.shift(); | ||
_this.events = []; | ||
_this.loop = function () { | ||
var event = _this.events.shift(); | ||
if (!event) { | ||
this.report(); | ||
_this.report(); | ||
return; | ||
} | ||
this.emit('comment', { | ||
text: this.commentator.comment(event), | ||
_this.emit('comment', { | ||
text: _this.commentator.comment(event), | ||
gameInfo: event.gameInfo, | ||
}); | ||
this.emit('event', event); | ||
setTimeout(this.loop, this.gameSpeed); | ||
_this.emit('event', event); | ||
setTimeout(_this.loop, _this.gameSpeed); | ||
}; | ||
this.homeTeam = homeTeam; | ||
this.awayTeam = awayTeam; | ||
this.commentator = commentator; | ||
this.engine = new Engine_1.default(this.homeTeam, this.awayTeam); | ||
_this.homeTeam = homeTeam; | ||
_this.awayTeam = awayTeam; | ||
_this.commentator = commentator; | ||
_this.engine = new Engine_1.default(_this.homeTeam, _this.awayTeam); | ||
return _this; | ||
} | ||
start() { | ||
Game.prototype.start = function () { | ||
var _this = this; | ||
this.engine.start(); | ||
this.events = this.engine.gameEvents.slice(); | ||
this.events.forEach(gameEvent => { | ||
this.emit('comment', { | ||
text: this.commentator.comment(gameEvent), | ||
gameInfo: this.engine.gameInfo, | ||
this.events.forEach(function (gameEvent) { | ||
_this.emit('comment', { | ||
text: _this.commentator.comment(gameEvent), | ||
gameInfo: _this.engine.gameInfo, | ||
}); | ||
@@ -50,13 +65,14 @@ }); | ||
this.simulate(); | ||
} | ||
simulate() { | ||
}; | ||
Game.prototype.simulate = function () { | ||
this.engine.simulate(); | ||
this.events = this.engine.gameEvents.slice(); | ||
this.loop(); | ||
} | ||
report() { | ||
const reporter = new Reporter_1.default(this.engine.gameEvents); | ||
}; | ||
Game.prototype.report = function () { | ||
var reporter = new Reporter_1.default(this.engine.gameEvents); | ||
this.emit('report', reporter.getReport()); | ||
} | ||
} | ||
}; | ||
return Game; | ||
}(events_1.EventEmitter)); | ||
exports.default = Game; |
@@ -1,4 +0,4 @@ | ||
import Team from './Team'; | ||
import { GameEvent } from './types/GameEvent'; | ||
import Commentator from './Commentator'; | ||
import type Team from './Team'; | ||
import type { GameEvent } from './types/GameEvent'; | ||
import type Commentator from './Commentator'; | ||
import { EventEmitter } from 'events'; | ||
@@ -5,0 +5,0 @@ import Engine from './Engine'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function getRandomElement(options) { | ||
const weighted = options.map(([action, weight]) => Array(weight).fill(action)).reduce((c, v) => c.concat(v), []); | ||
var weighted = options.map(function (_a) { | ||
var action = _a[0], weight = _a[1]; | ||
return Array(weight).fill(action); | ||
}).reduce(function (c, v) { return c.concat(v); }, []); | ||
return weighted[Math.floor((Math.random() * weighted.length))]; | ||
} | ||
exports.default = getRandomElement; |
{ | ||
"name": "@bleckert/football-simulator", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"scripts": { | ||
@@ -13,3 +13,3 @@ "start": "ts-node test/index.ts", | ||
"ts-node": "^8.3.0", | ||
"typescript": "^3.5.3" | ||
"typescript": "^4.3.2" | ||
}, | ||
@@ -16,0 +16,0 @@ "dependencies": { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Position_1 = require("./enums/Position"); | ||
class Player { | ||
constructor(info, biometrics, attributes, position) { | ||
var Position_1 = require("./enums/Position"); | ||
var Player = /** @class */ (function () { | ||
function Player(info, biometrics, attributes, position) { | ||
this.info = info; | ||
@@ -11,7 +11,7 @@ this.biometrics = biometrics; | ||
} | ||
ratingAverage() { | ||
const rating = this.rating(); | ||
return Object.values(rating).reduce((a, b) => a + b) / Object.values(rating).length; | ||
} | ||
rating() { | ||
Player.prototype.ratingAverage = function () { | ||
var rating = this.rating(); | ||
return Object.values(rating).reduce(function (a, b) { return a + b; }) / Object.values(rating).length; | ||
}; | ||
Player.prototype.rating = function () { | ||
if (this.position === Position_1.Position.GK) { | ||
@@ -35,8 +35,8 @@ return { | ||
}; | ||
} | ||
averageRating(ratings) { | ||
return ratings.reduce((acc, curr) => acc + curr) / ratings.length; | ||
} | ||
defenceRating() { | ||
const rating = this.rating(); | ||
}; | ||
Player.prototype.averageRating = function (ratings) { | ||
return ratings.reduce(function (acc, curr) { return acc + curr; }) / ratings.length; | ||
}; | ||
Player.prototype.defenceRating = function () { | ||
var rating = this.rating(); | ||
return this.averageRating([ | ||
@@ -47,5 +47,5 @@ rating.defending, | ||
]); | ||
} | ||
possessionRating() { | ||
const rating = this.rating(); | ||
}; | ||
Player.prototype.possessionRating = function () { | ||
var rating = this.rating(); | ||
return this.averageRating([ | ||
@@ -56,5 +56,5 @@ rating.dribbling, | ||
]); | ||
} | ||
attackRating() { | ||
const rating = this.rating(); | ||
}; | ||
Player.prototype.attackRating = function () { | ||
var rating = this.rating(); | ||
return this.averageRating([ | ||
@@ -67,7 +67,12 @@ rating.dribbling, | ||
]); | ||
} | ||
attributesAverage(...attributes) { | ||
return (attributes.reduce((a, b) => a + b) / attributes.length) / 20 * 100; | ||
} | ||
} | ||
}; | ||
Player.prototype.attributesAverage = function () { | ||
var attributes = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
attributes[_i] = arguments[_i]; | ||
} | ||
return (attributes.reduce(function (a, b) { return a + b; }) / attributes.length) / 20 * 100; | ||
}; | ||
return Player; | ||
}()); | ||
exports.default = Player; |
@@ -1,4 +0,4 @@ | ||
import { GameEvent } from './types/GameEvent'; | ||
import Player from "./Player"; | ||
import Team from "./Team"; | ||
import type { GameEvent } from './types/GameEvent'; | ||
import type Player from "./Player"; | ||
import type Team from "./Team"; | ||
export default class Reporter { | ||
@@ -5,0 +5,0 @@ gameEvents: GameEvent[]; |
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Event_1 = require("./enums/Event"); | ||
class Reporter { | ||
constructor(gameEvents) { | ||
var Event_1 = require("./enums/Event"); | ||
var Reporter = /** @class */ (function () { | ||
function Reporter(gameEvents) { | ||
var _this = this; | ||
this.home = { | ||
@@ -19,14 +31,14 @@ goals: 0, | ||
this.scoreSheet = []; | ||
this.registerEvent = (gameEvent) => { | ||
const side = (gameEvent.attackingTeam && gameEvent.attackingTeam.home) ? 'home' : 'away'; | ||
this[side].possession += 1; | ||
this.registerEvent = function (gameEvent) { | ||
var 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; | ||
_this[side].shots += 1; | ||
} | ||
if ([Event_1.Event.Save, Event_1.Event.Goal].includes(gameEvent.event)) { | ||
this[side].shotsOnGoal += 1; | ||
_this[side].shotsOnGoal += 1; | ||
} | ||
if (gameEvent.event === Event_1.Event.Goal) { | ||
this[side].goals += 1; | ||
this.scoreSheet.push({ | ||
_this[side].goals += 1; | ||
_this.scoreSheet.push({ | ||
matchMinute: gameEvent.gameInfo.matchMinute, | ||
@@ -41,12 +53,13 @@ goalScorer: gameEvent.attackingPrimaryPlayer, | ||
} | ||
getReport() { | ||
Reporter.prototype.getReport = function () { | ||
this.gameEvents.forEach(this.registerEvent); | ||
const totalPossession = this.home.possession + this.away.possession; | ||
var totalPossession = this.home.possession + this.away.possession; | ||
return { | ||
home: { ...this.home, possession: this.home.possession / totalPossession }, | ||
away: { ...this.away, possession: this.away.possession / totalPossession }, | ||
home: __assign(__assign({}, this.home), { possession: this.home.possession / totalPossession }), | ||
away: __assign(__assign({}, this.away), { possession: this.away.possession / totalPossession }), | ||
scoreSheet: this.scoreSheet, | ||
}; | ||
} | ||
} | ||
}; | ||
return Reporter; | ||
}()); | ||
exports.default = Reporter; |
@@ -1,5 +0,5 @@ | ||
import {GameEvent} from './types/GameEvent'; | ||
import type {GameEvent} from './types/GameEvent'; | ||
import {Event} from './enums/Event'; | ||
import Player from "./Player"; | ||
import Team from "./Team"; | ||
import type Player from "./Player"; | ||
import type Team from "./Team"; | ||
@@ -6,0 +6,0 @@ export default class Reporter { |
@@ -1,4 +0,4 @@ | ||
import Player from './Player'; | ||
import type Player from './Player'; | ||
import { FieldArea } from "./enums/FieldArea"; | ||
import { GameInfo } from "./types/GameInfo"; | ||
import type { GameInfo } from "./types/GameInfo"; | ||
import { Action } from "./enums/Action"; | ||
@@ -5,0 +5,0 @@ export interface TeamInterface { |
125
Team.js
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Position_1 = require("./enums/Position"); | ||
const FieldArea_1 = require("./enums/FieldArea"); | ||
const Action_1 = require("./enums/Action"); | ||
const getRandomElement_1 = __importDefault(require("./lib/getRandomElement")); | ||
function createWeights(attacker = false) { | ||
return { | ||
[FieldArea_1.FieldArea.Defence]: { | ||
var Position_1 = require("./enums/Position"); | ||
var FieldArea_1 = require("./enums/FieldArea"); | ||
var Action_1 = require("./enums/Action"); | ||
var getRandomElement_1 = require("./lib/getRandomElement"); | ||
function createWeights(attacker) { | ||
var _a; | ||
if (attacker === void 0) { attacker = false; } | ||
return _a = {}, | ||
_a[FieldArea_1.FieldArea.Defence] = { | ||
defenders: (attacker) ? 0.6 : 0.1, | ||
@@ -17,3 +16,3 @@ midfielders: 0.3, | ||
}, | ||
[FieldArea_1.FieldArea.Midfield]: { | ||
_a[FieldArea_1.FieldArea.Midfield] = { | ||
defenders: 0.25, | ||
@@ -23,11 +22,11 @@ midfielders: 0.5, | ||
}, | ||
[FieldArea_1.FieldArea.Offense]: { | ||
_a[FieldArea_1.FieldArea.Offense] = { | ||
defenders: (attacker) ? 0.1 : 0.6, | ||
midfielders: 0.3, | ||
attackers: (attacker) ? 0.6 : 0.1, | ||
} | ||
}; | ||
}, | ||
_a; | ||
} | ||
class Team { | ||
constructor(home, name, players) { | ||
var Team = /** @class */ (function () { | ||
function Team(home, name, players) { | ||
this.home = home; | ||
@@ -37,3 +36,3 @@ this.name = name; | ||
} | ||
rating() { | ||
Team.prototype.rating = function () { | ||
return { | ||
@@ -44,58 +43,64 @@ goalkeeping: this.goalkeeperRating(), | ||
}; | ||
} | ||
getGoalkeepers() { | ||
return this.players.filter(player => player.position === Position_1.Position.GK); | ||
} | ||
getFieldPlayers(exclude = []) { | ||
return this.players.filter(player => player.position !== Position_1.Position.GK).filter(player => !exclude.length || exclude.indexOf(player) < 0); | ||
} | ||
averageRating(map, players = null) { | ||
const playersList = (players) ? players : this.getFieldPlayers(); | ||
return playersList.map(map).reduce((a, b) => a + b) / playersList.length; | ||
} | ||
goalkeeperRating() { | ||
return this.averageRating(player => player.ratingAverage(), this.getGoalkeepers()); | ||
} | ||
defenceRating() { | ||
return this.averageRating(player => player.defenceRating()); | ||
} | ||
possessionRating() { | ||
return this.averageRating(player => player.possessionRating()); | ||
} | ||
attackRating() { | ||
return this.averageRating(player => player.attackRating()); | ||
} | ||
simulateMove(ballPosition, gameInfo) { | ||
}; | ||
Team.prototype.getGoalkeepers = function () { | ||
return this.players.filter(function (player) { return player.position === Position_1.Position.GK; }); | ||
}; | ||
Team.prototype.getFieldPlayers = function (exclude) { | ||
if (exclude === void 0) { exclude = []; } | ||
return this.players.filter(function (player) { return player.position !== Position_1.Position.GK; }).filter(function (player) { return !exclude.length || exclude.indexOf(player) < 0; }); | ||
}; | ||
Team.prototype.averageRating = function (map, players) { | ||
if (players === void 0) { players = null; } | ||
var playersList = (players) ? players : this.getFieldPlayers(); | ||
return playersList.map(map).reduce(function (a, b) { return a + b; }) / playersList.length; | ||
}; | ||
Team.prototype.goalkeeperRating = function () { | ||
return this.averageRating(function (player) { return player.ratingAverage(); }, this.getGoalkeepers()); | ||
}; | ||
Team.prototype.defenceRating = function () { | ||
return this.averageRating(function (player) { return player.defenceRating(); }); | ||
}; | ||
Team.prototype.possessionRating = function () { | ||
return this.averageRating(function (player) { return player.possessionRating(); }); | ||
}; | ||
Team.prototype.attackRating = function () { | ||
return this.averageRating(function (player) { return player.attackRating(); }); | ||
}; | ||
Team.prototype.simulateMove = function (ballPosition, gameInfo) { | ||
if (ballPosition === FieldArea_1.FieldArea.Offense) { | ||
const options = [[Action_1.Action.GoalAttempt, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
return getRandomElement_1.default(options); | ||
var options_1 = [[Action_1.Action.GoalAttempt, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
return getRandomElement_1.default(options_1); | ||
} | ||
const options = [[Action_1.Action.Advance, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
var options = [[Action_1.Action.Advance, 50], [Action_1.Action.Stay, 35], [Action_1.Action.Retreat, 15]]; | ||
return getRandomElement_1.default(options); | ||
} | ||
getProbablePlayer(fieldPosition, attacker, exclude = []) { | ||
const weights = createWeights(attacker); | ||
const players = []; | ||
this.getFieldPlayers(exclude).forEach(player => { | ||
}; | ||
Team.prototype.getProbablePlayer = function (fieldPosition, attacker, exclude) { | ||
if (exclude === void 0) { exclude = []; } | ||
var weights = createWeights(attacker); | ||
var players = []; | ||
this.getFieldPlayers(exclude).forEach(function (player) { | ||
if (Position_1.defencePositions.indexOf(player.position) > -1) { | ||
players.push({ player, weight: weights[fieldPosition].defenders }); | ||
players.push({ player: player, weight: weights[fieldPosition].defenders }); | ||
} | ||
else if (Position_1.midfieldPositions.indexOf(player.position) > -1) { | ||
players.push({ player, weight: weights[fieldPosition].midfielders }); | ||
players.push({ player: player, weight: weights[fieldPosition].midfielders }); | ||
} | ||
else { | ||
players.push({ player, weight: weights[fieldPosition].attackers }); | ||
players.push({ player: player, weight: weights[fieldPosition].attackers }); | ||
} | ||
}); | ||
const random = Math.min(Math.random(), 0.6); | ||
const foundPlayers = players.filter(player => player.weight >= random).map(obj => obj.player); | ||
var random = Math.min(Math.random(), 0.6); | ||
var foundPlayers = players.filter(function (player) { return player.weight >= random; }).map(function (obj) { return obj.player; }); | ||
return foundPlayers[Math.floor(Math.random() * foundPlayers.length)]; | ||
} | ||
attacker(fieldPosition, exclude = []) { | ||
}; | ||
Team.prototype.attacker = function (fieldPosition, exclude) { | ||
if (exclude === void 0) { exclude = []; } | ||
return this.getProbablePlayer(fieldPosition, true, exclude); | ||
} | ||
defender(fieldPosition, exclude = []) { | ||
}; | ||
Team.prototype.defender = function (fieldPosition, exclude) { | ||
if (exclude === void 0) { exclude = []; } | ||
return this.getProbablePlayer(fieldPosition, false, exclude); | ||
} | ||
} | ||
}; | ||
return Team; | ||
}()); | ||
exports.default = Team; |
@@ -1,5 +0,5 @@ | ||
import Player, { PlayerRating } from './Player'; | ||
import type Player from './Player'; | ||
import { defencePositions, midfieldPositions, Position } from './enums/Position'; | ||
import { FieldArea } from "./enums/FieldArea"; | ||
import { GameInfo } from "./types/GameInfo"; | ||
import type { GameInfo } from "./types/GameInfo"; | ||
import { Action } from "./enums/Action"; | ||
@@ -6,0 +6,0 @@ import getRandomElement from "./lib/getRandomElement"; |
@@ -1,8 +0,8 @@ | ||
import { Event } from '../enums/Event'; | ||
import { GameInfo } from './GameInfo'; | ||
import Team from "../Team"; | ||
import { FieldArea } from "../enums/FieldArea"; | ||
import Player from "../Player"; | ||
import { GoalType } from "../enums/GoalType"; | ||
import { AssistType } from "../enums/AssistType"; | ||
import type { Event } from '../enums/Event'; | ||
import type { GameInfo } from './GameInfo'; | ||
import type Team from '../Team'; | ||
import type { FieldArea } from '../enums/FieldArea'; | ||
import type Player from '../Player'; | ||
import type { GoalType } from '../enums/GoalType'; | ||
import type { AssistType } from '../enums/AssistType'; | ||
export interface GameEvent { | ||
@@ -9,0 +9,0 @@ event: Event; |
@@ -1,8 +0,8 @@ | ||
import { Event } from '../enums/Event'; | ||
import { GameInfo } from './GameInfo'; | ||
import Team from "../Team"; | ||
import { FieldArea } from "../enums/FieldArea"; | ||
import Player from "../Player"; | ||
import { GoalType } from "../enums/GoalType"; | ||
import { AssistType } from "../enums/AssistType"; | ||
import type { Event } from '../enums/Event'; | ||
import type { GameInfo } from './GameInfo'; | ||
import type Team from '../Team'; | ||
import type { FieldArea } from '../enums/FieldArea'; | ||
import type Player from '../Player'; | ||
import type { GoalType } from '../enums/GoalType'; | ||
import type { AssistType } from '../enums/AssistType'; | ||
@@ -9,0 +9,0 @@ export interface GameEvent { |
84127
2283