diff options
Diffstat (limited to 'tests/test_conflict.py')
| -rw-r--r-- | tests/test_conflict.py | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/tests/test_conflict.py b/tests/test_conflict.py new file mode 100644 index 0000000..7fd7f6e --- /dev/null +++ b/tests/test_conflict.py @@ -0,0 +1,77 @@ +import unittest + +import numpy as np + +from tests.helpers import make_race +from worldhistory.conflict import apply_conflict, apply_curse, conflict +from worldhistory.state import new_state + + +def two(ca, cb): + return [make_race("a", conflict=ca), make_race("b", conflict=cb)] + + +class ConflictTest(unittest.TestCase): + def test_internal_scales_with_crowding(self): + r = [make_race("a", conflict={"internal": 0.01})] + loss, _ = conflict(np.array([[100.0, 100.0]]), np.ones((1, 2)), np.array([[0.5, 1.0]]), r) + np.testing.assert_allclose(loss[0], [0.5, 1.0]) + + def test_no_border_losses_alone_or_within_family(self): + r = two({"border": 0.1}, {"border": 0.1}) + r[1].family = "a" + loss, press = conflict(np.array([[50.0], [50.0]]), np.ones((2, 1)), np.zeros((2, 1)), r) + np.testing.assert_array_equal(loss, 0) + np.testing.assert_array_equal(press, 0) + + def test_warlike_neighbour_hurts_more(self): + P = np.array([[50.0], [50.0]]) + r = two({"border": 0.1, "aggression": 0.1}, {"border": 0.1, "aggression": 0.9}) + loss, press = conflict(P, np.zeros((2, 1)), np.zeros((2, 1)), r) + self.assertGreater(loss[0, 0], loss[1, 0]) + self.assertGreater(press[0, 0], press[1, 0]) + # a: attacked = 0.9*1/1*0.5 = 0.45; attacking b outside b's core = 0 -> loss 0.1*0.45*50 + self.assertAlmostEqual(float(loss[0, 0]), 0.1 * 0.45 * 50) + + def test_defend_in_core_raises_attacker_losses(self): + P = np.array([[50.0], [50.0]]) + base = two({"border": 0.1, "aggression": 0.8}, {"border": 0.1, "defend": 1.0}) + hard = two({"border": 0.1, "aggression": 0.8}, {"border": 0.1, "defend": 3.0}) + core = np.array([[0.0], [1.0]]) # the cell is b's core habitat + l1, _ = conflict(P, core, np.zeros((2, 1)), base) + l3, _ = conflict(P, core, np.zeros((2, 1)), hard) + self.assertGreater(l3[0, 0], l1[0, 0]) + + def test_yield_turns_losses_into_moves(self): + st = new_state(1, 1, np.zeros((1, 6)), seed=0) + st.P[0, 0] = 100.0 + apply_conflict(st, np.array([[10.0]]), [make_race(conflict={"yield": 0.8})]) + self.assertAlmostEqual(float(st.P[0, 0]), 98.0) + self.assertAlmostEqual(float(st.push[0, 0]), 8.0) + + +class DreadCurseTest(unittest.TestCase): + def test_dread_deters_attacks_and_repels(self): + P = np.array([[50.0], [50.0]]) + plain = two({"border": 0.1}, {"border": 0.1, "aggression": 0.9}) + feared = two({"border": 0.1, "dread": 0.8}, {"border": 0.1, "aggression": 0.9}) + l0, p0 = conflict(P, np.zeros((2, 1)), np.zeros((2, 1)), plain) + l1, p1 = conflict(P, np.zeros((2, 1)), np.zeros((2, 1)), feared) + self.assertAlmostEqual(float(l1[0, 0]), 0.2 * float(l0[0, 0])) # b's attacks on a × (1 − 0.8) + self.assertAlmostEqual(float(p1[1, 0]), float(p0[1, 0]) + 0.8 * 0.5) # b is repelled by a's dread × a's share + + def test_blame_falls_on_attackers_of_cursing_race(self): + P = np.array([[50.0], [50.0]]) + r = two({"border": 0.1, "curse": 2.0}, {"border": 0.1, "aggression": 0.9}) + blame = np.zeros((2, 1)) + conflict(P, np.zeros((2, 1)), np.zeros((2, 1)), r, blame=blame) + # share of a's clan killed by b = border 0.1 × attacked 0.9·1·0.5 = 0.045; × curse 2 + np.testing.assert_allclose(blame[:, 0], [0.0, 0.09]) + + def test_curse_kills_and_fades(self): + st = new_state(1, 2, np.zeros((1, 6)), seed=0) + st.P[0] = [100.0, 100.0] + apply_curse(st, np.array([[0.1, 0.0]]), decay=0.5) + np.testing.assert_allclose(st.P[0], [90.0, 100.0]) + apply_curse(st, np.zeros((1, 2)), decay=0.5) + np.testing.assert_allclose(st.P[0], [85.5, 100.0]) # curse halves: 5 % this step |
