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": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"immer": "^10.1.1",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-check": "^4.0.4",
|
"svelte-check": "^4.0.4",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.7.0",
|
||||||
@ -877,6 +878,16 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"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": {
|
"node_modules/is-reference": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"immer": "^10.1.1",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^4.2.19",
|
||||||
"svelte-check": "^4.0.4",
|
"svelte-check": "^4.0.4",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.7.0",
|
||||||
|
@ -1,8 +1,58 @@
|
|||||||
<script lang="ts">
|
<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>
|
</script>
|
||||||
|
|
||||||
<main>
|
<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>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.buildings {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: hsla(0, 0%, 100%, 0.87);
|
||||||
background-color: #242424;
|
background-color: hsl(0, 0%, 14%);
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
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.css'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
|
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById('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