Add resource production buildings and a way to upgrade them.
This commit is contained in:
parent
d5058c4cbe
commit
b564520d56
11
package-lock.json
generated
11
package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"immer": "^10.1.1",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^4.0.4",
|
||||
"tslib": "^2.7.0",
|
||||
@ -877,6 +878,16 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"immer": "^10.1.1",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^4.0.4",
|
||||
"tslib": "^2.7.0",
|
||||
|
@ -1,8 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import moves from "./moves";
|
||||
import update from "./update";
|
||||
import village from "./village";
|
||||
|
||||
|
||||
onMount(() => {
|
||||
let frame: number;
|
||||
|
||||
function loop(timestamp: number) {
|
||||
frame = requestAnimationFrame(loop);
|
||||
update(timestamp);
|
||||
}
|
||||
frame = requestAnimationFrame(loop);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function upgradeBuilding(id: number) {
|
||||
moves.upgradeBuilding(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<ul>
|
||||
<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 class="buildings">
|
||||
{ #each $village.buildings as building }
|
||||
<div>
|
||||
<p>{ building.name }</p>
|
||||
<p>Level: { building.level }</p>
|
||||
<p>
|
||||
<button on:click={ () => upgradeBuilding(building.id) }>
|
||||
Upgrade
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
{ /each }
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.buildings {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
||||
|
@ -4,8 +4,8 @@
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
color: hsla(0, 0%, 100%, 0.87);
|
||||
background-color: hsl(0, 0%, 14%);
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
62
src/buildings.ts
Normal file
62
src/buildings.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { VillageState } from "./village";
|
||||
|
||||
|
||||
export default {
|
||||
'woodcutter': {
|
||||
name: 'Woodcutter',
|
||||
level: 1,
|
||||
cost: (level: number) => {
|
||||
return {
|
||||
wood: level * 10,
|
||||
stone: level * 10,
|
||||
iron: level * 10,
|
||||
food: 0,
|
||||
};
|
||||
},
|
||||
behavior: {
|
||||
production: (V: VillageState, self: any, delta: number) => {
|
||||
const outputPerMinute = 5 * (self.level * self.level);
|
||||
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0;
|
||||
V.resources.wood += outputPerMilisecond * delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
'mine': {
|
||||
name: 'Mine',
|
||||
level: 1,
|
||||
cost: (level: number) => {
|
||||
return {
|
||||
wood: level * 10,
|
||||
stone: level * 10,
|
||||
iron: level * 10,
|
||||
food: 0,
|
||||
};
|
||||
},
|
||||
behavior: {
|
||||
production: (V: VillageState, self: any, delta: number) => {
|
||||
const outputPerMinute = 5 * (self.level * self.level);
|
||||
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0;
|
||||
V.resources.iron += outputPerMilisecond * delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
'pit': {
|
||||
name: 'Pit',
|
||||
level: 1,
|
||||
cost: (level: number) => {
|
||||
return {
|
||||
wood: level * 10,
|
||||
stone: level * 10,
|
||||
iron: level * 10,
|
||||
food: 0,
|
||||
};
|
||||
},
|
||||
behavior: {
|
||||
production: (V: VillageState, self: any, delta: number) => {
|
||||
const outputPerMinute = 5 * (self.level * self.level);
|
||||
const outputPerMilisecond = outputPerMinute / 60.0 / 1000.0;
|
||||
V.resources.stone += outputPerMilisecond * delta;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
@ -1,8 +1,10 @@
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
});
|
||||
|
||||
export default app
|
||||
|
||||
export default app;
|
||||
|
31
src/moves/index.ts
Normal file
31
src/moves/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import village, { type VillageState } from '../village';
|
||||
import upgradeBuilding from './upgradeBuilding';
|
||||
|
||||
|
||||
// Encapsulates a move function into a store update, where the data is made
|
||||
// immutable by immer. This allows moves to be much, much simpler to write,
|
||||
// while ensuring that states are always immutable. We'll benefit from this
|
||||
// when we want to implement an undo option.
|
||||
export function makeMove(move: (...args: any[]) => boolean) {
|
||||
// Create a new function.
|
||||
return (...args: any[]) => {
|
||||
let result: boolean = false;
|
||||
|
||||
// That updates the game state.
|
||||
village.update(state => {
|
||||
// With an immutable state.
|
||||
return produce(state, (G: VillageState) => {
|
||||
result = move(G, ...args);
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
upgradeBuilding: makeMove(upgradeBuilding),
|
||||
};
|
29
src/moves/upgradeBuilding.ts
Normal file
29
src/moves/upgradeBuilding.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { VillageState } from "../village";
|
||||
|
||||
|
||||
export default function upgradeBuilding(V: VillageState, buildingId: number) {
|
||||
const building = V.buildings.find(b => b.id === buildingId);
|
||||
if (!building) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cost = building.cost(building.level);
|
||||
|
||||
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;
|
||||
|
||||
building.level++;
|
||||
|
||||
return true;
|
||||
}
|
21
src/types.ts
Normal file
21
src/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export interface Cost {
|
||||
wood: number;
|
||||
stone: number;
|
||||
iron: number;
|
||||
food: number;
|
||||
}
|
||||
|
||||
|
||||
export interface BuildingSource {
|
||||
name: string;
|
||||
level: number;
|
||||
cost: (level: number) => Cost;
|
||||
behavior: {
|
||||
production?: Function;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface Building extends BuildingSource {
|
||||
id: number;
|
||||
}
|
29
src/update.ts
Normal file
29
src/update.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { produce } from 'immer';
|
||||
|
||||
import village, { type VillageState } from "./village";
|
||||
|
||||
|
||||
let lastFrame: number;
|
||||
|
||||
|
||||
export default function update(timestamp: number) {
|
||||
if (!lastFrame) {
|
||||
lastFrame = timestamp;
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = timestamp - lastFrame;
|
||||
|
||||
village.update(state => {
|
||||
return produce(state, (V: VillageState) => {
|
||||
V.buildings.forEach(b => {
|
||||
if (b.behavior.production) {
|
||||
b.behavior.production(V, b, delta);
|
||||
}
|
||||
});
|
||||
return V;
|
||||
});
|
||||
});
|
||||
|
||||
lastFrame = timestamp;
|
||||
}
|
44
src/village.ts
Normal file
44
src/village.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { writable } from "svelte/store";
|
||||
import buildings from "./buildings";
|
||||
import type { Building } from "./types";
|
||||
|
||||
|
||||
export interface VillageState {
|
||||
buildings: Building[];
|
||||
resources: {
|
||||
wood: number;
|
||||
stone: number;
|
||||
iron: number;
|
||||
food: number;
|
||||
culture: number;
|
||||
}
|
||||
}
|
||||
|
||||
let uid = 0;
|
||||
|
||||
const village = writable<VillageState>({
|
||||
buildings: [
|
||||
{ ...buildings.woodcutter, id: uid++ },
|
||||
{ ...buildings.woodcutter, id: uid++ },
|
||||
{ ...buildings.woodcutter, id: uid++ },
|
||||
{ ...buildings.woodcutter, id: uid++ },
|
||||
{ ...buildings.mine, id: uid++ },
|
||||
{ ...buildings.mine, id: uid++ },
|
||||
{ ...buildings.mine, id: uid++ },
|
||||
{ ...buildings.mine, id: uid++ },
|
||||
{ ...buildings.pit, id: uid++ },
|
||||
{ ...buildings.pit, id: uid++ },
|
||||
{ ...buildings.pit, id: uid++ },
|
||||
{ ...buildings.pit, id: uid++ },
|
||||
],
|
||||
resources: {
|
||||
wood: 100,
|
||||
stone: 100,
|
||||
iron: 100,
|
||||
food: 0,
|
||||
culture: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default village;
|
Loading…
Reference in New Issue
Block a user