@civ-clone/base-city-lifecycle 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/workflows/build-on-push.yml +39 -0
- package/.prettierrc +4 -0
- package/LICENSE +21 -0
- package/README.md +42 -2
- package/lib/assignWorkers.d.ts +114 -0
- package/lib/assignWorkers.js +184 -0
- package/lib/assignWorkers.js.map +1 -0
- package/lib/assignWorkers.ts +372 -0
- package/lib/canBeWorked.d.ts +28 -0
- package/lib/canBeWorked.js +27 -0
- package/lib/canBeWorked.js.map +1 -0
- package/lib/canBeWorked.ts +43 -0
- package/lib/captured.d.ts +12 -0
- package/lib/captured.js +30 -0
- package/lib/captured.js.map +1 -0
- package/lib/captured.ts +31 -0
- package/lib/claimName.d.ts +12 -0
- package/lib/claimName.js +9 -0
- package/lib/claimName.js.map +1 -0
- package/lib/claimName.ts +13 -0
- package/lib/created.d.ts +41 -0
- package/lib/created.js +52 -0
- package/lib/created.js.map +1 -0
- package/lib/created.ts +110 -0
- package/lib/destroyed.d.ts +23 -0
- package/lib/destroyed.js +29 -0
- package/lib/destroyed.js.map +1 -0
- package/lib/destroyed.ts +39 -0
- package/lib/events.d.ts +39 -0
- package/lib/events.js +33 -0
- package/lib/events.js.map +1 -0
- package/lib/events.ts +51 -0
- package/lib/famine.d.ts +41 -0
- package/lib/famine.js +44 -0
- package/lib/famine.js.map +1 -0
- package/lib/famine.ts +68 -0
- package/lib/foodStorage.d.ts +21 -0
- package/lib/foodStorage.js +27 -0
- package/lib/foodStorage.js.map +1 -0
- package/lib/foodStorage.ts +30 -0
- package/lib/occupiedTiles.d.ts +39 -0
- package/lib/occupiedTiles.js +50 -0
- package/lib/occupiedTiles.js.map +1 -0
- package/lib/occupiedTiles.ts +84 -0
- package/lib/playerActions.d.ts +72 -0
- package/lib/playerActions.js +68 -0
- package/lib/playerActions.js.map +1 -0
- package/lib/playerActions.ts +114 -0
- package/lib/processYield.d.ts +30 -0
- package/lib/processYield.js +54 -0
- package/lib/processYield.js.map +1 -0
- package/lib/processYield.ts +82 -0
- package/lib/unsupported.d.ts +6 -0
- package/lib/unsupported.js +9 -0
- package/lib/unsupported.js.map +1 -0
- package/lib/unsupported.ts +8 -0
- package/package.json +45 -4
- package/tests/base-city-lifecycle.test.ts +536 -0
- package/tests/r3b.test.ts +341 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import * as base from '../lib/assignWorkers';
|
|
2
|
+
import * as library from '@civ-clone/library-city/lib/assignWorkers';
|
|
3
|
+
import { describe, it } from 'mocha';
|
|
4
|
+
import {
|
|
5
|
+
destroyUnits,
|
|
6
|
+
releaseSpecialists,
|
|
7
|
+
releaseWorkedTiles,
|
|
8
|
+
} from '../lib/destroyed';
|
|
9
|
+
import {
|
|
10
|
+
registerCityBuild,
|
|
11
|
+
registerCityGrowth,
|
|
12
|
+
workCityTile,
|
|
13
|
+
} from '../lib/created';
|
|
14
|
+
import AvailableCityBuildItemsRegistry from '@civ-clone/core-city-build/AvailableCityBuildItemsRegistry';
|
|
15
|
+
import AvailableSpecialistRegistry from '@civ-clone/core-city/AvailableSpecialistRegistry';
|
|
16
|
+
import CityBuildRegistry from '@civ-clone/core-city-build/CityBuildRegistry';
|
|
17
|
+
import CityGrowthRegistry from '@civ-clone/core-city-growth/CityGrowthRegistry';
|
|
18
|
+
import Effect from '@civ-clone/core-rule/Effect';
|
|
19
|
+
import PlayerWorldRegistry from '@civ-clone/core-player-world/PlayerWorldRegistry';
|
|
20
|
+
import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
|
|
21
|
+
import Specialist from '@civ-clone/core-city/Specialist';
|
|
22
|
+
import SpecialistRegistry from '@civ-clone/core-city/SpecialistRegistry';
|
|
23
|
+
import TileReassigned from '@civ-clone/core-city/Rules/TileReassigned';
|
|
24
|
+
import WorkedTile from '@civ-clone/core-city/WorkedTile';
|
|
25
|
+
import WorkedTileRegistry from '@civ-clone/core-city/WorkedTileRegistry';
|
|
26
|
+
import { clearCapturedBuild, keepCapturedBuild } from '../lib/captured';
|
|
27
|
+
import { emitBuildingComplete } from '../lib/events';
|
|
28
|
+
import { expect } from 'chai';
|
|
29
|
+
import { readFileSync } from 'fs';
|
|
30
|
+
import { releaseOccupiedWorkedTiles } from '../lib/occupiedTiles';
|
|
31
|
+
|
|
32
|
+
class Entertainer extends Specialist {}
|
|
33
|
+
class Scientist extends Specialist {}
|
|
34
|
+
|
|
35
|
+
const named = (name: string, extra: object = {}) => ({ name, ...extra } as any),
|
|
36
|
+
// A city with no tiles of its own to work, but its centre.
|
|
37
|
+
standInCity = (name: string, centre: unknown, player: unknown) =>
|
|
38
|
+
named(name, {
|
|
39
|
+
tile: () => centre,
|
|
40
|
+
tiles: () => ({ entries: () => [], includes: () => false }),
|
|
41
|
+
tilesWorked: () => ({ yields: () => [], entries: () => [] }),
|
|
42
|
+
player: () => player,
|
|
43
|
+
}),
|
|
44
|
+
growthOf = (size: number) =>
|
|
45
|
+
({ getByCity: () => ({ size: () => size }) } as any);
|
|
46
|
+
|
|
47
|
+
describe('assignWorkers', (): void => {
|
|
48
|
+
// The compiled functions, with the yields named the same way: library-city's come from its `Yields` module.
|
|
49
|
+
const source = (fn: unknown): string =>
|
|
50
|
+
String(fn)
|
|
51
|
+
.replace(/Yields_1\.(Food|Production|Trade)/g, '$1')
|
|
52
|
+
.replace(/(Food|Production|Trade)_1\.default/g, '$1');
|
|
53
|
+
|
|
54
|
+
// The whole compiled module, private helpers (`removeSpecialist`) included, with only where the yields come from and
|
|
55
|
+
// the lifting note taken out of each.
|
|
56
|
+
it("is library-city 0.1.4's file, unchanged", (): void => {
|
|
57
|
+
const module = (path: string): string =>
|
|
58
|
+
readFileSync(path, 'utf8')
|
|
59
|
+
.split('\n')
|
|
60
|
+
.filter(
|
|
61
|
+
(line) =>
|
|
62
|
+
!line.startsWith('//') &&
|
|
63
|
+
!/^const (Yields|Food|Production|Trade)_1 = require/.test(line)
|
|
64
|
+
)
|
|
65
|
+
.join('\n')
|
|
66
|
+
.replace(/Yields_1\.(Food|Production|Trade)/g, '$1')
|
|
67
|
+
.replace(/(Food|Production|Trade)_1\.default/g, '$1');
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
module(require.resolve('../lib/assignWorkers.js')) ===
|
|
71
|
+
module(require.resolve('@civ-clone/library-city/lib/assignWorkers.js'))
|
|
72
|
+
).true;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("exports library-city's functions, each unchanged", (): void => {
|
|
76
|
+
const names = Object.keys(library).filter(
|
|
77
|
+
(name) => typeof (library as any)[name] === 'function'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(names).to.not.be.empty;
|
|
81
|
+
expect(
|
|
82
|
+
Object.keys(base).filter(
|
|
83
|
+
(name) => typeof (base as any)[name] === 'function'
|
|
84
|
+
)
|
|
85
|
+
).to.have.members(names);
|
|
86
|
+
|
|
87
|
+
names.forEach((name: string): void => {
|
|
88
|
+
expect(source((base as any)[name]), name).to.equal(
|
|
89
|
+
source((library as any)[name])
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('places citizens as specialists of the first kind offered, and turns one into the next kind', (): void => {
|
|
95
|
+
const specialistRegistry = new SpecialistRegistry(),
|
|
96
|
+
available = new AvailableSpecialistRegistry(),
|
|
97
|
+
city = standInCity('city', named('centre'), named('player'));
|
|
98
|
+
|
|
99
|
+
available.register(Entertainer, Scientist);
|
|
100
|
+
|
|
101
|
+
const specialist = base.addSpecialist(city, specialistRegistry, available)!;
|
|
102
|
+
|
|
103
|
+
expect(specialist).to.instanceOf(Entertainer);
|
|
104
|
+
expect(
|
|
105
|
+
base.citizenCount(city, new WorkedTileRegistry(), specialistRegistry)
|
|
106
|
+
).to.equal(1);
|
|
107
|
+
|
|
108
|
+
const changed = base.changeSpecialist(
|
|
109
|
+
specialist,
|
|
110
|
+
specialistRegistry,
|
|
111
|
+
available
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(changed).to.instanceOf(Scientist);
|
|
115
|
+
expect(specialistRegistry.getByCity(city)).to.eql([changed]);
|
|
116
|
+
expect(
|
|
117
|
+
base.addSpecialist(
|
|
118
|
+
city,
|
|
119
|
+
specialistRegistry,
|
|
120
|
+
new AvailableSpecialistRegistry()
|
|
121
|
+
)
|
|
122
|
+
).to.null;
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('created', (): void => {
|
|
127
|
+
it('gives a new city its build, asked for as a new one, and its growth', (): void => {
|
|
128
|
+
const city = named('city'),
|
|
129
|
+
ruleRegistry = new RuleRegistry(),
|
|
130
|
+
cityBuildRegistry = new CityBuildRegistry(),
|
|
131
|
+
cityGrowthRegistry = new CityGrowthRegistry(),
|
|
132
|
+
cityBuild = registerCityBuild(
|
|
133
|
+
city,
|
|
134
|
+
cityBuildRegistry,
|
|
135
|
+
new AvailableCityBuildItemsRegistry(),
|
|
136
|
+
ruleRegistry
|
|
137
|
+
),
|
|
138
|
+
cityGrowth = registerCityGrowth(city, cityGrowthRegistry, ruleRegistry);
|
|
139
|
+
|
|
140
|
+
expect(cityBuildRegistry.getByCity(city)).to.equal(cityBuild);
|
|
141
|
+
expect(cityBuild.cleared()).to.eql({ reason: 'created', item: null });
|
|
142
|
+
expect(cityGrowthRegistry.getByCity(city)).to.equal(cityGrowth);
|
|
143
|
+
expect(cityGrowth.size()).to.equal(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('works its own centre, and lets the city that worked it choose again first', (): void => {
|
|
147
|
+
const centre = named('centre'),
|
|
148
|
+
player = named('player'),
|
|
149
|
+
previous = standInCity('previous', named('elsewhere'), player),
|
|
150
|
+
city = standInCity('city', centre, player),
|
|
151
|
+
workedTileRegistry = new WorkedTileRegistry(),
|
|
152
|
+
ruleRegistry = new RuleRegistry(),
|
|
153
|
+
log: unknown[][] = [];
|
|
154
|
+
|
|
155
|
+
workedTileRegistry.register(new WorkedTile(centre as any, previous));
|
|
156
|
+
|
|
157
|
+
ruleRegistry.register(
|
|
158
|
+
new TileReassigned(
|
|
159
|
+
new Effect((...args: unknown[]) => {
|
|
160
|
+
log.push([
|
|
161
|
+
...args,
|
|
162
|
+
workedTileRegistry.getByTile(centre as any)!.city(),
|
|
163
|
+
]);
|
|
164
|
+
})
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
workCityTile(
|
|
169
|
+
city,
|
|
170
|
+
ruleRegistry,
|
|
171
|
+
new PlayerWorldRegistry(),
|
|
172
|
+
growthOf(0),
|
|
173
|
+
workedTileRegistry,
|
|
174
|
+
new SpecialistRegistry(),
|
|
175
|
+
new AvailableSpecialistRegistry()
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(workedTileRegistry.getByTile(centre as any)!.city()).to.equal(city);
|
|
179
|
+
expect(log).to.eql([[previous, centre, city]]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('places a citizen with no tile to work as a specialist', (): void => {
|
|
183
|
+
const city = standInCity('city', named('centre'), named('player')),
|
|
184
|
+
specialistRegistry = new SpecialistRegistry(),
|
|
185
|
+
available = new AvailableSpecialistRegistry();
|
|
186
|
+
|
|
187
|
+
available.register(Entertainer);
|
|
188
|
+
|
|
189
|
+
workCityTile(
|
|
190
|
+
city,
|
|
191
|
+
new RuleRegistry(),
|
|
192
|
+
new PlayerWorldRegistry(),
|
|
193
|
+
growthOf(1),
|
|
194
|
+
new WorkedTileRegistry(),
|
|
195
|
+
specialistRegistry,
|
|
196
|
+
available
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(specialistRegistry.getByCity(city)).to.have.length(1);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('destroyed', (): void => {
|
|
204
|
+
it('destroys only the units not destroyed already', (): void => {
|
|
205
|
+
const log: string[] = [],
|
|
206
|
+
unit = (name: string, destroyed: boolean) =>
|
|
207
|
+
named(name, {
|
|
208
|
+
destroyed: () => destroyed,
|
|
209
|
+
destroy: () => log.push(name),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
destroyUnits([unit('a', false), unit('b', true), unit('c', false)]);
|
|
213
|
+
|
|
214
|
+
expect(log).to.eql(['a', 'c']);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("frees the city's tiles and its specialists, and no other city's", (): void => {
|
|
218
|
+
const city = named('city'),
|
|
219
|
+
other = named('other'),
|
|
220
|
+
workedTileRegistry = new WorkedTileRegistry(),
|
|
221
|
+
specialistRegistry = new SpecialistRegistry(),
|
|
222
|
+
kept = new WorkedTile(named('kept') as any, other),
|
|
223
|
+
keptSpecialist = new Entertainer(other);
|
|
224
|
+
|
|
225
|
+
workedTileRegistry.register(
|
|
226
|
+
new WorkedTile(named('a') as any, city),
|
|
227
|
+
kept,
|
|
228
|
+
new WorkedTile(named('b') as any, city)
|
|
229
|
+
);
|
|
230
|
+
specialistRegistry.register(
|
|
231
|
+
new Entertainer(city),
|
|
232
|
+
keptSpecialist,
|
|
233
|
+
new Scientist(city)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
releaseWorkedTiles(city, workedTileRegistry);
|
|
237
|
+
releaseSpecialists(city, specialistRegistry);
|
|
238
|
+
|
|
239
|
+
expect(workedTileRegistry.entries()).to.eql([kept]);
|
|
240
|
+
expect(specialistRegistry.entries()).to.eql([keptSpecialist]);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('releaseOccupiedWorkedTiles', (): void => {
|
|
245
|
+
it("releases the tiles another player's unit stands on, never the centre, and hands them to TileReassigned", (): void => {
|
|
246
|
+
const player = named('player'),
|
|
247
|
+
enemy = named('enemy'),
|
|
248
|
+
centre = named('centre'),
|
|
249
|
+
occupied = named('occupied'),
|
|
250
|
+
own = named('own'),
|
|
251
|
+
free = named('free'),
|
|
252
|
+
city = named('city', { tile: () => centre }),
|
|
253
|
+
workedTileRegistry = new WorkedTileRegistry(),
|
|
254
|
+
ruleRegistry = new RuleRegistry(),
|
|
255
|
+
log: unknown[][] = [],
|
|
256
|
+
unitsOn: Map<unknown, unknown[]> = new Map([
|
|
257
|
+
[centre, [named('enemyOnCentre', { player: () => enemy })]],
|
|
258
|
+
[occupied, [named('enemyUnit', { player: () => enemy })]],
|
|
259
|
+
[own, [named('ownUnit', { player: () => player })]],
|
|
260
|
+
]),
|
|
261
|
+
centreWorked = new WorkedTile(centre, city),
|
|
262
|
+
ownWorked = new WorkedTile(own, city),
|
|
263
|
+
freeWorked = new WorkedTile(free, city);
|
|
264
|
+
|
|
265
|
+
workedTileRegistry.register(
|
|
266
|
+
centreWorked,
|
|
267
|
+
new WorkedTile(occupied, city),
|
|
268
|
+
ownWorked,
|
|
269
|
+
freeWorked
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
ruleRegistry.register(
|
|
273
|
+
new TileReassigned(new Effect((...args: unknown[]) => log.push(args)))
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
releaseOccupiedWorkedTiles(
|
|
277
|
+
player,
|
|
278
|
+
{ getByPlayer: () => [city] } as any,
|
|
279
|
+
{ getByTile: (tile: unknown) => unitsOn.get(tile) ?? [] } as any,
|
|
280
|
+
workedTileRegistry,
|
|
281
|
+
ruleRegistry
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(workedTileRegistry.entries()).to.eql([
|
|
285
|
+
centreWorked,
|
|
286
|
+
ownWorked,
|
|
287
|
+
freeWorked,
|
|
288
|
+
]);
|
|
289
|
+
expect(log).to.eql([[city, occupied]]);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('emitBuildingComplete', (): void => {
|
|
294
|
+
it('tells clients of the build and what was built', (): void => {
|
|
295
|
+
const emitted: unknown[][] = [],
|
|
296
|
+
cityBuild = named('cityBuild'),
|
|
297
|
+
built = named('built');
|
|
298
|
+
|
|
299
|
+
emitBuildingComplete(cityBuild, built, {
|
|
300
|
+
emit: (...args: unknown[]) => emitted.push(args),
|
|
301
|
+
} as any);
|
|
302
|
+
|
|
303
|
+
expect(emitted).to.eql([['city:building-complete', cityBuild, built]]);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('captured', (): void => {
|
|
308
|
+
const standInBuild = (building: unknown, log: unknown[][]) =>
|
|
309
|
+
({
|
|
310
|
+
building: () => building,
|
|
311
|
+
setCleared: (...args: unknown[]) => log.push(['setCleared', ...args]),
|
|
312
|
+
cancel: (...args: unknown[]) => log.push(['cancel', ...args]),
|
|
313
|
+
clearQueue: (...args: unknown[]) => log.push(['clearQueue', ...args]),
|
|
314
|
+
reprice: (...args: unknown[]) => log.push(['reprice', ...args]),
|
|
315
|
+
} as any);
|
|
316
|
+
|
|
317
|
+
it('clears the build, or asks for one when there was none', (): void => {
|
|
318
|
+
const log: unknown[][] = [];
|
|
319
|
+
|
|
320
|
+
clearCapturedBuild(standInBuild(named('item'), log));
|
|
321
|
+
clearCapturedBuild(standInBuild(null, log));
|
|
322
|
+
|
|
323
|
+
expect(log).to.eql([
|
|
324
|
+
['cancel', 'captured'],
|
|
325
|
+
['setCleared', 'captured'],
|
|
326
|
+
]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('keeps the item at the new price without the queue, or asks for one when there was none', (): void => {
|
|
330
|
+
const log: unknown[][] = [];
|
|
331
|
+
|
|
332
|
+
keepCapturedBuild(standInBuild(named('item'), log));
|
|
333
|
+
keepCapturedBuild(standInBuild(null, log));
|
|
334
|
+
|
|
335
|
+
expect(log).to.eql([
|
|
336
|
+
['clearQueue'],
|
|
337
|
+
['reprice'],
|
|
338
|
+
['setCleared', 'captured'],
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
});
|
package/tsconfig.json
ADDED