import { PartyName } from './parties/PartyName' ; import { IParty } from './parties/IParty' ; import { MarsFirst } from './parties/MarsFirst' ; import { Scientists } from './parties/Scientists' ; import { Unity } from './parties/Unity' ; import { Kelvinists } from './parties/Kelvinists' ; import { Reds } from './parties/Reds' ; import { Greens } from './parties/Greens' ; import { Player , PlayerId } from '../Player' ; import { Game } from '../Game' ; import { GlobalEventDealer , getGlobalEventByName } from './globalEvents/GlobalEventDealer' ; import { IGlobalEvent } from './globalEvents/IGlobalEvent' ; import { ISerializable } from '../ISerializable' ; import { SerializedTurmoil } from './SerializedTurmoil' ; import { PLAYER_DELEGATES_COUNT } from '../constants' ; import { AgendaStyle , PoliticalAgendasData , PoliticalAgendas } from './PoliticalAgendas' ; import { CardName } from '../CardName' ; import { DeferredAction } from '../deferredActions/DeferredAction' ; export type NeutralPlayer = 'NEUTRAL' ; export interface IPartyFactory { partyName : PartyName ; Factory : new ( ) => IParty ; } export const ALL_PARTIES = [ { partyName : PartyName . MARS , Factory : MarsFirst } , { partyName : PartyName . SCIENTISTS , Factory : Scientists } , { partyName : PartyName . UNITY , Factory : Unity } , { partyName : PartyName . GREENS , Factory : Greens } , { partyName : PartyName . REDS , Factory : Reds } , { partyName : PartyName . KELVINISTS , Factory : Kelvinists } , ] ; const UNINITIALIZED_POLITICAL_AGENDAS_DATA = { agendas : new Map ( ) , agendaStyle : AgendaStyle . CHAIRMAN , } ; export class Turmoil implements ISerializable < SerializedTurmoil > { public chairman : undefined | PlayerId | NeutralPlayer = undefined ; public rulingParty : IParty ; public dominantParty : IParty ; public lobby : Set < PlayerId > = new Set < PlayerId > ( ) ; public delegateReserve : Array < PlayerId | NeutralPlayer > = [ ] ; public parties : Array < IParty > = ALL_PARTIES . map ( ( cf ) => new cf . Factory ( ) ) ; public playersInfluenceBonus : Map < string , number > = new Map < string , number > ( ) ; public readonly globalEventDealer : GlobalEventDealer ; public distantGlobalEvent : IGlobalEvent | undefined ; public comingGlobalEvent : IGlobalEvent | undefined ; public currentGlobalEvent : IGlobalEvent | undefined ; public politicalAgendasData : PoliticalAgendasData = UNINITIALIZED_POLITICAL_AGENDAS_DATA ; private constructor ( rulingPartyName , chairman , dominantPartyName , globalEventDealer ) { this . rulingParty = this . getPartyByName ( rulingPartyName ) ; this . chairman = chairman ; this . dominantParty = this . getPartyByName ( dominantPartyName ) ; this . globalEventDealer = globalEventDealer ; } public static newInstance ( game , agendaStyle = AgendaStyle . STANDARD ) { const dealer = GlobalEventDealer . newInstance ( game ) ; const turmoil = new Turmoil ( PartyName . GREENS , 'NEUTRAL' , PartyName . GREENS , dealer ) ; game . log ( 'A neutral delegate is the new chairman.' ) ; game . log ( 'Greens are in power in the first generation.' ) ; turmoil . parties = ALL_PARTIES . map ( ( cf ) => new cf . Factory ( ) ) ; game . getPlayers ( ) . forEach ( ( player ) => { turmoil . lobby . add ( player . id ) ; for ( let i = 0 ; i < PLAYER_DELEGATES_COUNT - 1 ; i ++ ) { turmoil . delegateReserve . push ( player . id ) ; } } ) ; for ( let i = 0 ; i < 13 ; i ++ ) { turmoil . delegateReserve . push ( 'NEUTRAL' ) ; } turmoil . politicalAgendasData = PoliticalAgendas . newInstance ( agendaStyle , turmoil . parties ) ; turmoil . onAgendaSelected ( game ) ; turmoil . initGlobalEvent ( game ) ; return turmoil ; } public static getTurmoil ( game ) { if ( game . turmoil === undefined ) { throw new Error ( `Assertion error: Turmoil not defined for ${ game . id }` ) ; } return game . turmoil ; } public static ifTurmoil ( game , cb ) { if ( game . gameOptions . turmoilExtension !== false ) { if ( game . turmoil === undefined ) { console . log ( `Assertion failure: game.turmoil is undefined for ${ game . id }` ) ; } else { return cb ( game . turmoil ) ; } } } public static ifTurmoilElse < T > ( game , cb , elseCb ) { if ( game . gameOptions . turmoilExtension !== false ) { if ( game . turmoil === undefined ) { console . log ( `Assertion failure: game.turmoil is undefined for ${ game . id }` ) ; } else { return cb ( game . turmoil ) ; } } return elseCb ( ) ; } public initGlobalEvent ( game ) { this . comingGlobalEvent = this . globalEventDealer . draw ( ) ; if ( this . comingGlobalEvent !== undefined ) { this . sendDelegateToParty ( 'NEUTRAL' , this . comingGlobalEvent . revealedDelegate , game ) ; } this . distantGlobalEvent = this . globalEventDealer . draw ( ) ; if ( this . distantGlobalEvent !== undefined ) { this . sendDelegateToParty ( 'NEUTRAL' , this . distantGlobalEvent . revealedDelegate , game ) ; } } public getPartyByName ( name ) { const party = this . parties . find ( ( party ) => party . name === name ) ; if ( party === undefined ) { throw new Error ( `Cannot find party with name {${ name }}` ) ; } return party ; } public sendDelegateToParty ( playerId , partyName , game , source = 'lobby' ) { const party = this . getPartyByName ( partyName ) ; if ( playerId !== 'NEUTRAL' && this . lobby . has ( playerId ) && source === 'lobby' ) { this . lobby . delete ( playerId ) ; } else { const index = this . delegateReserve . indexOf ( playerId ) ; if ( index > - 1 ) { this . delegateReserve . splice ( index , 1 ) ; } else { console . log ( `${ playerId }/${ game . id } tried to get a delegate from an empty reserve.` ) ; return ; } } party . sendDelegate ( playerId , game ) ; this . checkDominantParty ( party ) ; } public removeDelegateFromParty ( playerId , partyName , game ) { const party = this . getPartyByName ( partyName ) ; this . delegateReserve . push ( playerId ) ; party . removeDelegate ( playerId , game ) ; this . checkDominantParty ( party ) ; } public replaceDelegateFromParty ( outgoingPlayerId , incomingPlayerId , source = 'lobby' , partyName , game ) { const party = this . getPartyByName ( partyName ) ; this . delegateReserve . push ( outgoingPlayerId ) ; party . removeDelegate ( outgoingPlayerId , game ) ; this . sendDelegateToParty ( incomingPlayerId , partyName , game , source ) ; } public checkDominantParty ( party ) { if ( this . dominantParty ) { const sortParties = [ ... this . parties ] . sort ( ( p1 , p2 ) => p2 . delegates . length - p1 . delegates . length ) ; const max = sortParties [ 0 ] . delegates . length ; if ( this . dominantParty . delegates . length !== max ) { this . setNextPartyAsDominant ( this . dominantParty ) ; } } else { this . dominantParty = party ; } } public setNextPartyAsDominant ( currentDominantParty ) { const sortParties = [ ... this . parties ] . sort ( ( p1 , p2 ) => p2 . delegates . length - p1 . delegates . length ) ; const max = sortParties [ 0 ] . delegates . length ; const currentIndex = this . parties . indexOf ( currentDominantParty ) ; let partiesToCheck = [ ] ; if ( currentIndex === 0 ) { partiesToCheck = this . parties . slice ( currentIndex + 1 ) ; } else if ( currentIndex === this . parties . length - 1 ) { partiesToCheck = this . parties . slice ( 0 , currentIndex ) ; } else { const left = this . parties . slice ( 0 , currentIndex ) ; const right = this . parties . slice ( currentIndex + 1 ) ; partiesToCheck = right . concat ( left ) ; } const partiesOrdered = partiesToCheck . reverse ( ) ; partiesOrdered . some ( ( newParty ) => { if ( newParty . delegates . length === max ) { this . dominantParty = newParty ; return true ; } return false ; } ) ; } public endGeneration ( game ) { game . getPlayers ( ) . forEach ( ( player ) => { player . decreaseTerraformRating ( ) ; } ) ; if ( this . currentGlobalEvent !== undefined ) { const currentGlobalEvent = this . currentGlobalEvent ; game . log ( 'Resolving global event ${0}' , ( b ) => b . globalEvent ( currentGlobalEvent ) ) ; currentGlobalEvent . resolve ( game , this ) ; } this . rulingParty = this . dominantParty ; if ( this . rulingParty ) { this . setRulingParty ( game ) ; } this . setNextPartyAsDominant ( this . rulingParty ! ) ; this . lobby . forEach ( ( playerId ) => { this . delegateReserve . push ( playerId ) ; } ) ; this . lobby = new Set < string > ( ) ; game . getPlayers ( ) . forEach ( ( player ) => { if ( this . hasDelegatesInReserve ( player . id ) ) { const index = this . delegateReserve . indexOf ( player . id ) ; if ( index > - 1 ) { this . delegateReserve . splice ( index , 1 ) ; } this . lobby . add ( player . id ) ; } } ) ; if ( this . currentGlobalEvent ) { this . globalEventDealer . discardedGlobalEvents . push ( this . currentGlobalEvent ) ; } this . currentGlobalEvent = this . comingGlobalEvent ; if ( this . currentGlobalEvent ) { this . sendDelegateToParty ( 'NEUTRAL' , this . currentGlobalEvent . currentDelegate , game ) ; } this . comingGlobalEvent = this . distantGlobalEvent ; this . distantGlobalEvent = this . globalEventDealer . draw ( ) ; if ( this . distantGlobalEvent ) { this . sendDelegateToParty ( 'NEUTRAL' , this . distantGlobalEvent . revealedDelegate , game ) ; } game . log ( 'Turmoil phase has been resolved' ) ; } public setRulingParty ( game ) { if ( this . rulingParty !== undefined ) { game . getPlayers ( ) . forEach ( ( player ) => player . hasTurmoilScienceTagBonus = false ) ; if ( this . chairman ) { this . delegateReserve . push ( this . chairman ) ; } this . chairman = this . rulingParty . partyLeader || 'NEUTRAL' ; const index = this . rulingParty . delegates . indexOf ( this . rulingParty . partyLeader ! ) ; this . rulingParty . delegates . splice ( index , 1 ) ; this . delegateReserve = this . delegateReserve . concat ( this . rulingParty . delegates ) ; this . rulingParty . partyLeader = undefined ; this . rulingParty . delegates = [ ] ; PoliticalAgendas . setNextAgenda ( this , game ) ; if ( this . chairman !== 'NEUTRAL' ) { const player = game . getPlayerById ( this . chairman ) ; const steps = player . corporationCard ?. name === CardName . TEMPEST_CONSULTANCY ? 2 : 1 ; game . defer ( new DeferredAction ( player , ( ) => { player . increaseTerraformRatingSteps ( steps ) ; game . log ( '${0} is the new chairman and gains ${1} TR' , ( b ) => b . player ( player ) . number ( steps ) ) ; return undefined ; } ) ) ; } else { game . log ( 'A neutral delegate is the new chairman.' ) ; } } } public onAgendaSelected ( game ) { const rulingParty = this . rulingParty ; const bonusId = PoliticalAgendas . currentAgenda ( this ) . bonusId ; const bonus = rulingParty . bonuses . find ( ( b ) => b . id === bonusId ) ; if ( bonus === undefined ) { throw new Error ( `Bonus id ${ bonusId } not found in party ${ rulingParty . name }` ) ; } game . log ( 'The ruling bonus is: ${0}' , ( b ) => b . string ( bonus . description ) ) ; bonus . grant ( game ) ; const policyId = PoliticalAgendas . currentAgenda ( this ) . policyId ; const policy = rulingParty . policies . find ( ( p ) => p . id === policyId ) ; if ( policy === undefined ) { throw new Error ( `Policy id ${ policyId } not found in party ${ rulingParty . name }` ) ; } game . log ( 'The ruling policy is: ${0}' , ( b ) => b . string ( policy . description ) ) ; if ( policy . apply !== undefined ) { policy . apply ( game ) ; } } public getPlayerInfluence ( player ) { let influence = 0 ; if ( this . chairman !== undefined && this . chairman === player . id ) influence ++ ; const dominantParty = this . dominantParty ; const isPartyLeader = dominantParty . partyLeader === player . id ; const delegateCount = dominantParty . delegates . filter ( ( delegate ) => delegate === player . id ) . length ; if ( isPartyLeader ) { influence ++ ; if ( delegateCount > 1 ) influence ++ ; } else { if ( delegateCount > 0 ) influence ++ ; } if ( this . playersInfluenceBonus . has ( player . id ) ) { const bonus = this . playersInfluenceBonus . get ( player . id ) ; if ( bonus ) { influence += bonus ; } } return influence ; } public addInfluenceBonus ( player , bonus = 1 ) { if ( this . playersInfluenceBonus . has ( player . id ) ) { let current = this . playersInfluenceBonus . get ( player . id ) ; if ( current ) { current += bonus ; this . playersInfluenceBonus . set ( player . id , current ) ; } } else { this . playersInfluenceBonus . set ( player . id , bonus ) ; } } public canPlay ( player , partyName ) { if ( this . rulingParty . name === partyName ) { return true ; } const party = this . getPartyByName ( partyName ) ; return party . getDelegates ( player . id ) >= 2 ; } public getPresentPlayersInReserve ( ) { return Array . from ( new Set ( this . delegateReserve ) ) ; } public getAvailableDelegateCount ( playerId , source ) { const delegatesInReserve = this . delegateReserve . filter ( ( p ) => p === playerId ) . length ; const delegatesInLobby = this . lobby . has ( playerId ) ? 1 : 0 ; switch ( source ) { case 'lobby' : return delegatesInLobby ; case 'reserve' : return delegatesInReserve ; case 'both' : return delegatesInLobby + delegatesInReserve ; } } public hasDelegatesInReserve ( playerId ) { return this . getAvailableDelegateCount ( playerId , 'reserve' ) > 0 ; } public getPlayerVictoryPoints ( player ) { let victory = 0 ; if ( this . chairman !== undefined && this . chairman === player . id ) victory ++ ; this . parties . forEach ( function ( party ) { if ( party . partyLeader === player . id ) { victory ++ ; } } ) ; return victory ; } public serialize ( ) { const result = { chairman : this . chairman , rulingParty : this . rulingParty . name , dominantParty : this . dominantParty . name , lobby : Array . from ( this . lobby ) , delegateReserve : this . delegateReserve , parties : this . parties . map ( ( p ) => { return { name : p . name , delegates : p . delegates , partyLeader : p . partyLeader , } ; } ) , playersInfluenceBonus : Array . from ( this . playersInfluenceBonus . entries ( ) ) , globalEventDealer : this . globalEventDealer . serialize ( ) , distantGlobalEvent : this . distantGlobalEvent ?. name , comingGlobalEvent : this . comingGlobalEvent ?. name , politicalAgendasData : PoliticalAgendas . serialize ( this . politicalAgendasData ) , } ; if ( this . currentGlobalEvent !== undefined ) { result . currentGlobalEvent = this . currentGlobalEvent . name ; } return result ; } public static deserialize ( d ) { const dealer = GlobalEventDealer . deserialize ( d . globalEventDealer ) ; const turmoil = new Turmoil ( d . rulingParty , d . chairman || 'NEUTRAL' , d . dominantParty , dealer ) ; turmoil . lobby = new Set ( d . lobby ) ; turmoil . delegateReserve = d . delegateReserve ; turmoil . politicalAgendasData = PoliticalAgendas . deserialize ( d . politicalAgendasData ) ; d . parties . forEach ( ( sp ) => { const tp = turmoil . getPartyByName ( sp . name ) ; if ( tp === undefined ) { throw new Error ( 'huh? unknown party: ' + sp . name ) ; } tp . delegates = sp . delegates ; tp . partyLeader = sp . partyLeader ; } ) ; turmoil . playersInfluenceBonus = new Map < string , number > ( d . playersInfluenceBonus ) ; if ( d . distantGlobalEvent ) { turmoil . distantGlobalEvent = getGlobalEventByName ( d . distantGlobalEvent ) ; } if ( d . comingGlobalEvent ) { turmoil . comingGlobalEvent = getGlobalEventByName ( d . comingGlobalEvent ) ; } if ( d . currentGlobalEvent ) { turmoil . currentGlobalEvent = getGlobalEventByName ( d . currentGlobalEvent ) ; } return turmoil ; } }