Skip to content

Commit

Permalink
tech card selection
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanseifert committed Dec 25, 2024
1 parent a6d28df commit ec00748
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 7 deletions.
115 changes: 115 additions & 0 deletions src/services/TechCardSelection.ts
Original file line number Diff line number Diff line change
@@ -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)
}

}
8 changes: 8 additions & 0 deletions src/services/enum/TechPlaceholder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Tech placeholder
*/
enum TechPlaceholder {
PLACEHOLDER = 'placeholder',
EMPTY = 'empty'
}
export default TechPlaceholder
4 changes: 4 additions & 0 deletions src/store/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {
Expand Down Expand Up @@ -90,3 +91,6 @@ export interface ProsperityCardsPersistence {
export interface RowPlaceholdersPersistence {
rows: number[]
}
export interface TechCardSelectionPersistence {
techs: (Tech|TechPlaceholder)[][]
}
4 changes: 2 additions & 2 deletions src/util/getTechDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/services/ProsperityCards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/services/RowPlaceholders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
56 changes: 56 additions & 0 deletions tests/unit/services/TechCardSelection.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
6 changes: 3 additions & 3 deletions tests/unit/util/getTechDuration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
})
Expand Down

0 comments on commit ec00748

Please sign in to comment.