Marks and dig selection in each master's colour; imps dig only their master's marks; AI marks near its ground, acts calmer, re-summons imps; allies never claim each other's ground
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
010a6a8a12
commit
773be21455
5 files changed
+161
-37
No files matched your search
@@ -10,11 +10,12 @@ use crate::game::*;
|
|||||||
use crate::map::*;
|
use crate::map::*;
|
||||||
|
|
||||||
// per difficulty (easy, medium, hard)
|
// per difficulty (easy, medium, hard)
|
||||||
const THINK: [f32; 3] = [4.0, 2.5, 1.5]; // seconds between two thoughts
|
const THINK: [f32; 3] = [5.2, 3.25, 1.95]; // seconds between two thoughts (a calm pace)
|
||||||
const MAX_RADIUS: [i32; 3] = [11, 15, 19]; // how far the dungeon grows around the heart
|
const MAX_RADIUS: [i32; 3] = [11, 15, 19]; // how far the dungeon grows around the heart
|
||||||
const MAX_IMPS: [usize; 3] = [6, 9, 12];
|
const MAX_IMPS: [usize; 3] = [5, 7, 9];
|
||||||
const FIRST_ATTACK: [f32; 3] = [720.0, 480.0, 330.0]; // no attack before (seconds)
|
const FIRST_ATTACK: [f32; 3] = [940.0, 625.0, 430.0]; // no attack before (seconds)
|
||||||
const BOLDNESS: [f32; 3] = [1.5, 1.15, 0.9]; // own army / enemy army needed to attack
|
const BOLDNESS: [f32; 3] = [1.5, 1.15, 0.9]; // own army / enemy army needed to attack
|
||||||
|
const MARK_REACH: i32 = 5; // an AI master marks only this close to its own ground
|
||||||
const MAX_TOWERS: [usize; 3] = [1, 2, 4];
|
const MAX_TOWERS: [usize; 3] = [1, 2, 4];
|
||||||
const START_GOLD: [f32; 3] = [1500.0, 2500.0, 3500.0];
|
const START_GOLD: [f32; 3] = [1500.0, 2500.0, 3500.0];
|
||||||
|
|
||||||
@@ -55,11 +56,56 @@ impl Game {
|
|||||||
self.ai_fight(k, d, side, cx, cy);
|
self.ai_fight(k, d, side, cx, cy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the tiles an AI master may mark: within MARK_REACH tiles of its own ground
|
||||||
|
fn ai_reach_mask(&self) -> Vec<bool> {
|
||||||
|
let me = self.me;
|
||||||
|
let mut dist = vec![u8::MAX; self.map.t.len()];
|
||||||
|
let mut queue = std::collections::VecDeque::new();
|
||||||
|
for (i, t) in self.map.t.iter().enumerate() {
|
||||||
|
if t.owner == me {
|
||||||
|
dist[i] = 0;
|
||||||
|
queue.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while let Some(i) = queue.pop_front() {
|
||||||
|
let d = dist[i];
|
||||||
|
if d as i32 >= MARK_REACH {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (x, y) = xy(i);
|
||||||
|
for &(dx, dy) in DIR8.iter() {
|
||||||
|
if inb(x + dx, y + dy) {
|
||||||
|
let j = idx(x + dx, y + dy);
|
||||||
|
if dist[j] > d + 1 {
|
||||||
|
dist[j] = d + 1;
|
||||||
|
queue.push_back(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dist.into_iter().map(|d| d as i32 <= MARK_REACH).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// marks a tile for this master's imps (only near its own ground)
|
||||||
|
fn ai_mark(&mut self, i: usize, mask: &[bool]) -> bool {
|
||||||
|
if !mask.get(i).copied().unwrap_or(false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let side = self.me.side().min(7);
|
||||||
|
let t = &mut self.map.t[i];
|
||||||
|
if t.marked && t.mark_by != side {
|
||||||
|
return false; // another master's plan
|
||||||
|
}
|
||||||
|
t.marked = true;
|
||||||
|
t.mark_by = side;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
// enough imps for the dungeon's size
|
// enough imps for the dungeon's size
|
||||||
fn ai_imps(&mut self, d: usize, cx: i32, cy: i32) {
|
fn ai_imps(&mut self, d: usize, cx: i32, cy: i32) {
|
||||||
let claimed = self.map.t.iter().filter(|t| t.owner == self.me).count();
|
let claimed = self.map.t.iter().filter(|t| t.owner == self.me).count();
|
||||||
let want = (4 + claimed / 30).min(MAX_IMPS[d]);
|
let want = (4 + claimed / 30).min(MAX_IMPS[d]);
|
||||||
if self.worker_count() < want && self.mana >= self.summon_cost() + 20.0 {
|
if self.worker_count() < want && self.mana >= self.summon_cost() {
|
||||||
self.summon(cx as f32 + 0.5, cy as f32 + 2.5);
|
self.summon(cx as f32 + 0.5, cy as f32 + 2.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,12 +113,13 @@ impl Game {
|
|||||||
// dig: rings of earth around the heart, veins first; the ring grows when it is dug out
|
// dig: rings of earth around the heart, veins first; the ring grows when it is dug out
|
||||||
fn ai_dig(&mut self, k: usize, d: usize, cx: i32, cy: i32) {
|
fn ai_dig(&mut self, k: usize, d: usize, cx: i32, cy: i32) {
|
||||||
let me = self.me;
|
let me = self.me;
|
||||||
|
let side = me.side().min(7);
|
||||||
let r = self.rivals[k].radius.max(5);
|
let r = self.rivals[k].radius.max(5);
|
||||||
let marked_mine = |g: &Game| {
|
let marked_mine = |g: &Game| {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
for y in cy - r - 4..=cy + r + 4 {
|
for y in cy - r - 4..=cy + r + 4 {
|
||||||
for x in cx - r - 4..=cx + r + 4 {
|
for x in cx - r - 4..=cx + r + 4 {
|
||||||
if g.map.get(x, y).map_or(false, |t| t.marked) {
|
if g.map.get(x, y).map_or(false, |t| t.marked && t.mark_by == side) {
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,13 +131,14 @@ impl Game {
|
|||||||
return; // the imps have enough to do
|
return; // the imps have enough to do
|
||||||
}
|
}
|
||||||
let mut added = 0;
|
let mut added = 0;
|
||||||
|
let mask = self.ai_reach_mask();
|
||||||
let diggable = |t: &Tile| t.diggable() && !t.plant() && !t.reinforced && t.kind != Kind::Steel && (t.owner == Owner::None || t.owner == me);
|
let diggable = |t: &Tile| t.diggable() && !t.plant() && !t.reinforced && t.kind != Kind::Steel && (t.owner == Owner::None || t.owner == me);
|
||||||
// veins a little beyond the ring, then the ring itself
|
// veins a little beyond the ring, then the ring itself
|
||||||
for pass in 0..2 {
|
for pass in 0..2 {
|
||||||
let reach = if pass == 0 { r + 3 } else { r };
|
let reach = if pass == 0 { r + 3 } else { r };
|
||||||
for y in cy - reach..=cy + reach {
|
for y in cy - reach..=cy + reach {
|
||||||
for x in cx - reach..=cx + reach {
|
for x in cx - reach..=cx + reach {
|
||||||
if added >= 16 || !inb(x, y) || x < 2 || y < 2 || x >= mw() as i32 - 2 || y >= mh() as i32 - 2 {
|
if added >= 11 || !inb(x, y) || x < 2 || y < 2 || x >= mw() as i32 - 2 || y >= mh() as i32 - 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let i = idx(x, y);
|
let i = idx(x, y);
|
||||||
@@ -98,9 +146,9 @@ impl Game {
|
|||||||
if t.marked || !diggable(&t) || (pass == 0 && t.vein().is_none()) {
|
if t.marked || !diggable(&t) || (pass == 0 && t.vein().is_none()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.map.t[i].marked = true;
|
if self.ai_mark(i, &mask) {
|
||||||
self.map.t[i].mark_by = self.me.side().min(7);
|
added += 1;
|
||||||
added += 1;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,12 +194,11 @@ impl Game {
|
|||||||
};
|
};
|
||||||
let Some(path) = found else { return false };
|
let Some(path) = found else { return false };
|
||||||
let me = self.me;
|
let me = self.me;
|
||||||
|
let mask = self.ai_reach_mask();
|
||||||
let mut cluster = Vec::new();
|
let mut cluster = Vec::new();
|
||||||
for &i in &path {
|
for &i in &path {
|
||||||
let t = self.map.t[i];
|
let t = self.map.t[i];
|
||||||
if t.diggable() && !t.plant() && t.kind != Kind::Steel && (t.owner == Owner::None || t.owner == me || t.reinforced) {
|
if t.diggable() && !t.plant() && t.kind != Kind::Steel && (t.owner == Owner::None || t.owner == me || t.reinforced) && self.ai_mark(i, &mask) {
|
||||||
self.map.t[i].marked = true;
|
|
||||||
self.map.t[i].mark_by = self.me.side().min(7);
|
|
||||||
if t.vein().is_some() {
|
if t.vein().is_some() {
|
||||||
cluster.push(i);
|
cluster.push(i);
|
||||||
}
|
}
|
||||||
@@ -163,8 +210,7 @@ impl Game {
|
|||||||
for &(dx, dy) in DIR8.iter() {
|
for &(dx, dy) in DIR8.iter() {
|
||||||
if let Some(t) = self.map.get(x + dx, y + dy) {
|
if let Some(t) = self.map.get(x + dx, y + dy) {
|
||||||
if t.vein().is_some() && t.owner == Owner::None {
|
if t.vein().is_some() && t.owner == Owner::None {
|
||||||
self.map.t[idx(x + dx, y + dy)].marked = true;
|
self.ai_mark(idx(x + dx, y + dy), &mask);
|
||||||
self.map.t[idx(x + dx, y + dy)].mark_by = self.me.side().min(7);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-9
@@ -3269,12 +3269,13 @@ impl Game {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- pit imps (workers)
|
// ---------------------------------------------------------------- pit imps (workers)
|
||||||
|
|
||||||
fn dig_neighbor(&self, i: usize, me: u32) -> Option<usize> {
|
// a neighbour of tile i that this imp may dig: marked by its own master (side)
|
||||||
|
fn dig_neighbor(&self, i: usize, me: u32, side: u8) -> Option<usize> {
|
||||||
let (x, y) = xy(i);
|
let (x, y) = xy(i);
|
||||||
let mut found = None;
|
let mut found = None;
|
||||||
for &(dx, dy) in &DIR4 {
|
for &(dx, dy) in &DIR4 {
|
||||||
if let Some(t) = self.map.get(x + dx, y + dy) {
|
if let Some(t) = self.map.get(x + dx, y + dy) {
|
||||||
if t.marked && t.diggable() {
|
if t.marked && t.mark_by == side && t.diggable() {
|
||||||
if t.reserved == me {
|
if t.reserved == me {
|
||||||
return Some(idx(x + dx, y + dy));
|
return Some(idx(x + dx, y + dy));
|
||||||
}
|
}
|
||||||
@@ -3312,14 +3313,16 @@ impl Game {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn claimable(&self, i: usize, me: u32) -> bool {
|
pub(crate) fn claimable(&self, i: usize, me: u32) -> bool {
|
||||||
let t = &self.map.t[i];
|
let t = &self.map.t[i];
|
||||||
let ok = if t.owner == self.me {
|
let ok = if t.owner == self.me {
|
||||||
false
|
false
|
||||||
} else if t.owner == Owner::None {
|
} else if t.owner == Owner::None {
|
||||||
matches!(t.kind, Kind::Floor | Kind::Portal | Kind::Bridge | Kind::LavaBridge | Kind::Shard | Kind::Treasury | Kind::ManaSpring | Kind::RuneChest)
|
matches!(t.kind, Kind::Floor | Kind::Portal | Kind::Bridge | Kind::LavaBridge | Kind::Shard | Kind::Treasury | Kind::ManaSpring | Kind::RuneChest)
|
||||||
|
} else if t.owner != Owner::Hero && self.allied(self.me_side, t.owner.side()) {
|
||||||
|
false // an ally's ground is never taken
|
||||||
} else {
|
} else {
|
||||||
// knight and other masters' ground (also vaults and shard chambers), but not while enemies fight there
|
// knight and enemy masters' ground (also vaults and shard chambers), but not while enemies fight there
|
||||||
matches!(t.kind, Kind::Floor | Kind::Treasury | Kind::Bridge | Kind::LavaBridge | Kind::Shard | Kind::Lair | Kind::Training) && !self.contested.get(i).copied().unwrap_or(false)
|
matches!(t.kind, Kind::Floor | Kind::Treasury | Kind::Bridge | Kind::LavaBridge | Kind::Shard | Kind::Lair | Kind::Training) && !self.contested.get(i).copied().unwrap_or(false)
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -3412,7 +3415,7 @@ impl Game {
|
|||||||
match e.job {
|
match e.job {
|
||||||
Job::Dig(t) => {
|
Job::Dig(t) => {
|
||||||
let tile = self.map.t[t];
|
let tile = self.map.t[t];
|
||||||
if !tile.marked || !tile.diggable() || !adj4(cur, t) {
|
if !tile.marked || tile.mark_by != e.side || !tile.diggable() || !adj4(cur, t) {
|
||||||
self.unreserve(me);
|
self.unreserve(me);
|
||||||
e.job = Job::None;
|
e.job = Job::None;
|
||||||
return;
|
return;
|
||||||
@@ -3647,9 +3650,9 @@ impl Game {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.dig_neighbor(i, me).is_some(), true) {
|
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.dig_neighbor(i, me, e.side).is_some(), true) {
|
||||||
let end = p.last().copied().unwrap_or(cur);
|
let end = p.last().copied().unwrap_or(cur);
|
||||||
if let Some(t) = self.dig_neighbor(end, me) {
|
if let Some(t) = self.dig_neighbor(end, me, e.side) {
|
||||||
self.map.t[t].reserved = me;
|
self.map.t[t].reserved = me;
|
||||||
e.path = p;
|
e.path = p;
|
||||||
e.job = Job::Dig(t);
|
e.job = Job::Dig(t);
|
||||||
@@ -4851,9 +4854,11 @@ impl Game {
|
|||||||
for x in x0.min(x1)..=x0.max(x1) {
|
for x in x0.min(x1)..=x0.max(x1) {
|
||||||
if inb(x, y) {
|
if inb(x, y) {
|
||||||
let t = &mut self.map.t[idx(x, y)];
|
let t = &mut self.map.t[idx(x, y)];
|
||||||
if t.diggable() {
|
// a master sets and clears only his own marks
|
||||||
|
let side = self.me.side().min(7);
|
||||||
|
if t.diggable() && (!t.marked || t.mark_by == side) {
|
||||||
t.marked = on;
|
t.marked = on;
|
||||||
t.mark_by = self.me.side().min(7);
|
t.mark_by = side;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -2097,7 +2097,11 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
|
|||||||
let ground = app.cam.ground_point(mx, my);
|
let ground = app.cam.ground_point(mx, my);
|
||||||
clear_background(Color::from_rgba(4, 3, 5, 255));
|
clear_background(Color::from_rgba(4, 3, 5, 255));
|
||||||
let (color, radius) = match ui.tool {
|
let (color, radius) = match ui.tool {
|
||||||
Tool::Dig => (Color::new(1.0, 0.85, 0.3, 1.0), None),
|
Tool::Dig => {
|
||||||
|
// the dig selection in the master's colour (like the marks)
|
||||||
|
let c = render3d::master_rgb(app.game.local_side);
|
||||||
|
(Color::new(c.x, c.y, c.z, 1.0), None)
|
||||||
|
}
|
||||||
Tool::Hand => (Color::new(0.9, 0.75, 0.6, 1.0), None),
|
Tool::Hand => (Color::new(0.9, 0.75, 0.6, 1.0), None),
|
||||||
Tool::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None),
|
Tool::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None),
|
||||||
Tool::Sell => (Color::new(1.0, 0.5, 0.3, 1.0), None),
|
Tool::Sell => (Color::new(1.0, 0.5, 0.3, 1.0), None),
|
||||||
|
|||||||
+5
-13
@@ -675,18 +675,10 @@ fn view_side() -> u8 {
|
|||||||
VIEW_SIDE.with(|v| v.get())
|
VIEW_SIDE.with(|v| v.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
// colour of a dig mark: own marks yellow, other masters' marks in their colour
|
// colour of a dig mark: always the colour of the master who set it
|
||||||
fn mark_rgb(by: u8) -> [u8; 3] {
|
fn mark_rgb(by: u8) -> [u8; 3] {
|
||||||
if by == view_side() {
|
let c = master_rgb(by);
|
||||||
return [255, 205, 50];
|
[(c.x * 255.0) as u8, (c.y * 255.0) as u8, (c.z * 255.0) as u8]
|
||||||
}
|
|
||||||
match by {
|
|
||||||
0 => [235, 90, 125], // the first master, seen by another one
|
|
||||||
2 => [80, 240, 110], // green rival
|
|
||||||
3 => [100, 150, 255], // blue rival
|
|
||||||
4 => [255, 150, 50], // the fourth master
|
|
||||||
_ => [255, 205, 50],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glowing box in the marker's colour around a block that is marked for digging
|
// Glowing box in the marker's colour around a block that is marked for digging
|
||||||
@@ -768,9 +760,9 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
|||||||
let (xf, zf) = (x as f32, z as f32);
|
let (xf, zf) = (x as f32, z as f32);
|
||||||
if !map.shown(idx(x, z)) {
|
if !map.shown(idx(x, z)) {
|
||||||
// fog of war: a black block, with black sides towards known open ground;
|
// fog of war: a black block, with black sides towards known open ground;
|
||||||
// tiles marked for digging glow dark yellow so the plan stays visible
|
// own tiles marked for digging glow darkly in the master's colour so the plan stays visible
|
||||||
let black = col(Vec3::splat(0.0), 1.0);
|
let black = col(Vec3::splat(0.0), 1.0);
|
||||||
let top = if t.marked && t.mark_by == view_side() { col(vec3(0.42, 0.33, 0.06), 1.0) } else { black };
|
let top = if t.marked && t.mark_by == view_side() { col(master_rgb(t.mark_by) * 0.42, 1.0) } else { black };
|
||||||
mb.quad([vec3(xf, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf + 1.0), vec3(xf, WALL_H, zf + 1.0)], [ua, ua, ua, ua], [top; 4], [Vec3::Y; 4]);
|
mb.quad([vec3(xf, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf + 1.0), vec3(xf, WALL_H, zf + 1.0)], [ua, ua, ua, ua], [top; 4], [Vec3::Y; 4]);
|
||||||
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
|
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
|
||||||
let open = map.get(x + dx, z + dz).map_or(false, |o| !o.solid()) && map.shown(idx((x + dx).max(0), (z + dz).max(0)));
|
let open = map.get(x + dx, z + dz).map_or(false, |o| !o.solid()) && map.shown(idx((x + dx).max(0), (z + dz).max(0)));
|
||||||
|
|||||||
@@ -468,6 +468,60 @@ mod tests {
|
|||||||
assert!(ground > ground0 + 20, "the yellow master digs and claims: {} -> {}", ground0, ground);
|
assert!(ground > ground0 + 20, "the yellow master digs and claims: {} -> {}", ground0, ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ai_marks_stay_near_its_ground_and_imps_dig_only_their_masters_marks() {
|
||||||
|
let mut g = rival_game(2);
|
||||||
|
let side = g.rivals[0].side;
|
||||||
|
let owner = Owner::of_side(side);
|
||||||
|
for _ in 0..(10 * 60 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
if g.time as i32 % 20 == 0 {
|
||||||
|
for (i, t) in g.map.t.iter().enumerate() {
|
||||||
|
if t.marked && t.mark_by == side {
|
||||||
|
let (x, y) = crate::map::xy(i);
|
||||||
|
let near = (-5..=5).any(|dy| (-5..=5).any(|dx| g.map.get(x + dx, y + dy).map_or(false, |o| o.owner == owner)));
|
||||||
|
assert!(near, "AI mark at {},{} far from its ground", x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// a foreign mark next to the player's imps stays untouched
|
||||||
|
let (cx, cy) = g.map.core;
|
||||||
|
let spot = (0..g.map.t.len()).find(|&i| {
|
||||||
|
let (x, y) = crate::map::xy(i);
|
||||||
|
(x - cx).abs() < 10 && (y - cy).abs() < 10 && g.map.t[i].kind == Kind::Earth && !g.map.t[i].marked
|
||||||
|
&& DIR4.iter().any(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |o| o.owner == Owner::Player && o.walkable()))
|
||||||
|
}).expect("earth beside the player's floor");
|
||||||
|
g.map.t[spot].reinforced = false;
|
||||||
|
g.map.t[spot].marked = true;
|
||||||
|
g.map.t[spot].mark_by = side;
|
||||||
|
for _ in 0..(60 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
}
|
||||||
|
assert_eq!(g.map.t[spot].kind, Kind::Earth, "the player's imps leave the AI's mark alone");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_ai_summons_new_imps_when_its_imps_die() {
|
||||||
|
let mut g = rival_game(1);
|
||||||
|
let side = g.rivals[0].side;
|
||||||
|
for _ in 0..(60 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
}
|
||||||
|
for e in g.ents.iter_mut().filter(|e| e.alive && e.side == side && e.kind == EK::Worker) {
|
||||||
|
e.hp = 0.0;
|
||||||
|
}
|
||||||
|
for _ in 0..20 {
|
||||||
|
g.update(0.05);
|
||||||
|
}
|
||||||
|
assert_eq!(g.ents.iter().filter(|e| e.alive && e.side == side && e.kind == EK::Worker).count(), 0, "all its imps are dead");
|
||||||
|
for _ in 0..(3 * 60 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
}
|
||||||
|
let imps = g.ents.iter().filter(|e| e.alive && e.side == side && e.kind == EK::Worker).count();
|
||||||
|
assert!(imps >= 2, "new imps (one per 80 mana, like the player): {}", imps);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ai_dig_marks_carry_the_masters_side() {
|
fn ai_dig_marks_carry_the_masters_side() {
|
||||||
let mut g = rival_game(1);
|
let mut g = rival_game(1);
|
||||||
@@ -564,6 +618,29 @@ mod tests {
|
|||||||
g
|
g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imps_take_ground_from_enemies_but_never_from_allies() {
|
||||||
|
let mut g = offline_league_game();
|
||||||
|
assert!(g.allied(0, 2));
|
||||||
|
// a partner floor tile right beside the player's floor
|
||||||
|
let spot = (0..g.map.t.len()).find(|&i| {
|
||||||
|
let (x, y) = crate::map::xy(i);
|
||||||
|
g.map.t[i].kind == Kind::Floor && g.map.t[i].owner == Owner::Player
|
||||||
|
&& DIR4.iter().any(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |o| o.kind == Kind::Floor && o.owner == Owner::Player))
|
||||||
|
&& x > 2 && y > 2
|
||||||
|
}).expect("player floor");
|
||||||
|
let (x, y) = crate::map::xy(spot);
|
||||||
|
let n = DIR4.iter().map(|&(dx, dy)| crate::map::idx(x + dx, y + dy)).find(|&j| g.map.t[j].owner == Owner::Player).unwrap();
|
||||||
|
g.map.t[n].owner = Owner::of_side(2);
|
||||||
|
g.map.t[n].reserved = 0;
|
||||||
|
assert!(!g.claimable(n, 999), "an ally's floor stays his");
|
||||||
|
// the same tile of an enemy master may be taken
|
||||||
|
g.coop = false;
|
||||||
|
g.teams = vec![(0, 0), (2, 1)];
|
||||||
|
assert!(!g.allied(0, 2));
|
||||||
|
assert!(g.claimable(n, 999), "an enemy's floor can be claimed");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_offline_coop_partner_is_an_allied_ai_master() {
|
fn an_offline_coop_partner_is_an_allied_ai_master() {
|
||||||
let mut g = offline_league_game();
|
let mut g = offline_league_game();
|
||||||
|
|||||||
Reference in new issue
Block a user