@civ-clone/base-city-happiness-status 0.1.0 → 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/README.md CHANGED
@@ -11,6 +11,11 @@ is a `core-pending-effect` `PendingEffect` on the city, named by the ruleset's o
11
11
  - `registerCityStatusHandler(handler, event, pendingEffects?, engine?)`: emits `event` with the city when its status
12
12
  ends. Register it when the rules are created, so a status loaded from a save can still end.
13
13
  - `forgetCityStatuses(city, handlers, pendingEffects?)`: drops statuses without ending them, for a city that's gone.
14
+ - `lib/statusTurn`: `cityStatusTurn(cities, options)` checks each city's `CivilDisorder` and `CelebrateLeader` rules
15
+ on one set of yields, emits `city:civil-disorder` and `city:leader-celebration`, and records or ends both statuses.
16
+ The ruleset passes its handlers and its own turns of the rule through hooks: `onDisorderContinues` (a second turn of
17
+ disorder, such as a Democracy falling), `onCelebrationChecked` (with whether the city celebrates again, for growth),
18
+ `forgetCelebration` (drop rather than end) and `celebrationSurvivesDisorder`.
14
19
 
15
20
  A library: it registers no rules. See `civ1-city-happiness` for Civilization's civil disorder and "We Love the King
16
21
  Day".
