Compare commits

...

2 Commits

12 changed files with 282 additions and 43 deletions

View File

@ -1,9 +1,11 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import Resources from "./hud/Resources.svelte";
import moves from "./moves"; import moves from "./moves";
import update from "./update"; import update from "./update";
import village from "./village"; import village from "./village";
import BuildingCreator from "./hud/BuildingCreator.svelte";
onMount(() => { onMount(() => {
@ -24,22 +26,24 @@
function upgradeBuilding(id: number) { function upgradeBuilding(id: number) {
moves.upgradeBuilding(id); moves.upgradeBuilding(id);
} }
let showBuildingCreator = false;
</script> </script>
{ #if showBuildingCreator }
<BuildingCreator close={ () => { showBuildingCreator = false } } />
{ /if }
<main> <main>
<header>
<Resources />
</header>
<div> <div>
<ul> <button on:click={ () => { showBuildingCreator = true } }>Create building</button>
<li>Wood: { Math.floor($village.resources.wood) }</li>
<li>Stone: { Math.floor($village.resources.stone) }</li>
<li>Iron: { Math.floor($village.resources.iron) }</li>
<li>Food: { Math.floor($village.resources.food) }</li>
</ul>
</div> </div>
<div class="buildings"> <div class="buildings">
{ #each $village.buildings as building } { #each $village.buildings as building }
<div> <div>
<p>{ building.name }</p> <p>{ building.name } ({ building.level })</p>
<p>Level: { building.level }</p>
<p> <p>
<button on:click={ () => upgradeBuilding(building.id) }> <button on:click={ () => upgradeBuilding(building.id) }>
Upgrade Upgrade
@ -52,7 +56,8 @@
<style> <style>
.buildings { .buildings {
display: flex; display: grid;
gap: 1em; gap: 1em;
grid-template-columns: repeat(4, 1fr);
} }
</style> </style>

View File

@ -24,9 +24,6 @@ a:hover {
body { body {
margin: 0; margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh; min-height: 100vh;
} }

View File

@ -1,10 +1,11 @@
import type { Building } from "./types";
import { getEmptyResources } from "./utils";
import type { VillageState } from "./village"; import type { VillageState } from "./village";
export default { export default {
'woodcutter': { 'woodcutter': {
name: 'Woodcutter', name: 'Woodcutter',
level: 1,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: level * 10, wood: level * 10,
@ -14,16 +15,16 @@ export default {
}; };
}, },
behavior: { behavior: {
production: (V: VillageState, self: any, delta: number) => { production: (V: VillageState, self: Building) => {
const prod = getEmptyResources();
const outputPerMinute = 5 * (self.level * self.level); const outputPerMinute = 5 * (self.level * self.level);
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0; prod.wood = outputPerMinute;
V.resources.wood += outputPerMilisecond * delta; return prod;
} }
} }
}, },
'mine': { 'mine': {
name: 'Mine', name: 'Mine',
level: 1,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: level * 10, wood: level * 10,
@ -33,16 +34,16 @@ export default {
}; };
}, },
behavior: { behavior: {
production: (V: VillageState, self: any, delta: number) => { production: (V: VillageState, self: Building) => {
const prod = getEmptyResources();
const outputPerMinute = 5 * (self.level * self.level); const outputPerMinute = 5 * (self.level * self.level);
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0; prod.iron = outputPerMinute;
V.resources.iron += outputPerMilisecond * delta; return prod;
} }
} }
}, },
'pit': { 'pit': {
name: 'Pit', name: 'Pit',
level: 1,
cost: (level: number) => { cost: (level: number) => {
return { return {
wood: level * 10, wood: level * 10,
@ -52,11 +53,58 @@ export default {
}; };
}, },
behavior: { behavior: {
production: (V: VillageState, self: any, delta: number) => { production: (V: VillageState, self: Building) => {
const prod = getEmptyResources();
const outputPerMinute = 5 * (self.level * self.level); const outputPerMinute = 5 * (self.level * self.level);
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0; prod.stone = outputPerMinute;
V.resources.stone += outputPerMilisecond * delta; return prod;
} }
} }
}, },
'warehouse': {
name: 'Warehouse',
cost: (level: number) => {
return {
wood: level * 10,
stone: level * 10,
iron: level * 10,
food: 0,
};
},
behavior: {
storage: (V: VillageState, self: Building) => {
const x = self.level;
const capacity = ( ( ( x + ( x * x ) ) / 2 ) + 3 ) * 25;
return {
'wood': capacity,
'stone': capacity,
'iron': capacity,
'food': 0,
}
}
},
},
'granary': {
name: 'Granary',
cost: (level: number) => {
return {
wood: level * 10,
stone: level * 10,
iron: level * 10,
food: 0,
};
},
behavior: {
storage: (V: VillageState, self: Building) => {
const x = self.level;
const capacity = ( ( ( x + ( x * x ) ) / 2 ) + 3 ) * 25;
return {
'wood': 0,
'stone': 0,
'iron': 0,
'food': capacity,
}
}
},
},
}; };

View File

@ -0,0 +1,69 @@
<script lang="ts">
import buildings from "../buildings";
import moves from "../moves";
export let close: () => void;
function build(type: string) {
if (moves.createBuilding(type)) {
close();
}
}
</script>
<section>
<div class="building-creator">
<header>
<h1>Build</h1>
<span class="close">
<button on:click={ close }>X</button>
</span>
</header>
<div class="buildings">
{ #each Object.entries(buildings) as [type, building] }
<div>
<p>{ building.name }</p>
<button on:click={ () => build(type) }>Build</button>
</div>
{ /each }
</div>
</div>
</section>
<style>
section {
background-color: hsl(0, 0%, 10%, 0.8);
display: grid;
place-items: center;
height: 100vh;
left: 0;
position: absolute;
top: 0;
width: 100vw;
}
.building-creator {
background-color: hsl(0, 0%, 20%);
border: 0.2em solid grey;
border-radius: .4em;
width: 80%;
height: 60%;
}
.building-creator header {
position: relative;
}
.building-creator header .close {
position: absolute;
right: 1em;
top: 0;
}
.building-creator .buildings {
display: grid;
gap: 1em;
grid-template-columns: repeat(4, 1fr);
}
</style>

21
src/hud/Resources.svelte Normal file
View File

@ -0,0 +1,21 @@
<script lang="ts">
import { getStorage } from "../utils";
import village from "../village";
$: capacity = getStorage($village);
</script>
<div class="resources">
<div>Wood: { Math.floor($village.resources.wood) } / { capacity.wood }</div>
<div>Stone: { Math.floor($village.resources.stone) } / { capacity.stone }</div>
<div>Iron: { Math.floor($village.resources.iron) } / { capacity.iron }</div>
<div>Food: { Math.floor($village.resources.food) } / { capacity.food }</div>
</div>
<style>
.resources {
display: flex;
justify-content: space-around;
}
</style>

View File

@ -0,0 +1,34 @@
import buildings from "../buildings";
import type { VillageState } from "../village";
let uid = 0;
export default function createBuilding(V: VillageState, buildingType: keyof typeof buildings) {
const building = buildings[buildingType];
const cost = building.cost(1);
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 newBuilding = {
...building,
level: 1,
id: uid++,
};
V.buildings.push(newBuilding);
return true;
}

View File

@ -1,6 +1,7 @@
import { produce } from 'immer'; import { produce } from 'immer';
import village, { type VillageState } from '../village'; import village, { type VillageState } from '../village';
import createBuilding from './createBuilding';
import upgradeBuilding from './upgradeBuilding'; import upgradeBuilding from './upgradeBuilding';
@ -16,8 +17,8 @@ export function makeMove(move: (...args: any[]) => boolean) {
// That updates the game state. // That updates the game state.
village.update(state => { village.update(state => {
// With an immutable state. // With an immutable state.
return produce(state, (G: VillageState) => { return produce(state, (V: VillageState) => {
result = move(G, ...args); result = move(V, ...args);
}); });
}); });
@ -27,5 +28,6 @@ export function makeMove(move: (...args: any[]) => boolean) {
export default { export default {
createBuilding: makeMove(createBuilding),
upgradeBuilding: makeMove(upgradeBuilding), upgradeBuilding: makeMove(upgradeBuilding),
}; };

View File

@ -7,7 +7,7 @@ export default function upgradeBuilding(V: VillageState, buildingId: number) {
return false; return false;
} }
const cost = building.cost(building.level); const cost = building.cost(building.level + 1);
if ( if (
cost.wood > V.resources.wood cost.wood > V.resources.wood

View File

@ -6,12 +6,16 @@ export interface Cost {
} }
export type Production = Cost;
export interface BuildingSource { export interface BuildingSource {
name: string; name: string;
level: number; level: number;
cost: (level: number) => Cost; cost: (level: number) => Cost;
behavior: { behavior: {
production?: Function; production?: Function;
storage?: Function;
}; };
} }

View File

@ -1,6 +1,8 @@
import { produce } from 'immer'; import { produce } from 'immer';
import { getProduction, getStorage } from './utils';
import village, { type VillageState } from "./village"; import village, { type VillageState } from "./village";
import type { Production } from './types';
let lastFrame: number; let lastFrame: number;
@ -16,11 +18,20 @@ export default function update(timestamp: number) {
village.update(state => { village.update(state => {
return produce(state, (V: VillageState) => { return produce(state, (V: VillageState) => {
V.buildings.forEach(b => { const productionPerMinute = getProduction(V);
if (b.behavior.production) { const storage = getStorage(V);
b.behavior.production(V, b, delta);
Object.keys(productionPerMinute).forEach((key) => {
const resource = key as keyof Production;
const outputPerMinute = productionPerMinute[resource];
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0;
V.resources[resource] += outputPerMilisecond * delta;
if (V.resources[resource] > storage[resource]) {
V.resources[resource] = storage[resource];
} }
}); });
return V; return V;
}); });
}); });

46
src/utils.ts Normal file
View File

@ -0,0 +1,46 @@
import type { Production } from "./types";
import type { VillageState } from "./village";
function _reduceResources(acc: Production, item: Production): Production {
return {
wood: acc.wood + item.wood,
stone: acc.stone + item.stone,
iron: acc.iron + item.iron,
food: acc.food + item.food,
};
}
export function getEmptyResources(): Production {
return {
wood: 0,
stone: 0,
iron: 0,
food: 0,
};
}
export function getProduction(villageState: VillageState): Production {
return villageState.buildings
.filter(b => b.behavior.production)
.map(b => {
if (b.behavior.production) {
return b.behavior.production(villageState, b);
}
})
.reduce(_reduceResources, getEmptyResources());
}
export function getStorage(villageState: VillageState): Production {
return villageState.buildings
.filter(b => b.behavior.storage)
.map(b => {
if (b.behavior.storage) {
return b.behavior.storage(villageState, b);
}
})
.reduce(_reduceResources, getEmptyResources());
}

View File

@ -11,25 +11,27 @@ export interface VillageState {
iron: number; iron: number;
food: number; food: number;
culture: number; culture: number;
} };
} }
let uid = 0; let uid = 0;
const village = writable<VillageState>({ const village = writable<VillageState>({
buildings: [ buildings: [
{ ...buildings.woodcutter, id: uid++ }, { ...buildings.woodcutter, level: 1, id: uid++ },
{ ...buildings.woodcutter, id: uid++ }, { ...buildings.woodcutter, level: 1, id: uid++ },
{ ...buildings.woodcutter, id: uid++ }, { ...buildings.woodcutter, level: 1, id: uid++ },
{ ...buildings.woodcutter, id: uid++ }, { ...buildings.woodcutter, level: 1, id: uid++ },
{ ...buildings.mine, id: uid++ }, { ...buildings.mine, level: 1, id: uid++ },
{ ...buildings.mine, id: uid++ }, { ...buildings.mine, level: 1, id: uid++ },
{ ...buildings.mine, id: uid++ }, { ...buildings.mine, level: 1, id: uid++ },
{ ...buildings.mine, id: uid++ }, { ...buildings.mine, level: 1, id: uid++ },
{ ...buildings.pit, id: uid++ }, { ...buildings.pit, level: 1, id: uid++ },
{ ...buildings.pit, id: uid++ }, { ...buildings.pit, level: 1, id: uid++ },
{ ...buildings.pit, id: uid++ }, { ...buildings.pit, level: 1, id: uid++ },
{ ...buildings.pit, id: uid++ }, { ...buildings.pit, level: 1, id: uid++ },
{ ...buildings.warehouse, level: 1, id: uid++ },
{ ...buildings.granary, level: 1, id: uid++ },
], ],
resources: { resources: {
wood: 100, wood: 100,