Skip to content

Commit

Permalink
Simplify player action payload providers
Browse files Browse the repository at this point in the history
  • Loading branch information
Konrad Jamrozik committed Jul 12, 2024
1 parent a7c2607 commit ceb2746
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 73 deletions.
6 changes: 6 additions & 0 deletions web/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ module.exports = {
'unicorn/no-negated-condition': 'off',
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-keyword-prefix.md
'unicorn/no-keyword-prefix': 'off',
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v53.0.0/docs/rules/prefer-object-from-entries.md
// I prefer using _.fromPairs() instead, for consistency with using other lodash methods.
// Specifically, I decided for following replacements:
// Object.fromEntries -> _.fromPairs
// Object.entries -> _.toPairs (or _.map)
'unicorn/prefer-object-from-entries': 'off',

// https://github.com/wix-incubator/eslint-plugin-lodash/blob/master/docs/rules/import-scope.md
'lodash/import-scope': ['error', 'full'],
Expand Down
153 changes: 86 additions & 67 deletions web/src/lib/api/playerActionsPayloadsProviders.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,100 @@
// The a bit more advanced typing done in this file was figured out with the help of ChatGPT:
// https://chat.openai.com/share/af4ac2cb-c221-4c7f-a5c6-e3cac23916c0
import _ from 'lodash'
import type { PlayerActionNameInTurn } from '../codesync/PlayerActionName'
import type { PlayerActionPayload } from '../codesync/PlayerActionPayload'

export const playerActionsPayloadsProviders: {
[actionName in PlayerActionNameInTurn]: PayloadProviderMap[actionName]
} = {
// CODESYNC: this must match the definition of PayloadProviderMap.
const actionNameToPayloadProviderFactoryMap: {
[actionName in PlayerActionNameInTurn]: PayloadProviderFactory
} =
// prettier-ignore
{
// Note: currently Cap always buys 1 capacity. See PlayerActionPayload.cs in backend.
BuyTransportCapacityPlayerAction: (TargetId: number) => ({
Name: 'BuyTransportCapacityPlayerAction' as const,
TargetId,
}),
HireAgentsPlayerAction: (TargetId: number) => ({
Name: 'HireAgentsPlayerAction' as const,
TargetId,
}),
SackAgentsPlayerAction: (Ids: number[]) => ({
Name: 'SackAgentsPlayerAction' as const,
Ids,
}),
SendAgentsToGenerateIncomePlayerAction: (Ids: number[]) => ({
Name: 'SendAgentsToGenerateIncomePlayerAction' as const,
Ids,
}),
SendAgentsToGatherIntelPlayerAction: (Ids: number[]) => ({
Name: 'SendAgentsToGatherIntelPlayerAction' as const,
Ids,
}),
SendAgentsToTrainingPlayerAction: (Ids: number[]) => ({
Name: 'SendAgentsToTrainingPlayerAction' as const,
Ids,
}),
RecallAgentsPlayerAction: (Ids: number[]) => ({
Name: 'RecallAgentsPlayerAction' as const,
Ids,
}),
LaunchMissionPlayerAction: (Ids: number[], TargetId: number) => ({
Name: 'LaunchMissionPlayerAction' as const,
Ids,
TargetId,
}),
InvestIntelPlayerAction: (Ids: number[], TargetId: number) => ({
Name: 'InvestIntelPlayerAction' as const,
Ids,
TargetId,
}),
BuyTransportCapacityPlayerAction : payloadFromTargetIdFactory,
HireAgentsPlayerAction : payloadFromTargetIdFactory,
InvestIntelPlayerAction : payloadFromIdsAndTargetIdFactory,
LaunchMissionPlayerAction : payloadFromIdsAndTargetIdFactory,
RecallAgentsPlayerAction : payloadFromIdsFactory,
SackAgentsPlayerAction : payloadFromIdsFactory,
SendAgentsToGatherIntelPlayerAction : payloadFromIdsFactory,
SendAgentsToGenerateIncomePlayerAction : payloadFromIdsFactory,
SendAgentsToTrainingPlayerAction : payloadFromIdsFactory,
}

export type PayloadProvider =
| PayloadFromIds
| PayloadFromTargetId
| PayloadFromIdsAndTargetId
// CODESYNC: this must match the definition of actionNameToPayloadProviderFactoryMap.
//
// Note: this block DOES NOT PROVIDE any type-safety,
// as the actionNameToPayloadProviderMap const is manually asserted to be of this type.
// It provides better autocomplete on the client side.
//
// However, even if this block would provide types-safety, it would protect only against
// given payload provider for given action having too many parameters, but not not enough.
//
// For example, if this declares "HireAgentsPlayerAction" player action should produce payload
// only from "Target" parameter,, and the actual implementation would take both "Target" and also "IDs" parameters,
// then this would properly catch it.
//
// However, if this declares "LaunchMissionPlayerAction" player action
// should produce payload from "IDs" and "TargetId" parameters, and the actual implementation
// would take "IDs" parameter only, then this would not catch that.
type PayloadProviderMap =
// prettier-ignore
{
BuyTransportCapacityPlayerAction : PayloadFromTargetId
HireAgentsPlayerAction : PayloadFromTargetId
InvestIntelPlayerAction : PayloadFromIdsAndTargetId
LaunchMissionPlayerAction : PayloadFromIdsAndTargetId
RecallAgentsPlayerAction : PayloadFromIds
SackAgentsPlayerAction : PayloadFromIds
SendAgentsToGatherIntelPlayerAction : PayloadFromIds
SendAgentsToGenerateIncomePlayerAction : PayloadFromIds
SendAgentsToTrainingPlayerAction : PayloadFromIds
}

export type PayloadFromIds = (Ids: number[]) => PlayerActionPayload
export type PayloadFromTargetId = (TargetId: number) => PlayerActionPayload
export type PayloadFromIdsAndTargetId = (
export const actionNameToPayloadProviderMap: PayloadProviderMap = _.fromPairs(
_.map(actionNameToPayloadProviderFactoryMap, (factory, actionName) => {
const typedActionName = actionName as PlayerActionNameInTurn
return [typedActionName, factory(typedActionName)]
}),
) as PayloadProviderMap

type PayloadProviderFactory =
| ((name: PlayerActionNameInTurn) => PayloadFromIds)
| ((name: PlayerActionNameInTurn) => PayloadFromIdsAndTargetId)
| ((name: PlayerActionNameInTurn) => PayloadFromTargetId)

type PayloadFromIds = (Ids: number[]) => PlayerActionPayload

type PayloadFromTargetId = (TargetId: number) => PlayerActionPayload

type PayloadFromIdsAndTargetId = (
Ids: number[],
TargetId: number,
) => PlayerActionPayload

type PayloadProviderMap = {
[key in PlayerActionNameInTurn]: PayloadProvider
} & {
// Note: this block provides type-safety only against a payload provider for given action
// having too many parameters, but not not enough.
// For example, if this declares "AdvanceTime" player action should produce payload
// from no parameters, and the actual implementation would take some parameters,
// then this would properly catch it.
// However, if this declares "RecallAgents" should produce payload from "IDs" parameter,
// and the actual implementation would take no parameters, then this would not catch that.
BuyTransportCapacityPlayerAction: PayloadFromTargetId
HireAgentsPlayerAction: PayloadFromTargetId
InvestIntelPlayerAction: PayloadFromIdsAndTargetId
LaunchMissionPlayerAction: PayloadFromIdsAndTargetId
RecallAgentsPlayerAction: PayloadFromIds
SackAgentsPlayerAction: PayloadFromIds
SendAgentsToGatherIntelPlayerAction: PayloadFromIds
SendAgentsToGenerateIncomePlayerAction: PayloadFromIds
SendAgentsToTrainingPlayerAction: PayloadFromIds
function payloadFromIdsFactory(name: PlayerActionNameInTurn): PayloadFromIds {
return (ids: number[]) => ({
Name: name,
Ids: ids,
})
}

function payloadFromTargetIdFactory(
name: PlayerActionNameInTurn,
): PayloadFromTargetId {
return (targetId: number) => ({
Name: name,
TargetId: targetId,
})
}

function payloadFromIdsAndTargetIdFactory(
name: PlayerActionNameInTurn,
): PayloadFromIdsAndTargetId {
return (ids: number[], targetId: number) => ({
Name: name,
Ids: ids,
TargetId: targetId,
})
}
12 changes: 6 additions & 6 deletions web/src/lib/gameSession/GameSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Md5 } from 'ts-md5'
import { GameSessionContext } from '../../components/GameSessionProvider'
import { callAdvanceTurnsApi } from '../api/advanceTurnsApi'
import { callApplyPlayerActionApi } from '../api/applyPlayerActionApi'
import { playerActionsPayloadsProviders } from '../api/playerActionsPayloadsProviders'
import { actionNameToPayloadProviderMap } from '../api/playerActionsPayloadsProviders'
import type { GameEventWithTurn } from '../codesync/GameEvent'
import {
getTurnNo,
Expand Down Expand Up @@ -116,7 +116,7 @@ export class GameSession {

public async hireAgents(count: number): Promise<boolean> {
const payloadProvider =
playerActionsPayloadsProviders.HireAgentsPlayerAction
actionNameToPayloadProviderMap.HireAgentsPlayerAction
const payload = payloadProvider(count)
return this.applyPlayerAction(payload)
}
Expand All @@ -126,7 +126,7 @@ export class GameSession {
amount: number,
): Promise<boolean> {
const payloadProvider =
playerActionsPayloadsProviders.InvestIntelPlayerAction
actionNameToPayloadProviderMap.InvestIntelPlayerAction
const payload = payloadProvider([factionId], amount)
return this.applyPlayerAction(payload)
}
Expand All @@ -140,7 +140,7 @@ export class GameSession {
}
/* c8 ignore stop */
const payloadProvider =
playerActionsPayloadsProviders.BuyTransportCapacityPlayerAction
actionNameToPayloadProviderMap.BuyTransportCapacityPlayerAction
const payload = payloadProvider(count)
return this.applyPlayerAction(payload)
}
Expand All @@ -150,7 +150,7 @@ export class GameSession {
missionSiteId: number,
): Promise<boolean> {
const payloadProvider =
playerActionsPayloadsProviders.LaunchMissionPlayerAction
actionNameToPayloadProviderMap.LaunchMissionPlayerAction
const payload = payloadProvider(agentsIds, missionSiteId)
return this.applyPlayerAction(payload)
}
Expand All @@ -159,7 +159,7 @@ export class GameSession {
playerActionName: AgentPlayerActionName,
agentsIds: number[],
): Promise<boolean> {
const payloadProvider = playerActionsPayloadsProviders[playerActionName]
const payloadProvider = actionNameToPayloadProviderMap[playerActionName]
const payload = payloadProvider(agentsIds)
return this.applyPlayerAction(payload)
}
Expand Down

0 comments on commit ceb2746

Please sign in to comment.