@@ -0,0 +1,70 @@
1
+ import {
2
+ PendingEffect,
3
+ PendingEffectRegistry,
4
+ } from '@civ-clone/core-pending-effect';
5
+ import City from '@civ-clone/core-city/City';
6
+ import { Engine } from '@civ-clone/core-engine/Engine';
7
+ import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
8
+ import Yield from '@civ-clone/core-yield/Yield';
9
+ /** What a city's check found this turn, and what was recorded before it. */
10
+ export type CityStatusCheck = {
11
+ city: City;
12
+ /** The yields the city was judged on, worked out once for both checks. */
13
+ yields: Yield[];
14
+ /** Whether the city is in civil disorder now. */
15
+ disorder: boolean;
16
+ /** Whether the city celebrates now. */
17
+ celebrating: boolean;
18
+ /** The city's disorder as recorded before this turn's check, or `null`. */
19
+ recordedDisorder: PendingEffect | null;
20
+ /** The city's celebration as recorded before this turn's check, or `null`. */
21
+ recordedCelebration: PendingEffect | null;
22
+ };
23
+ /** How a ruleset keeps its cities' disorder and celebrations from one turn to the next. */
24
+ export type CityStatusTurn = {
25
+ /** The status `handler`s the ruleset records disorder and celebration under (see `cityStatus`). */
26
+ disorderHandler: string;
27
+ celebrationHandler: string;
28
+ pendingEffects: PendingEffectRegistry;
29
+ ruleRegistry: RuleRegistry;
30
+ engine: Engine;
31
+ /**
32
+ * Whether a city that celebrated before, and is in disorder now without celebrating, keeps its celebration (Civ1
33
+ * keeps it, without a word, until order returns); otherwise its celebration is ended or forgotten as usual.
34
+ */
35
+ celebrationSurvivesDisorder?: boolean;
36
+ /** Called for a city in disorder for a second turn running (or more), before its status is updated. */
37
+ onDisorderContinues?: (check: CityStatusCheck) => void;
38
+ /**
39
+ * Called for each city whose celebration is updated, before it is, with whether it celebrates for a second turn
40
+ * running (or more): what a ruleset grows a city for.
41
+ */
42
+ onCelebrationChecked?: (check: CityStatusCheck, again: boolean) => void;
43
+ /**
44
+ * Whether a celebration that has stopped is dropped without its end being told (no event), rather than ended.
45
+ * Ended, by default.
46
+ */
47
+ forgetCelebration?: (check: CityStatusCheck) => boolean;
48
+ };
49
+ /**
50
+ * Checks each of `cities` for civil disorder and for celebrating, at the start of their player's turn, emitting
51
+ * `city:civil-disorder` for each city in disorder, and records the statuses that start and ends those that stop
52
+ * (`updateCityStatus`), emitting `city:leader-celebration` as a celebration starts. The handlers' events (registered
53
+ * with `registerCityStatusHandler`) tell of order restored and celebrations ended. The cities are taken in order, and a
54
+ * hook may change the player's government part-way through them.
55
+ */
56
+ export declare const cityStatusTurn: (
57
+ cities: City[],
58
+ {
59
+ disorderHandler,
60
+ celebrationHandler,
61
+ pendingEffects,
62
+ ruleRegistry,
63
+ engine,
64
+ celebrationSurvivesDisorder,
65
+ onDisorderContinues,
66
+ onCelebrationChecked,
67
+ forgetCelebration,
68
+ }: CityStatusTurn
69
+ ) => void;
70
+ export default cityStatusTurn;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cityStatusTurn = void 0;
4
+ const cityStatus_1 = require("./cityStatus");
5
+ const CelebrateLeader_1 = require("@civ-clone/core-city-happiness/Rules/CelebrateLeader");
6
+ const CivilDisorder_1 = require("@civ-clone/core-city-happiness/Rules/CivilDisorder");
7
+ /**
8
+ * Checks each of `cities` for civil disorder and for celebrating, at the start of their player's turn, emitting
9
+ * `city:civil-disorder` for each city in disorder, and records the statuses that start and ends those that stop
10
+ * (`updateCityStatus`), emitting `city:leader-celebration` as a celebration starts. The handlers' events (registered
11
+ * with `registerCityStatusHandler`) tell of order restored and celebrations ended. The cities are taken in order, and a
12
+ * hook may change the player's government part-way through them.
13
+ */
14
+ const cityStatusTurn = (cities, { disorderHandler, celebrationHandler, pendingEffects, ruleRegistry, engine, celebrationSurvivesDisorder = false, onDisorderContinues = () => { }, onCelebrationChecked = () => { }, forgetCelebration = () => false, }) => cities.forEach((city) => {
15
+ // Both rules read the same yields, and working them out isn't cheap (civ-clone/web-renderer#315).
16
+ const yields = city.yields(), check = {
17
+ city,
18
+ yields,
19
+ disorder: ruleRegistry
20
+ .process(CivilDisorder_1.default, city, yields)
21
+ .some((result) => result),
22
+ celebrating: ruleRegistry
23
+ .process(CelebrateLeader_1.default, city, yields)
24
+ .some((result) => result),
25
+ recordedDisorder: (0, cityStatus_1.cityStatus)(disorderHandler, city, pendingEffects),
26
+ recordedCelebration: (0, cityStatus_1.cityStatus)(celebrationHandler, city, pendingEffects),
27
+ };
28
+ if (check.disorder) {
29
+ engine.emit('city:civil-disorder', city);
30
+ }
31
+ if (check.disorder && check.recordedDisorder !== null) {
32
+ onDisorderContinues(check);
33
+ }
34
+ onCelebrationChecked(check, check.celebrating && check.recordedCelebration !== null);
35
+ // Records disorder that has started, and ends disorder that has stopped.
36
+ (0, cityStatus_1.updateCityStatus)(disorderHandler, city, check.disorder, check.recordedDisorder, pendingEffects);
37
+ if (celebrationSurvivesDisorder && check.disorder && !check.celebrating) {
38
+ return;
39
+ }
40
+ if (!check.celebrating &&
41
+ check.recordedCelebration !== null &&
42
+ forgetCelebration(check)) {
43
+ (0, cityStatus_1.forgetCityStatuses)(city, [celebrationHandler], pendingEffects);
44
+ return;
45
+ }
46
+ (0, cityStatus_1.updateCityStatus)(celebrationHandler, city, check.celebrating, check.recordedCelebration, pendingEffects, () => engine.emit('city:leader-celebration', city));
47
+ });
48
+ exports.cityStatusTurn = cityStatusTurn;
49
+ exports.default = exports.cityStatusTurn;
50
+ //# sourceMappingURL=statusTurn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusTurn.js","sourceRoot":"","sources":["statusTurn.ts"],"names":[],"mappings":";;;AAIA,6CAAgF;AAChF,0FAAmF;AAEnF,sFAA+E;AA+C/E;;;;;;GAMG;AACI,MAAM,cAAc,GAAG,CAC5B,MAAc,EACd,EACE,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,MAAM,EACN,2BAA2B,GAAG,KAAK,EACnC,mBAAmB,GAAG,GAAG,EAAE,GAAE,CAAC,EAC9B,oBAAoB,GAAG,GAAG,EAAE,GAAE,CAAC,EAC/B,iBAAiB,GAAG,GAAG,EAAE,CAAC,KAAK,GAChB,EACX,EAAE,CACR,MAAM,CAAC,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE;IAClC,kGAAkG;IAClG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,EAC1B,KAAK,GAAoB;QACvB,IAAI;QACJ,MAAM;QACN,QAAQ,EAAE,YAAY;aACnB,OAAO,CAAC,uBAAa,EAAE,IAAI,EAAE,MAAM,CAAC;aACpC,IAAI,CAAC,CAAC,MAAe,EAAW,EAAE,CAAC,MAAM,CAAC;QAC7C,WAAW,EAAE,YAAY;aACtB,OAAO,CAAC,yBAAe,EAAE,IAAI,EAAE,MAAM,CAAC;aACtC,IAAI,CAAC,CAAC,MAAe,EAAW,EAAE,CAAC,MAAM,CAAC;QAC7C,gBAAgB,EAAE,IAAA,uBAAU,EAAC,eAAe,EAAE,IAAI,EAAE,cAAc,CAAC;QACnE,mBAAmB,EAAE,IAAA,uBAAU,EAC7B,kBAAkB,EAClB,IAAI,EACJ,cAAc,CACf;KACF,CAAC;IAEJ,IAAI,KAAK,CAAC,QAAQ,EAAE;QAClB,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;KAC1C;IAED,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,gBAAgB,KAAK,IAAI,EAAE;QACrD,mBAAmB,CAAC,KAAK,CAAC,CAAC;KAC5B;IAED,oBAAoB,CAClB,KAAK,EACL,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,mBAAmB,KAAK,IAAI,CACxD,CAAC;IAEF,yEAAyE;IACzE,IAAA,6BAAgB,EACd,eAAe,EACf,IAAI,EACJ,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,gBAAgB,EACtB,cAAc,CACf,CAAC;IAEF,IAAI,2BAA2B,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;QACvE,OAAO;KACR;IAED,IACE,CAAC,KAAK,CAAC,WAAW;QAClB,KAAK,CAAC,mBAAmB,KAAK,IAAI;QAClC,iBAAiB,CAAC,KAAK,CAAC,EACxB;QACA,IAAA,+BAAkB,EAAC,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE,cAAc,CAAC,CAAC;QAE/D,OAAO;KACR;IAED,IAAA,6BAAgB,EACd,kBAAkB,EAClB,IAAI,EACJ,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,mBAAmB,EACzB,cAAc,EACd,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,IAAI,CAAC,CACnD,CAAC;AACJ,CAAC,CAAC,CAAC;AA9EQ,QAAA,cAAc,kBA8EtB;AAEL,kBAAe,sBAAc,CAAC"}
@@ -0,0 +1,142 @@
1
+ import {
2
+ PendingEffect,
3
+ PendingEffectRegistry,
4
+ } from '@civ-clone/core-pending-effect';
5
+ import { cityStatus, forgetCityStatuses, updateCityStatus } from './cityStatus';
6
+ import CelebrateLeader from '@civ-clone/core-city-happiness/Rules/CelebrateLeader';
7
+ import City from '@civ-clone/core-city/City';
8
+ import CivilDisorder from '@civ-clone/core-city-happiness/Rules/CivilDisorder';
9
+ import { Engine } from '@civ-clone/core-engine/Engine';
10
+ import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
11
+ import Yield from '@civ-clone/core-yield/Yield';
12
+
13
+ /** What a city's check found this turn, and what was recorded before it. */
14
+ export type CityStatusCheck = {
15
+ city: City;
16
+ /** The yields the city was judged on, worked out once for both checks. */
17
+ yields: Yield[];
18
+ /** Whether the city is in civil disorder now. */
19
+ disorder: boolean;
20
+ /** Whether the city celebrates now. */
21
+ celebrating: boolean;
22
+ /** The city's disorder as recorded before this turn's check, or `null`. */
23
+ recordedDisorder: PendingEffect | null;
24
+ /** The city's celebration as recorded before this turn's check, or `null`. */
25
+ recordedCelebration: PendingEffect | null;
26
+ };
27
+
28
+ /** How a ruleset keeps its cities' disorder and celebrations from one turn to the next. */
29
+ export type CityStatusTurn = {
30
+ /** The status `handler`s the ruleset records disorder and celebration under (see `cityStatus`). */
31
+ disorderHandler: string;
32
+ celebrationHandler: string;
33
+ pendingEffects: PendingEffectRegistry;
34
+ ruleRegistry: RuleRegistry;
35
+ engine: Engine;
36
+ /**
37
+ * Whether a city that celebrated before, and is in disorder now without celebrating, keeps its celebration (Civ1
38
+ * keeps it, without a word, until order returns); otherwise its celebration is ended or forgotten as usual.
39
+ */
40
+ celebrationSurvivesDisorder?: boolean;
41
+ /** Called for a city in disorder for a second turn running (or more), before its status is updated. */
42
+ onDisorderContinues?: (check: CityStatusCheck) => void;
43
+ /**
44
+ * Called for each city whose celebration is updated, before it is, with whether it celebrates for a second turn
45
+ * running (or more): what a ruleset grows a city for.
46
+ */
47
+ onCelebrationChecked?: (check: CityStatusCheck, again: boolean) => void;
48
+ /**
49
+ * Whether a celebration that has stopped is dropped without its end being told (no event), rather than ended.
50
+ * Ended, by default.
51
+ */
52
+ forgetCelebration?: (check: CityStatusCheck) => boolean;
53
+ };
54
+
55
+ /**
56
+ * Checks each of `cities` for civil disorder and for celebrating, at the start of their player's turn, emitting
57
+ * `city:civil-disorder` for each city in disorder, and records the statuses that start and ends those that stop
58
+ * (`updateCityStatus`), emitting `city:leader-celebration` as a celebration starts. The handlers' events (registered
59
+ * with `registerCityStatusHandler`) tell of order restored and celebrations ended. The cities are taken in order, and a
60
+ * hook may change the player's government part-way through them.
61
+ */
62
+ export const cityStatusTurn = (
63
+ cities: City[],
64
+ {
65
+ disorderHandler,
66
+ celebrationHandler,
67
+ pendingEffects,
68
+ ruleRegistry,
69
+ engine,
70
+ celebrationSurvivesDisorder = false,
71
+ onDisorderContinues = () => {},
72
+ onCelebrationChecked = () => {},
73
+ forgetCelebration = () => false,
74
+ }: CityStatusTurn
75
+ ): void =>
76
+ cities.forEach((city: City): void => {
77
+ // Both rules read the same yields, and working them out isn't cheap (civ-clone/web-renderer#315).
78
+ const yields = city.yields(),
79
+ check: CityStatusCheck = {
80
+ city,
81
+ yields,
82
+ disorder: ruleRegistry
83
+ .process(CivilDisorder, city, yields)
84
+ .some((result: boolean): boolean => result),
85
+ celebrating: ruleRegistry
86
+ .process(CelebrateLeader, city, yields)
87
+ .some((result: boolean): boolean => result),
88
+ recordedDisorder: cityStatus(disorderHandler, city, pendingEffects),
89
+ recordedCelebration: cityStatus(
90
+ celebrationHandler,
91
+ city,
92
+ pendingEffects
93
+ ),
94
+ };
95
+
96
+ if (check.disorder) {
97
+ engine.emit('city:civil-disorder', city);
98
+ }
99
+
100
+ if (check.disorder && check.recordedDisorder !== null) {
101
+ onDisorderContinues(check);
102
+ }
103
+
104
+ onCelebrationChecked(
105
+ check,
106
+ check.celebrating && check.recordedCelebration !== null
107
+ );
108
+
109
+ // Records disorder that has started, and ends disorder that has stopped.
110
+ updateCityStatus(
111
+ disorderHandler,
112
+ city,
113
+ check.disorder,
114
+ check.recordedDisorder,
115
+ pendingEffects
116
+ );
117
+
118
+ if (celebrationSurvivesDisorder && check.disorder && !check.celebrating) {
119
+ return;
120
+ }
121
+
122
+ if (
123
+ !check.celebrating &&
124
+ check.recordedCelebration !== null &&
125
+ forgetCelebration(check)
126
+ ) {
127
+ forgetCityStatuses(city, [celebrationHandler], pendingEffects);
128
+
129
+ return;
130
+ }
131
+
132
+ updateCityStatus(
133
+ celebrationHandler,
134
+ city,
135
+ check.celebrating,
136
+ check.recordedCelebration,
137
+ pendingEffects,
138
+ () => engine.emit('city:leader-celebration', city)
139
+ );
140
+ });
141
+
142
+ export default cityStatusTurn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civ-clone/base-city-happiness-status",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Keeps a city's status, such as disorder or celebrating, from one turn to the next and in the save.",
5
5
  "repository": "git@github.com:civ-clone/base-city-happiness-status.git",
