Skip to content

Commit 4f3c0ce

Browse files
committed
Calculate residential tax revenue from population\
Issue: #13
1 parent 7914dd4 commit 4f3c0ce

6 files changed

Lines changed: 155 additions & 24528 deletions

File tree

‎Windows/Cities-Box.cpp/Cities-Box.cpp/App/log.txt‎

Lines changed: 23 additions & 24498 deletions
Large diffs are not rendered by default.
Binary file not shown.
Binary file not shown.

‎src/Menu.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ bool Menu::budgetMenu() {
501501
drawRow(U"Fire", Format(budget.fire), 1, [&]{ decreaseBudget(budget.fire); }, [&]{ increaseBudget(budget.fire); });
502502
drawRow(U"Post", Format(budget.post), 2, [&]{ decreaseBudget(budget.post); }, [&]{ increaseBudget(budget.post); });
503503
drawRow(U"Education", Format(budget.education), 3, [&]{ decreaseBudget(budget.education); }, [&]{ increaseBudget(budget.education); });
504-
drawRow(U"Residential", U"{:.1f}"_fmt(tax.residential), 4, [&]{ tax.residential -= 1.0; }, [&]{ tax.residential += 1.0; });
504+
drawRow(U"Residential", U"{:.1f}%"_fmt(tax.residential), 4, [&]{ tax.residential -= 1.0; }, [&]{ tax.residential += 1.0; });
505505
drawRow(U"Commercial", U"{:.1f}"_fmt(tax.commercial), 5, [&]{ tax.commercial -= 1.0; }, [&]{ tax.commercial += 1.0; });
506506
drawRow(U"Office", U"{:.1f}"_fmt(tax.office), 6, [&]{ tax.office -= 1.0; }, [&]{ tax.office += 1.0; });
507507
drawRow(U"Industrial", U"{:.1f}"_fmt(tax.industrial), 7, [&]{ tax.industrial -= 1.0; }, [&]{ tax.industrial += 1.0; });

‎src/citiesbox-rs/src/citymap/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ mod tests {
536536
simulation_objects,
537537
);
538538

539-
assert_eq!(update.snapshot.money, 99_015);
539+
assert_eq!(update.snapshot.money, 99_014);
540540
}
541541

542542
#[test]

‎src/citiesbox-rs/src/simulation/finance.rs‎

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use crate::citymap::ffi;
33
use std::collections::HashSet;
44

55
const CONSTRUCTION_COST: i32 = 5;
6+
const RESIDENTIAL_TAX_BASE_PER_RESIDENT: f64 = 100.0;
67

78
// Values are shared with CategoryID::Type in src/Enums.hpp.
8-
const CATEGORY_RESIDENTIAL: i32 = 19;
99
const CATEGORY_COMMERCIAL: i32 = 20;
1010
const CATEGORY_OFFICE: i32 = 21;
1111
const CATEGORY_INDUSTRIAL: i32 = 22;
@@ -17,7 +17,6 @@ const CATEGORY_EDUCATION: i32 = 28;
1717

1818
#[derive(Default)]
1919
struct FinanceObjectCounts {
20-
residential: i64,
2120
commercial: i64,
2221
office: i64,
2322
industrial: i64,
@@ -53,17 +52,21 @@ impl SimulationState {
5352
)
5453
});
5554

56-
let income = [
57-
(counts.residential, self.tax_residential),
58-
(counts.commercial, self.tax_commercial),
59-
(counts.office, self.tax_office),
60-
(counts.industrial, self.tax_industrial),
61-
(counts.farm, self.tax_farm),
62-
]
63-
.into_iter()
64-
.map(|(count, tax)| count as f64 * finite_nonnegative(tax))
65-
.sum::<f64>()
66-
.trunc() as i64;
55+
let residential_income = f64::from(self.population.max(0))
56+
* RESIDENTIAL_TAX_BASE_PER_RESIDENT
57+
* finite_nonnegative(self.tax_residential)
58+
/ 100.0;
59+
let income = residential_income
60+
+ [
61+
(counts.commercial, self.tax_commercial),
62+
(counts.office, self.tax_office),
63+
(counts.industrial, self.tax_industrial),
64+
(counts.farm, self.tax_farm),
65+
]
66+
.into_iter()
67+
.map(|(count, tax)| count as f64 * finite_nonnegative(tax))
68+
.sum::<f64>();
69+
let income = income.trunc() as i64;
6770

