diff --git a/src/services/TechCardSelection.ts b/src/services/TechCardSelection.ts new file mode 100644 index 0000000..281f0af --- /dev/null +++ b/src/services/TechCardSelection.ts @@ -0,0 +1,115 @@ +import { TechCardSelectionPersistence } from '@/store/state' +import Tech from './enum/Tech' +import TechPlaceholder from './enum/TechPlaceholder' +import { ref } from 'vue' +import { cloneDeep, shuffle } from 'lodash' +import getAllEnumValues from '@brdgm/brdgm-commons/src/util/enum/getAllEnumValues' +import DraftingRowCard from './DraftingRowCard' +import DraftingPriorityCard from './DraftingPriorityCard' +import TechMatch from './enum/TechMatch' +import getTechDuration from '@/util/getTechDuration' + +/** + * Manages the drafting of tech cards. + */ +export default class TechCardSelection { + + private readonly _techs + private readonly _round : number + + private constructor(techs: (Tech|TechPlaceholder)[][], round: number) { + this._techs = ref(techs) + this._round = round + } + + public get techs() : readonly (Tech|TechPlaceholder)[][] { + return this._techs.value + } + + /** + * Gets persistence view. + */ + public toPersistence() : TechCardSelectionPersistence { + return { + techs: this._techs.value + } + } + + /** + * Determines the tech card to be drafted by Automa. + * @param row Row selection + * @param priority Priority selection + * @param prosperityTechs Prosperity techs + */ + public determineTech(rowCard: DraftingRowCard, priorityCard: DraftingPriorityCard, prosperityTechs: Tech[]) : Tech { + for (const row of rowCard.rows) { + const rowTechs = this._techs.value[row - 1] + if (this.getOnlyTechs(rowTechs).length > 2) { + for (const priority of priorityCard.priority) { + const matchingIndexes = this.findMatchingIndexes(rowTechs, priority, prosperityTechs) + for (const column of rowCard.columns) { + if (matchingIndexes.includes(column - 1)) { + return rowTechs[column - 1] as Tech + } + } + } + } + } + throw new Error('Not matching tech card found.') + } + + private getOnlyTechs(techs: (Tech|TechPlaceholder)[]) : Tech[] { + return techs.filter(tech => tech != TechPlaceholder.PLACEHOLDER && tech != TechPlaceholder.EMPTY) as Tech[] + } + + private findMatchingIndexes(techs: (Tech|TechPlaceholder)[], priority: (Tech|TechMatch), prosperityTechs: Tech[]) : number[] { + const onlyTechs = this.getOnlyTechs(techs) + const maxAge = onlyTechs.map(tech => getTechDuration(tech, this._round)).reduce((a,b) => Math.max(a,b)) + let matchingTechs + if (priority == TechMatch.MAX_AGE) { + matchingTechs = onlyTechs.filter(tech => getTechDuration(tech, this._round) == maxAge) + } + else if (priority == TechMatch.PROSPERITY) { + matchingTechs = onlyTechs.filter(tech => prosperityTechs.includes(tech)) + } + else { + matchingTechs = onlyTechs.filter(tech => tech == priority) + } + return matchingTechs.map(tech => techs.indexOf(tech)) + } + + /** + * Removes a tech card. + * @param tech Tech card. + */ + public remove(tech: Tech) : void { + this._techs.value.forEach((row,index) => { + this._techs.value[index] = row.map(item => item == tech ? TechPlaceholder.EMPTY : item) + }) + } + + /** + * Creates a selection of techs in 4 ros respecting the placeholder rows. + */ + public static new(placeholderRows: number[], round: number) : TechCardSelection { + const allTechs = shuffle(getAllEnumValues(Tech)) + const techs : (Tech|TechPlaceholder)[][] = [] + for (let row = 1; row <= 4; row++) { + if (placeholderRows.includes(row)) { + techs.push([...allTechs.splice(0, 3), TechPlaceholder.PLACEHOLDER]) + } + else { + techs.push(allTechs.splice(0, 4)) + } + } + return new TechCardSelection(techs, round) + } + + /** + * Re-creates from persistence. + */ + public static fromPersistence(persistence : TechCardSelectionPersistence, round: number) : TechCardSelection { + return new TechCardSelection(cloneDeep(persistence.techs), round) + } + +} diff --git a/src/services/enum/TechPlaceholder.ts b/src/services/enum/TechPlaceholder.ts new file mode 100644 index 0000000..fa80f83 --- /dev/null +++ b/src/services/enum/TechPlaceholder.ts @@ -0,0 +1,8 @@ +/** + * Tech placeholder + */ +enum TechPlaceholder { + PLACEHOLDER = 'placeholder', + EMPTY = 'empty' +} +export default TechPlaceholder diff --git a/src/store/state.ts b/src/store/state.ts index 3a35fb0..4d55dae 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -3,6 +3,7 @@ import { name } from '@/../package.json' import DifficultyLevel from '@/services/enum/DifficultyLevel' import Player from '@/services/enum/Player' import Tech from '@/services/enum/Tech' +import TechPlaceholder from '@/services/enum/TechPlaceholder' export const useStateStore = defineStore(`${name}.state`, { state: () => { @@ -90,3 +91,6 @@ export interface ProsperityCardsPersistence { export interface RowPlaceholdersPersistence { rows: number[] } +export interface TechCardSelectionPersistence { + techs: (Tech|TechPlaceholder)[][] +} diff --git a/src/util/getTechDuration.ts b/src/util/getTechDuration.ts index c91a8f0..48e538d 100644 --- a/src/util/getTechDuration.ts +++ b/src/util/getTechDuration.ts @@ -6,8 +6,8 @@ import Tech from '@/services/enum/Tech' * @param age Age * @returns Age duration */ -export default function(tech: Tech, age: number) : number { - const index = age - 1 +export default function(tech: Tech, round: number) : number { + const index = round - 1 switch (tech) { case Tech.AGRICULTURE: return [2,3,1,2,1,1,2,1][index] case Tech.ARMY: return [1,1,1,2,2,3,2,1][index] diff --git a/tests/unit/services/ProsperityCards.spec.ts b/tests/unit/services/ProsperityCards.spec.ts index 7392ff8..faf2797 100644 --- a/tests/unit/services/ProsperityCards.spec.ts +++ b/tests/unit/services/ProsperityCards.spec.ts @@ -16,7 +16,7 @@ describe('services/ProsperityCards', () => { expect(persistence.discard.length).to.eq(0) }) - it('fromPersistence', () => { + it('prepareForNextRound', () => { const botCards = ProsperityCards.fromPersistence({ pile: [ Tech.COMMUNICATION, Tech.ECONOMICS, diff --git a/tests/unit/services/RowPlaceholders.spec.ts b/tests/unit/services/RowPlaceholders.spec.ts index f88f107..c704297 100644 --- a/tests/unit/services/RowPlaceholders.spec.ts +++ b/tests/unit/services/RowPlaceholders.spec.ts @@ -15,7 +15,7 @@ describe('services/RowPlaceholders', () => { expect(persistence.rows.length).to.eq(2) }) - it('fromPersistence', () => { + it('prepareForNextRound', () => { const rowPlaceholders = RowPlaceholders.fromPersistence({rows: [1, 3]}) expect(rowPlaceholders.rows).to.eql([1, 3]) diff --git a/tests/unit/services/TechCardSelection.spec.ts b/tests/unit/services/TechCardSelection.spec.ts new file mode 100644 index 0000000..b81f0f0 --- /dev/null +++ b/tests/unit/services/TechCardSelection.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai' +import Tech from '@/services/enum/Tech' +import TechCardSelection from '@/services/TechCardSelection' +import TechPlaceholder from '@/services/enum/TechPlaceholder' +import DraftingRowCards from '@/services/DraftingRowCards' +import DraftingPriorityCards from '@/services/DraftingPriorityCards' + +describe('services/TechCardSelection', () => { + it('new', () => { + const techCardSelection = TechCardSelection.new([1,4], 1) + + expect(techCardSelection.techs.length).to.eq(4) + expect(techCardSelection.techs.flat().length).to.eq(16) + + const persistence = techCardSelection.toPersistence() + expect(persistence.techs.length).to.eq(4) + expect(persistence.techs.flat().length).to.eq(16) + }) + + it('remove', () => { + const techCardSelection = TechCardSelection.fromPersistence({ + techs: [ + [Tech.COMMUNICATION, Tech.ECONOMICS, Tech.ENERGY, Tech.ENGINEERING], + [Tech.GOVERNMENT, Tech.MEDICINE, Tech.SCIENCE, TechPlaceholder.PLACEHOLDER], + [Tech.SOCIAL_SCIENCE, Tech.TRANSPORTATION, Tech.ARTS, TechPlaceholder.PLACEHOLDER], + [Tech.ARMY, Tech.MILITARY, Tech.WEAPONRY, Tech.AGRICULTURE] + ] + }, 1) + expect(techCardSelection.techs.flat().length).to.eq(16) + + techCardSelection.remove(Tech.TRANSPORTATION) + expect(techCardSelection.techs.flat().length).to.eq(16) + expect(techCardSelection.techs[2]).to.eql([Tech.SOCIAL_SCIENCE, TechPlaceholder.EMPTY, Tech.ARTS, TechPlaceholder.PLACEHOLDER]) + }) + + it('determineTech', () => { + const techCardSelection = TechCardSelection.fromPersistence({ + techs: [ + [Tech.WEAPONRY, TechPlaceholder.EMPTY, TechPlaceholder.EMPTY, Tech.ENGINEERING], + [Tech.GOVERNMENT, Tech.MEDICINE, Tech.SCIENCE, TechPlaceholder.PLACEHOLDER], + [Tech.SOCIAL_SCIENCE, Tech.TRANSPORTATION, Tech.ARTS, TechPlaceholder.PLACEHOLDER], + [Tech.ARMY, Tech.MILITARY, Tech.AGRICULTURE, Tech.COMMUNICATION] + ] + }, 2) + const prosperityTechs = [Tech.ECONOMICS, Tech.MEDICINE, Tech.SCIENCE, Tech.TRANSPORTATION] + + // rows [4,3,2,1] priority [MAX_AGE, PROSPERITY,ENGINEERING,ARMY] columns [1,2,3,4] + expect(techCardSelection.determineTech(DraftingRowCards.get(1), DraftingPriorityCards.get(1), prosperityTechs)).to.eq(Tech.AGRICULTURE) + + // rows [1,4,3,2], priority [PROSPERITY,ENGINEERING,COMMUNICATION,MAX_AGE] columns [1,2,4,3] + expect(techCardSelection.determineTech(DraftingRowCards.get(2), DraftingPriorityCards.get(3), prosperityTechs)).to.eq(Tech.COMMUNICATION) + + // rows [2,1,4,3], priority [PROSPERITY,ENGINEERING,COMMUNICATION,MAX_AGE] columns [1,3,2,4] + expect(techCardSelection.determineTech(DraftingRowCards.get(3), DraftingPriorityCards.get(3), prosperityTechs)).to.eq(Tech.SCIENCE) + }) +}) diff --git a/tests/unit/util/getTechDuration.spec.ts b/tests/unit/util/getTechDuration.spec.ts index 014c74d..0a4a3f3 100644 --- a/tests/unit/util/getTechDuration.spec.ts +++ b/tests/unit/util/getTechDuration.spec.ts @@ -6,11 +6,11 @@ import getTechDuration from '@/util/getTechDuration' describe('util/getTechDuration', () => { it('getTechDuration', () => { for (const tech of getAllEnumValues(Tech)) { - for (let age = 1; age<=8; age++) { - const duration = getTechDuration(tech, age) + for (let round = 1; round<=8; round++) { + const duration = getTechDuration(tech, round) expect(duration).to.greaterThanOrEqual(1) expect(duration).to.lessThanOrEqual(3) - expect(age + duration - 1).to.lessThanOrEqual(8) + expect(round + duration - 1).to.lessThanOrEqual(8) } } })