6
6
  "keywords": [
@@ -29,7 +29,10 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@civ-clone/core-city": "^0.1.24",
32
+ "@civ-clone/core-city-happiness": "^0.1.2",
32
33
  "@civ-clone/core-engine": "^0.1.6",
33
- "@civ-clone/core-pending-effect": "^0.1.2"
34
+ "@civ-clone/core-pending-effect": "^0.1.2",
35
+ "@civ-clone/core-rule": "^0.1.9",
36
+ "@civ-clone/core-yield": "^0.1.6"
34
37
  }
35
38
  }
@@ -0,0 +1,187 @@
1
+ import { CityStatusCheck, cityStatusTurn } from '../lib/statusTurn';
2
+ import { cityStatus, registerCityStatusHandler } from '../lib/cityStatus';
3
+ import CelebrateLeader from '@civ-clone/core-city-happiness/Rules/CelebrateLeader';
4
+ import City from '@civ-clone/core-city/City';
5
+ import CivilDisorder from '@civ-clone/core-city-happiness/Rules/CivilDisorder';
6
+ import Effect from '@civ-clone/core-rule/Effect';
7
+ import Engine from '@civ-clone/core-engine/Engine';
8
+ import { PendingEffectRegistry } from '@civ-clone/core-pending-effect';
9
+ import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
10
+ import { expect } from 'chai';
11
+
12
+ const UPSET = 'test:upset',
13
+ CHEERFUL = 'test:cheerful';
14
+
15
+ describe('cityStatusTurn', (): void => {
16
+ const setUp = () => {
17
+ const pendingEffects = new PendingEffectRegistry(),
18
+ ruleRegistry = new RuleRegistry(),
19
+ events: [string, unknown][] = [],
20
+ engine = {
21
+ emit: (event: string, target: unknown) => events.push([event, target]),
22
+ } as unknown as Engine,
23
+ // What each city's check finds this turn, by name.
24
+ state: { [name: string]: { disorder: boolean; celebrating: boolean } } =
25
+ {},
26
+ yieldsAsked: string[] = [],
27
+ city = (name: string) =>
28
+ ({
29
+ name,
30
+ yields: () => {
31
+ yieldsAsked.push(name);
32
+
33
+ return [name];
34
+ },
35
+ } as unknown as City),
36
+ a = city('a'),
37
+ b = city('b');
38
+
39
+ ruleRegistry.register(
40
+ new CivilDisorder(
41
+ new Effect(
42
+ (city: City, yields: unknown[]): boolean =>
43
+ yields[0] === (city as any).name &&
44
+ state[(city as any).name].disorder
45
+ )
46
+ ),
47
+ new CelebrateLeader(
48
+ new Effect(
49
+ (city: City, yields: unknown[]): boolean =>
50
+ yields[0] === (city as any).name &&
51
+ state[(city as any).name].celebrating
52
+ )
53
+ )
54
+ );
55
+
56
+ registerCityStatusHandler(
57
+ UPSET,
58
+ 'city:order-restored',
59
+ pendingEffects,
60
+ engine
61
+ );
62
+ registerCityStatusHandler(
63
+ CHEERFUL,
64
+ 'city:celebration-ended',
65
+ pendingEffects,
66
+ engine
67
+ );
68
+
69
+ const turn = (extra: object = {}) =>
70
+ cityStatusTurn([a, b], {
71
+ disorderHandler: UPSET,
72
+ celebrationHandler: CHEERFUL,
73
+ pendingEffects,
74
+ ruleRegistry,
75
+ engine,
76
+ ...extra,
77
+ });
78
+
79
+ return { pendingEffects, events, state, yieldsAsked, a, b, turn };
80
+ };
81
+
82
+ it('works out the yields once per city, records disorder and celebrations as they start and ends them as they stop', (): void => {
83
+ const { pendingEffects, events, state, yieldsAsked, a, b, turn } = setUp();
84
+
85
+ state.a = { disorder: true, celebrating: false };
86
+ state.b = { disorder: false, celebrating: true };
87
+
88
+ turn();
89
+
90
+ expect(yieldsAsked).eql(['a', 'b']);
91
+ expect(events).eql([
92
+ ['city:civil-disorder', a],
93
+ ['city:leader-celebration', b],
94
+ ]);
95
+ expect(cityStatus(UPSET, a, pendingEffects)).not.null;
96
+ expect(cityStatus(CHEERFUL, b, pendingEffects)).not.null;
97
+
98
+ events.length = 0;
99
+ state.a = { disorder: false, celebrating: false };
100
+ state.b = { disorder: false, celebrating: false };
101
+
102
+ turn();
103
+
104
+ expect(events).eql([
105
+ ['city:order-restored', a],
106
+ ['city:celebration-ended', b],
107
+ ]);
108
+ expect(pendingEffects.entries()).empty;
109
+ });
110
+
111
+ it('calls the hooks with the check, telling a second turn running from a first', (): void => {
112
+ const { state, a, b, turn } = setUp(),
113
+ continued: City[] = [],
114
+ checked: [City, boolean][] = [];
115
+
116
+ state.a = { disorder: true, celebrating: false };
117
+ state.b = { disorder: false, celebrating: true };
118
+
119
+ const hooks = {
120
+ onDisorderContinues: ({ city }: CityStatusCheck) => continued.push(city),
121
+ onCelebrationChecked: ({ city }: CityStatusCheck, again: boolean) =>
122
+ checked.push([city, again]),
123
+ };
124
+
125
+ turn(hooks);
126
+ turn(hooks);
127
+
128
+ expect(continued).eql([a]);
129
+ expect(checked).eql([
130
+ [a, false],
131
+ [b, false],
132
+ [a, false],
133
+ [b, true],
134
+ ]);
135
+ });
136
+
137
+ it('keeps a celebration through disorder only when the ruleset says so', (): void => {
138
+ const { pendingEffects, events, state, a, turn } = setUp();
139
+
140
+ state.a = { disorder: false, celebrating: true };
141
+ state.b = { disorder: false, celebrating: false };
142
+
143
+ turn();
144
+
145
+ state.a = { disorder: true, celebrating: false };
146
+ events.length = 0;
147
+
148
+ turn({ celebrationSurvivesDisorder: true });
149
+
150
+ expect(cityStatus(CHEERFUL, a, pendingEffects)).not.null;
151
+ expect(events).eql([['city:civil-disorder', a]]);
152
+
153
+ turn();
154
+
155
+ expect(cityStatus(CHEERFUL, a, pendingEffects)).null;
156
+ expect(events).eql([
157
+ ['city:civil-disorder', a],
158
+ ['city:civil-disorder', a],
159
+ ['city:celebration-ended', a],
160
+ ]);
161
+ });
162
+
163
+ it('drops a celebration without a word when the ruleset forgets it', (): void => {
164
+ const { pendingEffects, events, state, a, turn } = setUp(),
165
+ asked: boolean[] = [];
166
+
167
+ state.a = { disorder: false, celebrating: true };
168
+ state.b = { disorder: false, celebrating: false };
169
+
170
+ turn();
171
+
172
+ state.a = { disorder: true, celebrating: false };
173
+ events.length = 0;
174
+
175
+ turn({
176
+ forgetCelebration: ({ disorder }: CityStatusCheck) => {
177
+ asked.push(disorder);
178
+
179
+ return disorder;
180
+ },
181
+ });
182
+
183
+ expect(asked).eql([true]);
184
+ expect(cityStatus(CHEERFUL, a, pendingEffects)).null;
185
+ expect(events).eql([['city:civil-disorder', a]]);
186
+ });
187
+ });