Add a first unit, Philosophers, that you can recruit, but they do nothing for now.
This commit is contained in:
parent
fe34ffee8d
commit
cd872d76e8
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Building } from "../types";
|
||||
import type { BuildingType } from "../types";
|
||||
|
||||
export let building: Building;
|
||||
export let building: BuildingType;
|
||||
</script>
|
||||
|
||||
<p>{ building.name }</p>
|
||||
|
@ -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: {},
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
];
|
16
src/data/units.ts
Normal file
16
src/data/units.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export default [
|
||||
{
|
||||
type: 'philosopher',
|
||||
name: 'Philosopher',
|
||||
cost: {
|
||||
wood: 20,
|
||||
stone: 50,
|
||||
iron: 0,
|
||||
food: 0,
|
||||
},
|
||||
behavior: {
|
||||
culturePerMinute: 1,
|
||||
foodIntakePerMinute: 2,
|
||||
},
|
||||
},
|
||||
];
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import buildings from "../buildings";
|
||||
import moves from "../moves";
|
||||
import showBuildingPanel from "../stores/showBuildingPanel";
|
||||
import { getBuilding } from "../utils";
|
||||
import village from "../village";
|
||||
import UniversityPanel from "./UniversityPanel.svelte";
|
||||
|
||||
function close() {
|
||||
showBuildingPanel.set(null);
|
||||
@ -32,7 +32,14 @@
|
||||
<button on:click={ close }>X</button>
|
||||
</span>
|
||||
</header>
|
||||
<div class="building">
|
||||
<div class="content">
|
||||
{ #if building.level === 0 }
|
||||
<p>Building in construction…</p>
|
||||
{ :else if building.type === 'university' }
|
||||
<UniversityPanel { building } />
|
||||
{ /if }
|
||||
</div>
|
||||
<div class="upgrade">
|
||||
<div>
|
||||
<p>{ building.name } ({ building.level })</p>
|
||||
<button on:click={ () => upgrade() }>Upgrade</button>
|
||||
|
39
src/hud/Cost.svelte
Normal file
39
src/hud/Cost.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import type { CostType } from "../types";
|
||||
|
||||
export let cost: CostType;
|
||||
</script>
|
||||
|
||||
<div class="cost">
|
||||
<div>
|
||||
<img src="/img/icons/wood.png" alt="Wood" />
|
||||
{ cost.wood }
|
||||
</div>
|
||||
<div>
|
||||
<img src="/img/icons/stone.png" alt="Stone" />
|
||||
{ cost.stone }
|
||||
</div>
|
||||
<div>
|
||||
<img src="/img/icons/iron.png" alt="Iron" />
|
||||
{ cost.iron }
|
||||
</div>
|
||||
<div>
|
||||
<img src="/img/icons/food.png" alt="Food" />
|
||||
{ cost.food }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.cost {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cost div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2em;
|
||||
}
|
||||
</style>
|
@ -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 @@
|
||||
|
||||
<section class="hud">
|
||||
<header>
|
||||
<Units />
|
||||
<Resources />
|
||||
<Navigation { setTab } />
|
||||
</header>
|
||||
|
18
src/hud/Units.svelte
Normal file
18
src/hud/Units.svelte
Normal file
@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import units from "../data/units";
|
||||
import village from "../village";
|
||||
|
||||
$: currentUnits = Object.entries($village.units).map(([type, count]) => {
|
||||
const unit = units.find(u => u.type === type);
|
||||
return {
|
||||
...unit,
|
||||
count,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<section>
|
||||
{ #each currentUnits as unit }
|
||||
<p>{ unit.name }: { unit.count }</p>
|
||||
{ /each }
|
||||
</section>
|
72
src/hud/UniversityPanel.svelte
Normal file
72
src/hud/UniversityPanel.svelte
Normal file
@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import moves from "../moves";
|
||||
import type { BuildingType, CostType, UnitType } from "../types";
|
||||
import { getUnitSource } from "../utils";
|
||||
import type { VillageState } from "../village";
|
||||
import village from "../village";
|
||||
import Cost from "./Cost.svelte";
|
||||
|
||||
export let building: BuildingType;
|
||||
|
||||
|
||||
let numberOfUnits = 1;
|
||||
$: unitType = building.behavior.units?.type || '';
|
||||
$: unit = getUnitSource(unitType);
|
||||
$: maximumUnits = getMaxCountOfUnits($village, unit);
|
||||
$: if (numberOfUnits > maximumUnits) {
|
||||
numberOfUnits = maximumUnits || 1;
|
||||
}
|
||||
$: cost = {
|
||||
wood: unit.cost.wood * numberOfUnits,
|
||||
stone: unit.cost.stone * numberOfUnits,
|
||||
iron: unit.cost.iron * numberOfUnits,
|
||||
food: unit.cost.food * numberOfUnits,
|
||||
};
|
||||
|
||||
|
||||
function recruit() {
|
||||
moves.recruitUnits(building.id, unitType, numberOfUnits);
|
||||
}
|
||||
|
||||
function getMaxCountOfUnits(V: VillageState, unitData: UnitType): number {
|
||||
let res = Infinity;
|
||||
Object.entries(V.resources).forEach(([resource, value]) => {
|
||||
const cost = unitData.cost[resource as keyof CostType] || 0;
|
||||
if (cost > 0) {
|
||||
res = Math.min(
|
||||
res,
|
||||
Math.floor(value / cost)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="university">
|
||||
<p>Create Philosophers</p>
|
||||
<div class="cost"><Cost { cost } /></div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
name="units"
|
||||
min="1"
|
||||
max={ maximumUnits }
|
||||
bind:value={ numberOfUnits }
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
name="units"
|
||||
min="1"
|
||||
max={ maximumUnits }
|
||||
bind:value={ numberOfUnits }
|
||||
/>
|
||||
<button on:click={ recruit } disabled={ numberOfUnits > maximumUnits }>Recruit</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.university .cost {
|
||||
margin: 1em;
|
||||
}
|
||||
</style>
|
@ -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";
|
||||
|
@ -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),
|
||||
};
|
||||
|
46
src/moves/recruitUnits.ts
Normal file
46
src/moves/recruitUnits.ts
Normal file
@ -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;
|
||||
}
|
29
src/types.ts
29
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
24
src/utils.ts
24
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<number> {
|
||||
return Object.keys(dict).map(i => parseInt(i)).sort((a, b) => a - b);
|
||||
}
|
||||
@ -89,7 +99,7 @@ export function shuffle<T>(array: Array<T>): Array<T> {
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user