Transform the world map into an actual map with regions on it.
This commit is contained in:
parent
83adff2603
commit
04b7ab7339
@ -1,74 +0,0 @@
|
||||
<script lang="ts">
|
||||
import moves from "../moves";
|
||||
import type { OasisType } from "../types";
|
||||
import { getRemainingUnitCount } from "../utils";
|
||||
import village from "../village";
|
||||
|
||||
export let region: OasisType;
|
||||
|
||||
|
||||
let numberOfUnits = 0;
|
||||
let repeatMission = false;
|
||||
$: maximumUnits = getRemainingUnitCount($village, 'soldier');
|
||||
$: if (numberOfUnits > maximumUnits) {
|
||||
numberOfUnits = maximumUnits || 0;
|
||||
}
|
||||
|
||||
|
||||
function setMaxUnits() {
|
||||
numberOfUnits = maximumUnits;
|
||||
}
|
||||
|
||||
|
||||
function pillage() {
|
||||
moves.pillage(region.state.index, numberOfUnits, repeatMission);
|
||||
}
|
||||
|
||||
|
||||
function toggleMissionRepeat() {
|
||||
moves.toggleMissionRepeat(region.state.index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h2>
|
||||
<img src="/img/icons/{ region.resource }.png" alt="{ region.resource }">
|
||||
Oasis
|
||||
(→ { region.distance })
|
||||
</h2>
|
||||
{ #if region.state.mission }
|
||||
<div>
|
||||
<p>{ region.state.mission.unitCount } soldiers are on a mission here.</p>
|
||||
<p>Remaining: { Math.ceil(region.state.mission.remainingTime / 1000) }</p>
|
||||
<label>
|
||||
<input type="checkbox" checked={ region.state.mission.repeat } on:change={ toggleMissionRepeat }>
|
||||
<span title="Send the same troops again when they come back">Repeat</span>
|
||||
</label>
|
||||
</div>
|
||||
{ :else }
|
||||
<div>
|
||||
<input
|
||||
type="range"
|
||||
name="units"
|
||||
min="0"
|
||||
max={ maximumUnits }
|
||||
bind:value={ numberOfUnits }
|
||||
/>
|
||||
{ numberOfUnits }
|
||||
<button on:click={ setMaxUnits }>↑</button>
|
||||
<button on:click={ pillage } disabled={ numberOfUnits === 0 }>Pillage</button>
|
||||
<label>
|
||||
<input type="checkbox" bind:value={ repeatMission }>
|
||||
<span title="Send the same troops again when they come back">Repeat</span>
|
||||
</label>
|
||||
</div>
|
||||
{ /if }
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h2 {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.4em;
|
||||
}
|
||||
</style>
|
112
src/board/OasisRegionPanel.svelte
Normal file
112
src/board/OasisRegionPanel.svelte
Normal file
@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
import moves from "../moves";
|
||||
import showOasisPanel from "../stores/showOasisPanel";
|
||||
import type { OasisType } from "../types";
|
||||
import { getRemainingUnitCount } from "../utils";
|
||||
import village from "../village";
|
||||
|
||||
|
||||
$: region = ($showOasisPanel !== null) ? $village.worldmap[$showOasisPanel] as OasisType : null;
|
||||
let numberOfUnits = 0;
|
||||
let repeatMission = false;
|
||||
$: maximumUnits = getRemainingUnitCount($village, 'soldier');
|
||||
$: if (numberOfUnits > maximumUnits) {
|
||||
numberOfUnits = maximumUnits || 0;
|
||||
}
|
||||
|
||||
|
||||
function setMaxUnits() {
|
||||
numberOfUnits = maximumUnits;
|
||||
}
|
||||
|
||||
|
||||
function pillage() {
|
||||
if (!region) return;
|
||||
moves.pillage(region.id, numberOfUnits, repeatMission);
|
||||
}
|
||||
|
||||
|
||||
function toggleMissionRepeat() {
|
||||
if (!region) return;
|
||||
moves.toggleMissionRepeat(region.id);
|
||||
}
|
||||
|
||||
|
||||
function close() {
|
||||
showOasisPanel.set(null);
|
||||
}
|
||||
</script>
|
||||
|
||||
{ #if region !== null }
|
||||
<section>
|
||||
<div class="oasis-panel">
|
||||
<header>
|
||||
<h1>
|
||||
<img src="/img/icons/{ region.resource }.png" alt="{ region.resource }">
|
||||
Oasis
|
||||
</h1>
|
||||
<span class="close">
|
||||
<button on:click={ close }>X</button>
|
||||
</span>
|
||||
</header>
|
||||
{ #if region.state.mission }
|
||||
<div>
|
||||
<p>{ region.state.mission.unitCount } soldiers are on a mission here.</p>
|
||||
<p>Remaining: { Math.ceil(region.state.mission.remainingTime / 1000) }</p>
|
||||
<label>
|
||||
<input type="checkbox" checked={ region.state.mission.repeat } on:change={ toggleMissionRepeat }>
|
||||
<span title="Send the same troops again when they come back">Repeat</span>
|
||||
</label>
|
||||
</div>
|
||||
{ :else }
|
||||
<div>
|
||||
<input
|
||||
type="range"
|
||||
name="units"
|
||||
min="0"
|
||||
max={ maximumUnits }
|
||||
bind:value={ numberOfUnits }
|
||||
/>
|
||||
{ numberOfUnits }
|
||||
<button on:click={ setMaxUnits }>↑</button>
|
||||
<button on:click={ pillage } disabled={ numberOfUnits === 0 }>Pillage</button>
|
||||
<label>
|
||||
<input type="checkbox" bind:value={ repeatMission }>
|
||||
<span title="Send the same troops again when they come back">Repeat</span>
|
||||
</label>
|
||||
</div>
|
||||
{ /if }
|
||||
</div>
|
||||
</section>
|
||||
{ /if }
|
||||
|
||||
<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;
|
||||
}
|
||||
|
||||
.oasis-panel {
|
||||
background-color: hsl(0, 0%, 20%);
|
||||
border: 0.2em solid grey;
|
||||
border-radius: .4em;
|
||||
width: 80%;
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
.oasis-panel header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oasis-panel header .close {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
@ -1,14 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { WORLD_MAP_HEIGHT, WORLD_MAP_WIDTH } from "../constants";
|
||||
import gameTab from "../stores/gameTab";
|
||||
import showOasisPanel from "../stores/showOasisPanel";
|
||||
import { WORLDMAP_TYPES } from "../types";
|
||||
import village from "../village";
|
||||
import OasisRegion from "./OasisRegion.svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<section class="worldmap">
|
||||
<section
|
||||
class="worldmap"
|
||||
style="--map-height: {WORLD_MAP_HEIGHT}; --map-width: {WORLD_MAP_WIDTH};"
|
||||
>
|
||||
{ #each $village.worldmap as region }
|
||||
<div class="region">
|
||||
{ #if region.type === 'oasis' }
|
||||
<OasisRegion { region } />
|
||||
<div
|
||||
class="region"
|
||||
class:empty={ region.type === WORLDMAP_TYPES.EMPTY }
|
||||
class:bourgade={ region.type === WORLDMAP_TYPES.BOURGADE }
|
||||
class:oasis={ region.type === WORLDMAP_TYPES.OASIS }
|
||||
>
|
||||
{ #if region.type === WORLDMAP_TYPES.BOURGADE }
|
||||
<button class="invisible" on:click={ () => gameTab.set('resources') }>
|
||||
<img src="/img/icons/field.svg" alt="">
|
||||
</button>
|
||||
{ :else if region.type === WORLDMAP_TYPES.OASIS }
|
||||
<button class="invisible" on:click={ () => showOasisPanel.set(region.id) }>
|
||||
<img src="/img/icons/{region.resource}.png" alt="">
|
||||
</button>
|
||||
{ :else }
|
||||
{ region.id }
|
||||
{ /if }
|
||||
</div>
|
||||
{ /each }
|
||||
@ -16,15 +34,35 @@
|
||||
|
||||
<style>
|
||||
.worldmap {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 1em;
|
||||
aspect-ratio: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--map-width), 1fr);
|
||||
grid-template-rows: repeat(var(--map-height), 1fr);
|
||||
height: 80vh;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.region {
|
||||
border: 1px solid white;
|
||||
padding: 1em;
|
||||
width: 40%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.region > button {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.region.bourgade {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.region.empty {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.region.oasis {
|
||||
background-color: cyan;
|
||||
}
|
||||
</style>
|
||||
|
@ -1 +1,3 @@
|
||||
export const CULTURE_TO_WIN = 20000;
|
||||
export const WORLD_MAP_HEIGHT = 9;
|
||||
export const WORLD_MAP_WIDTH = 9;
|
||||
|
@ -6,11 +6,11 @@
|
||||
import BuildingRecruitment from "./BuildingRecruitment.svelte";
|
||||
import Cost from "./Cost.svelte";
|
||||
|
||||
|
||||
function close() {
|
||||
showBuildingPanel.set(null);
|
||||
}
|
||||
|
||||
|
||||
function upgrade() {
|
||||
if ($showBuildingPanel === null) {
|
||||
return;
|
||||
|
@ -14,6 +14,7 @@
|
||||
import Units from "./Units.svelte";
|
||||
import Victory from "./Victory.svelte";
|
||||
import Quests from "./Quests.svelte";
|
||||
import OasisRegionPanel from "../board/OasisRegionPanel.svelte";
|
||||
|
||||
|
||||
onMount(() => {
|
||||
@ -57,6 +58,7 @@
|
||||
<section class="overlay">
|
||||
<BuildingCreator />
|
||||
<BuildingPanel />
|
||||
<OasisRegionPanel />
|
||||
<Victory />
|
||||
</section>
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import type { OasisType, RegionType } from "./types";
|
||||
import { getUnitSource } from "./utils";
|
||||
import { WORLDMAP_TYPES, type OasisType, type RegionType } from "./types";
|
||||
import { assert, getUnitSource } from "./utils";
|
||||
import type { VillageState } from "./village";
|
||||
|
||||
|
||||
export function resolveMission(V: VillageState, region: RegionType) {
|
||||
assert(region.type === WORLDMAP_TYPES.OASIS);
|
||||
|
||||
const mission = region.state.mission;
|
||||
if (!mission) {
|
||||
return;
|
||||
@ -11,7 +13,7 @@ export function resolveMission(V: VillageState, region: RegionType) {
|
||||
|
||||
switch (mission.type) {
|
||||
case 'pillage':
|
||||
if (region.type === 'oasis') {
|
||||
if (region.type === WORLDMAP_TYPES.OASIS) {
|
||||
resolvePillageOasis(V, region);
|
||||
}
|
||||
break;
|
||||
@ -20,7 +22,7 @@ export function resolveMission(V: VillageState, region: RegionType) {
|
||||
}
|
||||
|
||||
if (mission.repeat) {
|
||||
mission.remainingTime = region.distance * 10 * 1000;
|
||||
mission.remainingTime = 1 * 10 * 1000;
|
||||
}
|
||||
else {
|
||||
delete region.state.mission;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getRemainingUnitCount } from "../utils";
|
||||
import { WORLDMAP_TYPES } from "../types";
|
||||
import { assert, getRemainingUnitCount } from "../utils";
|
||||
import type { VillageState } from "../village";
|
||||
|
||||
|
||||
@ -9,6 +10,8 @@ export default function pillage(V: VillageState, regionIndex: number, soldiersCo
|
||||
|
||||
const region = V.worldmap[regionIndex];
|
||||
|
||||
assert(region.type === WORLDMAP_TYPES.OASIS);
|
||||
|
||||
if (region.state.mission) {
|
||||
return false;
|
||||
}
|
||||
@ -17,7 +20,7 @@ export default function pillage(V: VillageState, regionIndex: number, soldiersCo
|
||||
type: 'pillage',
|
||||
unitType: 'soldier',
|
||||
unitCount: soldiersCount,
|
||||
remainingTime: region.distance * 10 * 1000,
|
||||
remainingTime: 1 * 10 * 1000,
|
||||
repeat,
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { WORLDMAP_TYPES } from "../types";
|
||||
import { assert } from "../utils";
|
||||
import type { VillageState } from "../village";
|
||||
|
||||
|
||||
export default function toggleMissionRepeat(V: VillageState, regionIndex: number) {
|
||||
const region = V.worldmap[regionIndex];
|
||||
|
||||
assert(region.type === WORLDMAP_TYPES.OASIS);
|
||||
|
||||
if (!region.state.mission) {
|
||||
return false;
|
||||
}
|
||||
|
4
src/stores/showOasisPanel.ts
Normal file
4
src/stores/showOasisPanel.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
|
||||
export default writable<number | null>(null);
|
34
src/types.ts
34
src/types.ts
@ -81,28 +81,40 @@ export interface MissionType {
|
||||
repeat: boolean;
|
||||
}
|
||||
|
||||
|
||||
export enum WORLDMAP_TYPES {
|
||||
EMPTY,
|
||||
OASIS,
|
||||
BOURGADE,
|
||||
}
|
||||
|
||||
|
||||
interface BaseRegionType {
|
||||
distance: number;
|
||||
id: number;
|
||||
type: WORLDMAP_TYPES;
|
||||
}
|
||||
|
||||
|
||||
export interface EmptyRegionType extends BaseRegionType {
|
||||
type: WORLDMAP_TYPES.EMPTY;
|
||||
}
|
||||
|
||||
|
||||
export interface OasisType extends BaseRegionType {
|
||||
type: WORLDMAP_TYPES.OASIS;
|
||||
resource: keyof CostType;
|
||||
state: {
|
||||
index: number;
|
||||
mission?: MissionType;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface OasisType extends BaseRegionType {
|
||||
type: 'oasis';
|
||||
resource: keyof CostType;
|
||||
}
|
||||
|
||||
|
||||
export interface BourgadeType extends BaseRegionType {
|
||||
type: 'bourgade';
|
||||
distance: number;
|
||||
type: WORLDMAP_TYPES.BOURGADE;
|
||||
}
|
||||
|
||||
|
||||
export type RegionType = OasisType | BourgadeType;
|
||||
export type RegionType = EmptyRegionType | OasisType | BourgadeType;
|
||||
|
||||
|
||||
export interface HeroType {
|
||||
|
@ -4,7 +4,7 @@ import { CULTURE_TO_WIN } from './constants';
|
||||
import { createQuest } from './create';
|
||||
import { resolveMission } from './missions';
|
||||
import type { ProductionType } from './types';
|
||||
import { getProduction, getStorage, shuffle } from './utils';
|
||||
import { getProduction, getRegionsWithMissions, getStorage, shuffle } from './utils';
|
||||
import village, { type VillageState } from "./village";
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ export default function update(timestamp: number) {
|
||||
});
|
||||
|
||||
// Advance missions.
|
||||
V.worldmap.forEach(region => {
|
||||
getRegionsWithMissions(V).forEach(region => {
|
||||
if (!region.state.mission) {
|
||||
return;
|
||||
}
|
||||
|
34
src/utils.ts
34
src/utils.ts
@ -1,5 +1,6 @@
|
||||
import { WORLD_MAP_HEIGHT, WORLD_MAP_WIDTH } from "./constants";
|
||||
import units from "./data/units";
|
||||
import type { BuildingType, CostType, MissionType, ProductionType, ResourcesType } from "./types";
|
||||
import { WORLDMAP_TYPES, type BuildingType, type CostType, type OasisType, type ProductionType, type ResourcesType } from "./types";
|
||||
import type { VillageState } from "./village";
|
||||
|
||||
|
||||
@ -88,9 +89,16 @@ export function getUnitSource(unitType: string) {
|
||||
}
|
||||
|
||||
|
||||
export function getRegionsWithMissions(V: VillageState): OasisType[] {
|
||||
return V.worldmap.filter(r => r.type === WORLDMAP_TYPES.OASIS);
|
||||
}
|
||||
|
||||
|
||||
export function getRemainingUnitCount(V: VillageState, unitType: string) {
|
||||
let total = V.units[unitType] || 0;
|
||||
const missions = V.worldmap.filter(r => r.state.mission?.unitType === unitType).map(r => r.state.mission);
|
||||
const missions = getRegionsWithMissions(V)
|
||||
.filter(r => r.state.mission?.unitType === unitType)
|
||||
.map(r => r.state.mission);
|
||||
missions.forEach(m => total -= m?.unitCount || 0);
|
||||
return total;
|
||||
}
|
||||
@ -170,3 +178,25 @@ export function getTownhall(V: VillageState): BuildingType {
|
||||
}
|
||||
return townhall;
|
||||
}
|
||||
|
||||
export function assert(condition: any, msg?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getAdjacentWorldmapCells(cellIndex: number) {
|
||||
const cells = [
|
||||
cellIndex - WORLD_MAP_WIDTH - 1,
|
||||
cellIndex - WORLD_MAP_WIDTH,
|
||||
cellIndex - WORLD_MAP_WIDTH + 1,
|
||||
cellIndex - 1,
|
||||
cellIndex + 1,
|
||||
cellIndex + WORLD_MAP_WIDTH - 1,
|
||||
cellIndex + WORLD_MAP_WIDTH,
|
||||
cellIndex + WORLD_MAP_WIDTH + 1,
|
||||
];
|
||||
|
||||
return cells.filter(c => c >= 0 && c < WORLD_MAP_WIDTH * WORLD_MAP_HEIGHT);
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ import { writable } from "svelte/store";
|
||||
import { createBuilding, createHero, createQuest } from "./create";
|
||||
import worldmap from "./data/worldmap";
|
||||
import { getTilesAtDistance, Hex } from "./hexgrid";
|
||||
import type { BuildingType, HeroType, QuestType, RegionType, ResourcesType } from "./types";
|
||||
import { getKeysAsNumbers, shuffle } from "./utils";
|
||||
import { WORLDMAP_TYPES, type BuildingType, type CostType, type HeroType, type QuestType, type RegionType, type ResourcesType } from "./types";
|
||||
import { getAdjacentWorldmapCells, getKeysAsNumbers, shuffle } from "./utils";
|
||||
import { WORLD_MAP_HEIGHT, WORLD_MAP_WIDTH } from "./constants";
|
||||
|
||||
|
||||
type Board = {
|
||||
@ -68,15 +69,28 @@ function getInitialOutsideBoard() {
|
||||
|
||||
|
||||
function getInitialWorldmap(): RegionType[] {
|
||||
return worldmap.map((r, index) => {
|
||||
const region = r as RegionType;
|
||||
return {
|
||||
...region,
|
||||
state: {
|
||||
index,
|
||||
},
|
||||
const board: RegionType[] = []
|
||||
for (let i = 0; i < WORLD_MAP_WIDTH * WORLD_MAP_HEIGHT; i++) {
|
||||
board[i] = {
|
||||
id: i,
|
||||
type: WORLDMAP_TYPES.EMPTY,
|
||||
};
|
||||
}
|
||||
const centerIndex = Math.floor((WORLD_MAP_WIDTH * WORLD_MAP_HEIGHT) / 2);
|
||||
board[centerIndex].type = WORLDMAP_TYPES.BOURGADE;
|
||||
|
||||
const adj = shuffle(getAdjacentWorldmapCells(centerIndex));
|
||||
worldmap.forEach(c => {
|
||||
const cellIndex = adj.pop() || 0;
|
||||
board[cellIndex] = {
|
||||
type: WORLDMAP_TYPES.OASIS,
|
||||
id: cellIndex,
|
||||
resource: c.resource as keyof CostType,
|
||||
state: {},
|
||||
};
|
||||
});
|
||||
|
||||
return board;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user