diff --git a/src/board/BuildingTile.svelte b/src/board/BuildingTile.svelte index 1465665..f8e8da8 100644 --- a/src/board/BuildingTile.svelte +++ b/src/board/BuildingTile.svelte @@ -1,7 +1,7 @@

{ building.name }

diff --git a/src/create.ts b/src/create.ts index 20713dd..7ba2388 100644 --- a/src/create.ts +++ b/src/create.ts @@ -1,6 +1,6 @@ -import buildings from "./buildings"; +import buildings from "./data/buildings"; import { Hex } from "./hexgrid"; -import type { Building, BuildingSource } from "./types"; +import type { BuildingType, BuildingSource } from "./types"; let uid = 0; @@ -17,7 +17,7 @@ export function getBuildingSource(buildingType: string): BuildingSource { } -export function createBuilding(buildingType: string): Building { +export function createBuilding(buildingType: string): BuildingType { const source: BuildingSource = getBuildingSource(buildingType); return { @@ -25,5 +25,6 @@ export function createBuilding(buildingType: string): Building { id: uid++, level: 1, tile: new Hex(0, 0), + state: {}, }; } diff --git a/src/buildings.ts b/src/data/buildings.ts similarity index 75% rename from src/buildings.ts rename to src/data/buildings.ts index 4ca610b..27fbe75 100644 --- a/src/buildings.ts +++ b/src/data/buildings.ts @@ -1,6 +1,6 @@ -import type { Building } from "./types"; -import { getEmptyResources } from "./utils"; -import type { VillageState } from "./village"; +import type { BuildingType } from "../types"; +import { getEmptyResources } from "../utils"; +import type { VillageState } from "../village"; export default [ @@ -17,7 +17,7 @@ export default [ }; }, behavior: { - storage: (_V: VillageState, _self: Building) => { + storage: (_V: VillageState, _self: BuildingType) => { return { 'wood': 100, 'stone': 100, @@ -40,7 +40,7 @@ export default [ }; }, behavior: { - production: (V: VillageState, self: Building) => { + production: (V: VillageState, self: BuildingType) => { const prod = getEmptyResources(); const outputPerMinute = 5 * (self.level * self.level); prod.wood = outputPerMinute; @@ -65,7 +65,7 @@ export default [ }; }, behavior: { - production: (V: VillageState, self: Building) => { + production: (V: VillageState, self: BuildingType) => { const prod = getEmptyResources(); const outputPerMinute = 5 * (self.level * self.level); prod.iron = outputPerMinute; @@ -90,7 +90,7 @@ export default [ }; }, behavior: { - production: (V: VillageState, self: Building) => { + production: (V: VillageState, self: BuildingType) => { const prod = getEmptyResources(); const outputPerMinute = 5 * (self.level * self.level); prod.stone = outputPerMinute; @@ -115,7 +115,7 @@ export default [ }; }, behavior: { - production: (V: VillageState, self: Building) => { + production: (V: VillageState, self: BuildingType) => { const prod = getEmptyResources(); const outputPerMinute = 5 * (self.level * self.level); prod.food = outputPerMinute; @@ -135,7 +135,7 @@ export default [ }; }, behavior: { - storage: (V: VillageState, self: Building) => { + storage: (V: VillageState, self: BuildingType) => { const x = self.level; const capacity = ( ( ( x + ( x * x ) ) / 2 ) + 3 ) * 25; return { @@ -159,7 +159,7 @@ export default [ }; }, behavior: { - storage: (V: VillageState, self: Building) => { + storage: (V: VillageState, self: BuildingType) => { const x = self.level; const capacity = ( ( ( x + ( x * x ) ) / 2 ) + 3 ) * 25; return { @@ -171,4 +171,28 @@ export default [ }, }, }, + { + type: 'university', + name: 'University', + cost: (level: number) => { + return { + wood: level * 100, + stone: level * 100, + iron: level * 100, + food: 0, + }; + }, + behavior: { + production: (V: VillageState, self: BuildingType) => { + const prod = getEmptyResources(); + const intakePerMinute = self.level * 2; + prod.food = -intakePerMinute; + return prod; + }, + units: { + type: 'philosopher', + recruitmentTime: (V: VillageState, self: BuildingType) => 2 - 0.06 * self.level, + }, + }, + } ]; diff --git a/src/data/units.ts b/src/data/units.ts new file mode 100644 index 0000000..49e996c --- /dev/null +++ b/src/data/units.ts @@ -0,0 +1,16 @@ +export default [ + { + type: 'philosopher', + name: 'Philosopher', + cost: { + wood: 20, + stone: 50, + iron: 0, + food: 0, + }, + behavior: { + culturePerMinute: 1, + foodIntakePerMinute: 2, + }, + }, +]; diff --git a/src/hud/BuildingPanel.svelte b/src/hud/BuildingPanel.svelte index 6e9721b..57da6da 100644 --- a/src/hud/BuildingPanel.svelte +++ b/src/hud/BuildingPanel.svelte @@ -1,9 +1,9 @@ + +
+
+ Wood + { cost.wood } +
+
+ Stone + { cost.stone } +
+
+ Iron + { cost.iron } +
+
+ Food + { cost.food } +
+
+ + diff --git a/src/hud/Game.svelte b/src/hud/Game.svelte index adf574f..7370fcb 100644 --- a/src/hud/Game.svelte +++ b/src/hud/Game.svelte @@ -9,8 +9,9 @@ import BuildingCreator from "./BuildingCreator.svelte"; import BuildingPanel from "./BuildingPanel.svelte"; import Navigation from "./Navigation.svelte"; - import Resources from "./Resources.svelte"; import Queue from "./Queue.svelte"; + import Resources from "./Resources.svelte"; + import Units from "./Units.svelte"; onMount(() => { @@ -34,6 +35,7 @@
+
diff --git a/src/hud/Units.svelte b/src/hud/Units.svelte new file mode 100644 index 0000000..f21bec6 --- /dev/null +++ b/src/hud/Units.svelte @@ -0,0 +1,18 @@ + + +
+ { #each currentUnits as unit } +

{ unit.name }: { unit.count }

+ { /each } +
diff --git a/src/hud/UniversityPanel.svelte b/src/hud/UniversityPanel.svelte new file mode 100644 index 0000000..1d00585 --- /dev/null +++ b/src/hud/UniversityPanel.svelte @@ -0,0 +1,72 @@ + + +
+

Create Philosophers

+
+ + + + +
+ + diff --git a/src/moves/build.ts b/src/moves/build.ts index ce77338..8818359 100644 --- a/src/moves/build.ts +++ b/src/moves/build.ts @@ -1,4 +1,4 @@ -import buildings from "../buildings"; +import buildings from "../data/buildings"; import { createBuilding, getBuildingSource } from "../create"; import type { Hex } from "../hexgrid"; import { enqueueBuilding } from "../utils"; diff --git a/src/moves/index.ts b/src/moves/index.ts index 7521b68..c7350e1 100644 --- a/src/moves/index.ts +++ b/src/moves/index.ts @@ -3,6 +3,7 @@ import { produce } from 'immer'; import village, { type VillageState } from '../village'; import build from './build'; import upgradeBuilding from './upgradeBuilding'; +import recruitUnits from './recruitUnits'; // Encapsulates a move function into a store update, where the data is made @@ -30,4 +31,5 @@ export function makeMove(move: (...args: any[]) => boolean) { export default { build: makeMove(build), upgradeBuilding: makeMove(upgradeBuilding), + recruitUnits: makeMove(recruitUnits), }; diff --git a/src/moves/recruitUnits.ts b/src/moves/recruitUnits.ts new file mode 100644 index 0000000..b3ba0a7 --- /dev/null +++ b/src/moves/recruitUnits.ts @@ -0,0 +1,46 @@ +import units from "../data/units"; +import { getBuilding } from "../utils"; +import type { VillageState } from "../village"; + + +export default function recruitUnits( + V: VillageState, buildingId: number, unitType: string, unitNumber: number +) { + const unit = units.find(u => u.type === unitType); + if (!unit) { + return false; + } + + const cost = { + wood: unit.cost.wood * unitNumber, + stone: unit.cost.stone * unitNumber, + iron: unit.cost.iron * unitNumber, + food: unit.cost.food * unitNumber, + }; + + if ( + cost.wood > V.resources.wood + || cost.stone > V.resources.stone + || cost.iron > V.resources.iron + || cost.food > V.resources.food + ) { + return false; + } + + V.resources.wood -= cost.wood; + V.resources.stone -= cost.stone; + V.resources.iron -= cost.iron; + V.resources.food -= cost.food; + + const building = getBuilding(V, buildingId); + if (!building.state.recruitment) { + building.state.recruitment = { + count: 0, + elapsedTime: 0, + }; + } + + building.state.recruitment.count += unitNumber; + + return true; +} diff --git a/src/types.ts b/src/types.ts index d8f61ac..259a6be 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,7 @@ import type { Hex } from "./hexgrid"; export type GameTab = 'village' | 'resources'; -export interface Cost { +export interface CostType { wood: number; stone: number; iron: number; @@ -12,23 +12,44 @@ export interface Cost { } -export type Production = Cost; +export type ProductionType = CostType; export interface BuildingSource { name: string; type: string; autoBuilt?: boolean; - cost: (level: number) => Cost; + cost: (level: number) => CostType; behavior: { production?: Function; storage?: Function; + units?: { + type: string; + recruitmentTime: Function; + }; }; } -export interface Building extends BuildingSource { +export interface BuildingType extends BuildingSource { id: number; level: number; tile: Hex; + state: { + recruitment?: { + count: number; + elapsedTime: number; + }; + }; +} + + +export interface UnitType { + type: string; + name: string; + cost: CostType; + behavior: { + foodIntakePerMinute: number; + culturePerMinute?: number; + } } diff --git a/src/update.ts b/src/update.ts index 75fc22a..6b3a3cc 100644 --- a/src/update.ts +++ b/src/update.ts @@ -2,7 +2,7 @@ import { produce } from 'immer'; import { getBuilding, getProduction, getStorage } from './utils'; import village, { type VillageState } from "./village"; -import type { Production } from './types'; +import type { ProductionType } from './types'; let lastFrame: number; @@ -33,7 +33,7 @@ export default function update(timestamp: number) { const storage = getStorage(V); Object.keys(productionPerMinute).forEach((key) => { - const resource = key as keyof Production; + const resource = key as keyof ProductionType; const outputPerMinute = productionPerMinute[resource]; const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0; V.resources[resource] += outputPerMilisecond * delta; @@ -46,6 +46,27 @@ export default function update(timestamp: number) { } }); + // Recruit units. + V.buildings.forEach(b => { + if (!b.state.recruitment || !b.state.recruitment.count) { + return; + } + + const recruitment = b.state.recruitment; + recruitment.elapsedTime += delta; + + const timeToRecruit = b.behavior.units?.recruitmentTime(V, b) * 1000; + if (recruitment.elapsedTime >= timeToRecruit) { + const unitType = b.behavior.units?.type || ''; + if (!V.units.hasOwnProperty(unitType)) { + V.units[unitType] = 0; + } + V.units[unitType]++; + recruitment.count--; + recruitment.elapsedTime = (recruitment.count === 0) ? 0 : timeToRecruit - recruitment.elapsedTime; + } + }); + return V; }); }); diff --git a/src/utils.ts b/src/utils.ts index 7ef51bb..b9cbe76 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ -import type { Building, Production } from "./types"; +import units from "./data/units"; +import type { BuildingType, ProductionType } from "./types"; import type { VillageState } from "./village"; -function _reduceResources(acc: Production, item: Production): Production { +function _reduceResources(acc: ProductionType, item: ProductionType): ProductionType { return { wood: acc.wood + item.wood, stone: acc.stone + item.stone, @@ -12,7 +13,7 @@ function _reduceResources(acc: Production, item: Production): Production { } -export function getEmptyResources(): Production { +export function getEmptyResources(): ProductionType { return { wood: 0, stone: 0, @@ -22,7 +23,7 @@ export function getEmptyResources(): Production { } -export function getProduction(villageState: VillageState): Production { +export function getProduction(villageState: VillageState): ProductionType { return villageState.buildings .filter(b => b.behavior.production && b.level > 0) .map(b => { @@ -34,7 +35,7 @@ export function getProduction(villageState: VillageState): Production { } -export function getStorage(villageState: VillageState): Production { +export function getStorage(villageState: VillageState): ProductionType { return villageState.buildings .filter(b => b.behavior.storage && b.level > 0) .map(b => { @@ -46,7 +47,7 @@ export function getStorage(villageState: VillageState): Production { } -export function getBuilding(V: VillageState, buildingId: number): Building { +export function getBuilding(V: VillageState, buildingId: number): BuildingType { const building = V.buildings.find(b => b.id === buildingId); if (!building) { throw new Error(`Cannot find building with id "${buildingId}"`); @@ -55,6 +56,15 @@ export function getBuilding(V: VillageState, buildingId: number): Building { } +export function getUnitSource(unitType: string) { + const unit = units.find(u => u.type === unitType); + if (unit === undefined) { + throw new Error(`Unknown unit type: "${unitType}"`); + } + return unit; +} + + export function getKeysAsNumbers(dict: Object): Array { return Object.keys(dict).map(i => parseInt(i)).sort((a, b) => a - b); } @@ -89,7 +99,7 @@ export function shuffle(array: Array): Array { } -export function enqueueBuilding(V: VillageState, building: Building) { +export function enqueueBuilding(V: VillageState, building: BuildingType) { const ongoingUpgrades = V.queue.filter(q => q.id === building.id); const level = building.level + 1 + ongoingUpgrades.length; const remainingTime = 1000 * level; diff --git a/src/village.ts b/src/village.ts index 069b080..95b7c63 100644 --- a/src/village.ts +++ b/src/village.ts @@ -1,9 +1,8 @@ import { writable } from "svelte/store"; -import buildings from "./buildings"; import { createBuilding } from "./create"; -import type { Building } from "./types"; import { getTilesAtDistance, Hex } from "./hexgrid"; +import type { BuildingType } from "./types"; import { getKeysAsNumbers, shuffle } from "./utils"; @@ -21,7 +20,10 @@ interface QueuedBuilding { export interface VillageState { - buildings: Building[]; + buildings: BuildingType[]; + units: { + [key: string]: number; + }, resources: { wood: number; stone: number; @@ -76,6 +78,7 @@ function getInitialOutsideBoard() { function getInitialState() { const state: VillageState = { buildings: [], + units: {}, resources: { wood: 60, stone: 60, @@ -112,6 +115,7 @@ function getInitialState() { } const newBuilding = createBuilding(type); newBuilding.tile = new Hex(x, y); + newBuilding.level = 10; state.outsideTiles[y][x] = newBuilding.id; state.buildings.push(newBuilding); });