6871
let updated_money = i64::from(self.money)
6972
.saturating_add(income)
@@ -82,7 +85,6 @@ fn count_finance_objects(simulation_objects: &[ffi::SimulationObjectState]) -> F
8285
}
8386

8487
let categories = &object.category_ids;
85-
counts.residential += i64::from(categories.contains(&CATEGORY_RESIDENTIAL));
8688
counts.commercial += i64::from(categories.contains(&CATEGORY_COMMERCIAL));
8789
counts.office += i64::from(categories.contains(&CATEGORY_OFFICE));
8890
counts.industrial += i64::from(categories.contains(&CATEGORY_INDUSTRIAL));
@@ -109,11 +111,10 @@ mod tests {
109111
use super::{
110112
CATEGORY_COMMERCIAL, CATEGORY_EDUCATION, CATEGORY_FARM, CATEGORY_FIRE_DEPARTMENT,
111113
CATEGORY_INDUSTRIAL, CATEGORY_OFFICE, CATEGORY_POLICE, CATEGORY_POST_OFFICE,
112-
CATEGORY_RESIDENTIAL,
113114
};
114115
use crate::{
115116
citymap::ffi,
116-
simulation::test_support::{state_at, FixedRandom},
117+
simulation::test_support::{FixedRandom, residential_tile, state_at},
117118
};
118119

119120
fn simulation_objects(
@@ -146,8 +147,8 @@ mod tests {
146147
state.tax_office = 9.0;
147148
state.tax_industrial = 10.0;
148149
state.tax_farm = 11.0;
150+
let mut residential_tiles = vec![residential_tile(10, 10)];
149151
let objects = simulation_objects(&[
150-
(CATEGORY_RESIDENTIAL, 10),
151152
(CATEGORY_COMMERCIAL, 20),
152153
(CATEGORY_OFFICE, 30),
153154
(CATEGORY_INDUSTRIAL, 40),
@@ -159,7 +160,15 @@ mod tests {
159160
]);
160161
let mut random = FixedRandom::new([]);
161162

162-
state.update_world_with_source(1, &mut [], &mut [], &mut [], &[], &objects, &mut random);
163+
state.update_world_with_source(
164+
1,
165+
&mut residential_tiles,
166+
&mut [],
167+
&mut [],
168+
&[],
169+
&objects,
170+
&mut random,
171+
);
163172

164173
assert_eq!((state.time.month, state.time.date), (5, 1));
165174
assert_eq!(state.money, 71_450);
@@ -169,7 +178,7 @@ mod tests {
169178
fn does_not_update_finances_on_other_dates() {
170179
let mut state = state_at(2024, 5, 1, 23, 59);
171180
state.money = 100_000;
172-
let objects = simulation_objects(&[(CATEGORY_RESIDENTIAL, 10), (CATEGORY_POLICE, 1)]);
181+
let objects = simulation_objects(&[(CATEGORY_POLICE, 1)]);
173182
let mut random = FixedRandom::new([]);
174183

175184
state.update_world_with_source(1, &mut [], &mut [], &mut [], &[], &objects, &mut random);
@@ -187,16 +196,16 @@ mod tests {
187196
state.budget_fire = 0;
188197
state.budget_post = 0;
189198
state.budget_education = 0;
190-
let objects = simulation_objects(&[(CATEGORY_RESIDENTIAL, 1)]);
199+
let mut residential_tiles = vec![residential_tile(1, 1)];
191200
let mut random = FixedRandom::new([]);
192201

193202
state.update_world_with_source(
194203
30 * 24 * 60,
195-
&mut [],
204+
&mut residential_tiles,
196205
&mut [],
197206
&mut [],
198207
&[],
199-
&objects,
208+
&[],
200209
&mut random,
201210
);
202211

@@ -213,10 +222,21 @@ mod tests {
213222
state.budget_fire = 0;
214223
state.budget_post = 0;
215224
state.budget_education = 0;
216-
let objects = simulation_objects(&[(CATEGORY_RESIDENTIAL, 1)]);
225+
let mut tile = residential_tile(1, 1);
226+
tile.ages.clear();
227+
tile.genders.clear();
228+
let mut residential_tiles = vec![tile];
217229
let mut random = FixedRandom::new([]);
218230

219-
state.update_world_with_source(1, &mut [], &mut [], &mut [], &[], &objects, &mut random);
231+
state.update_world_with_source(
232+
1,
233+
&mut residential_tiles,
234+
&mut [],
235+
&mut [],
236+
&[],
237+
&[],
238+
&mut random,
239+
);
220240

221241
assert_eq!(
222242
(state.time.year, state.time.month, state.time.date),
@@ -244,10 +264,19 @@ mod tests {
244264
farm: 0.0,
245265
},
246266
);
247-
let objects = simulation_objects(&[(CATEGORY_RESIDENTIAL, 10), (CATEGORY_POLICE, 1)]);
267+
let mut residential_tiles = vec![residential_tile(10, 10)];
268+
let objects = simulation_objects(&[(CATEGORY_POLICE, 1)]);
248269
let mut random = FixedRandom::new([]);
249270

250-
state.update_world_with_source(1, &mut [], &mut [], &mut [], &[], &objects, &mut random);
271+
state.update_world_with_source(
272+
1,
273+
&mut residential_tiles,
274+
&mut [],
275+
&mut [],
276+
&[],
277+
&objects,
278+
&mut random,
279+
);
251280

252281
assert_eq!(state.money, 150);
253282
}
@@ -258,14 +287,87 @@ mod tests {
258287
state.money = i32::MAX - 1;
259288
state.tax_residential = f64::MAX;
260289
state.budget_police = 100;
261-
let objects = simulation_objects(&[(CATEGORY_RESIDENTIAL, 1)]);
290+
let mut residential_tiles = vec![residential_tile(1, 1)];
262291
let mut random = FixedRandom::new([]);
263292

264-
state.update_world_with_source(1, &mut [], &mut [], &mut [], &[], &objects, &mut random);
293+
state.update_world_with_source(
294+
1,
295+
&mut residential_tiles,
296+
&mut [],
297+
&mut [],
298+
&[],
299+
&[],
300+
&mut random,
301+
);
265302

266303
assert_eq!(state.money, i32::MAX);
267304
}
268305

306+
#[test]
307+
fn residential_tax_uses_population_and_fractional_percent_rate() {
308+
let mut state = state_at(2024, 1, 1, 0, 0);
309+
state.money = 100;
310+
state.population = 100;
311+
state.tax_residential = 7.5;
312+
state.tax_commercial = 0.0;
313+
state.tax_office = 0.0;
314+
state.tax_industrial = 0.0;
315+
state.tax_farm = 0.0;
316+
state.budget_police = 0;
317+
state.budget_fire = 0;
318+
state.budget_post = 0;
319+
state.budget_education = 0;
320+
321+
state.update_monthly_finances(&[]);
322+
323+
assert_eq!(state.money, 850);
324+
}
325+
326+
#[test]
327+
fn residential_object_count_does_not_change_residential_tax() {
328+
let mut without_homes = state_at(2024, 1, 1, 0, 0);
329+
without_homes.money = 0;
330+
without_homes.population = 10;
331+
without_homes.tax_residential = 7.0;
332+
without_homes.update_monthly_finances(&[]);
333+
334+
let mut with_multi_tile_home = state_at(2024, 1, 1, 0, 0);
335+
with_multi_tile_home.money = 0;
336+
with_multi_tile_home.population = 10;
337+
with_multi_tile_home.tax_residential = 7.0;
338+
with_multi_tile_home.update_monthly_finances(&[
339+
ffi::SimulationObjectState {
340+
object_id: 42,
341+
category_ids: vec![19],
342+
},
343+
ffi::SimulationObjectState {
344+
object_id: 42,
345+
category_ids: vec![19],
346+
},
347+
]);
348+
349+
assert_eq!(without_homes.money, 70);
350+
assert_eq!(with_multi_tile_home.money, 70);
351+
}
352+
353+
#[test]
354+
fn empty_homes_and_negative_population_produce_no_residential_tax() {
355+
let residential_objects = simulation_objects(&[(19, 10)]);
356+
let mut empty_city = state_at(2024, 1, 1, 0, 0);
357+
empty_city.money = 100;
358+
empty_city.population = 0;
359+
empty_city.tax_residential = 7.0;
360+
361+
empty_city.update_monthly_finances(&residential_objects);
362+
363+
assert_eq!(empty_city.money, 100);
364+
365+
empty_city.population = -10;
366+
empty_city.update_monthly_finances(&residential_objects);
367+
368+
assert_eq!(empty_city.money, 100);
369+
}
370+
269371
#[test]
270372
fn counts_multi_tile_object_and_each_category_only_once() {
271373
let objects = vec![

0 commit comments

Comments
 (0)