Can have intelligent strike after first hit

This commit is contained in:
Pierre HUBERT 2022-09-18 19:02:04 +02:00
parent d1e8efc764
commit dd0ca5941a
3 changed files with 224 additions and 6 deletions

View File

@ -97,6 +97,26 @@ impl Coordinates {
&& self.y < rules.map_height as i32 && self.y < rules.map_height as i32
} }
pub fn add_x<E>(&self, x: E) -> Self
where
E: Into<i32>,
{
Self {
x: self.x + x.into(),
y: self.y,
}
}
pub fn add_y<E>(&self, y: E) -> Self
where
E: Into<i32>,
{
Self {
x: self.x,
y: self.y + y.into(),
}
}
pub fn human_print(&self) -> String { pub fn human_print(&self) -> String {
format!( format!(
"{}:{}", "{}:{}",
@ -111,9 +131,9 @@ impl Coordinates {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
pub struct BoatPosition { pub struct BoatPosition {
start: Coordinates, pub start: Coordinates,
len: usize, pub len: usize,
direction: BoatDirection, pub direction: BoatDirection,
} }
impl BoatPosition { impl BoatPosition {
@ -147,7 +167,7 @@ impl BoatPosition {
} }
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]
pub struct BoatsLayout(Vec<BoatPosition>); pub struct BoatsLayout(Vec<BoatPosition>);
impl BoatsLayout { impl BoatsLayout {

View File

@ -4,7 +4,7 @@ use crate::data::{
BoatPosition, BoatsLayout, Coordinates, GameRules, MapCellContent, PrintableMap, BoatPosition, BoatsLayout, Coordinates, GameRules, MapCellContent, PrintableMap,
}; };
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
pub struct CurrentGameMapStatus { pub struct CurrentGameMapStatus {
pub boats: BoatsLayout, pub boats: BoatsLayout,
pub successful_strikes: Vec<Coordinates>, pub successful_strikes: Vec<Coordinates>,
@ -51,7 +51,7 @@ impl PrintableMap for PrintableCurrentGameMapStatus {
} }
} }
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
pub struct CurrentGameStatus { pub struct CurrentGameStatus {
pub rules: GameRules, pub rules: GameRules,
pub your_map: CurrentGameMapStatus, pub your_map: CurrentGameMapStatus,
@ -93,6 +93,107 @@ impl CurrentGameStatus {
panic!("Could not find fire location!") panic!("Could not find fire location!")
} }
pub fn get_sunk_locations(&self) -> Vec<Coordinates> {
self.opponent_map
.sunk_boats
.iter()
.map(|f| f.all_coordinates())
.reduce(|mut a, mut b| {
a.append(&mut b);
a
})
.unwrap_or_default()
}
pub fn get_successful_but_un_sunk_locations(&self) -> Vec<Coordinates> {
let sunk_location = self.get_sunk_locations();
self.opponent_map
.successful_strikes
.iter()
.filter(|c| !sunk_location.contains(c))
.map(Coordinates::clone)
.collect()
}
fn test_attack_direction(
&self,
pos: &[Coordinates],
mut point: Coordinates,
add_x: i32,
add_y: i32,
) -> Option<Coordinates> {
while pos.contains(&point) {
point = point.add_x(add_x).add_y(add_y);
}
if self.can_fire_at_location(point) {
return Some(point);
}
None
}
fn horizontal_attack_attempt(
&self,
pos: &[Coordinates],
left: Coordinates,
right: Coordinates,
) -> Option<Coordinates> {
// Try right
if let Some(point) = self.test_attack_direction(pos, right, 1, 0) {
return Some(point);
}
// Try left
if let Some(point) = self.test_attack_direction(pos, left, -1, 0) {
return Some(point);
}
None
}
/// Attempt to continue an attack, if possible
pub fn continue_attack_boat(&self) -> Option<Coordinates> {
let pos = self.get_successful_but_un_sunk_locations();
if pos.is_empty() {
return None;
}
let start = pos[0];
let up = start.add_y(-1);
let down = start.add_y(1);
let left = start.add_x(-1);
let right = start.add_x(1);
// Try to do it horizontally
if !pos.contains(&up) && !pos.contains(&down) {
if let Some(c) = self.horizontal_attack_attempt(&pos, left, right) {
return Some(c);
}
}
// Try to do it vertically
// Try up
if let Some(point) = self.test_attack_direction(&pos, up, 0, -1) {
return Some(point);
}
// Try down
if let Some(point) = self.test_attack_direction(&pos, down, 0, 1) {
return Some(point);
}
// Try to do it horizontally again, but unconditionally
if let Some(c) = self.horizontal_attack_attempt(&pos, left, right) {
return Some(c);
}
// We could even panic here
None
}
pub fn print_your_map(&self) { pub fn print_your_map(&self) {
PrintableCurrentGameMapStatus(self.rules.clone(), self.your_map.clone()).print_map() PrintableCurrentGameMapStatus(self.rules.clone(), self.your_map.clone()).print_map()
} }
@ -101,3 +202,94 @@ impl CurrentGameStatus {
PrintableCurrentGameMapStatus(self.rules.clone(), self.opponent_map.clone()).print_map() PrintableCurrentGameMapStatus(self.rules.clone(), self.opponent_map.clone()).print_map()
} }
} }
#[cfg(test)]
mod test {
use crate::data::*;
#[test]
fn get_successful_but_un_sunk_locations() {
let unfinished = Coordinates::new(0, 0);
let sunk = Coordinates::new(3, 3);
let mut status = CurrentGameStatus::default();
status.opponent_map.successful_strikes.push(sunk);
status.opponent_map.successful_strikes.push(unfinished);
status.opponent_map.sunk_boats.push(BoatPosition {
start: sunk,
len: 1,
direction: BoatDirection::Left,
});
assert_eq!(status.get_sunk_locations(), vec![sunk]);
assert_eq!(
status.get_successful_but_un_sunk_locations(),
vec![unfinished]
);
}
#[test]
fn no_continue_attack() {
let status = CurrentGameStatus::default();
assert!(status.get_successful_but_un_sunk_locations().is_empty());
let next_fire = status.continue_attack_boat();
assert!(next_fire.is_none());
}
#[test]
fn continue_attack_unknown_direction() {
let unfinished = Coordinates::new(2, 2);
let possible_next_strikes = vec![
unfinished.add_x(-1),
unfinished.add_x(1),
unfinished.add_y(-1),
unfinished.add_y(1),
];
let mut status = CurrentGameStatus::default();
status.opponent_map.successful_strikes.push(unfinished);
let next_fire = status.continue_attack_boat();
assert!(next_fire.is_some());
assert!(possible_next_strikes.contains(&next_fire.unwrap()))
}
#[test]
fn continue_attack_vertically_only() {
let unfinished = Coordinates::new(2, 2);
let possible_next_strikes = vec![unfinished.add_x(-1), unfinished.add_x(2)];
let mut status = CurrentGameStatus::default();
status.opponent_map.successful_strikes.push(unfinished);
status
.opponent_map
.successful_strikes
.push(unfinished.add_x(1));
let next_fire = status.continue_attack_boat();
assert!(next_fire.is_some());
assert!(possible_next_strikes.contains(&next_fire.unwrap()))
}
#[test]
fn continue_attack_vertically_after_horizontal_fail() {
let unfinished = Coordinates::new(2, 2);
let possible_next_strikes = vec![unfinished.add_y(-1), unfinished.add_y(1)];
let mut status = CurrentGameStatus::default();
status.opponent_map.successful_strikes.push(unfinished);
status.opponent_map.failed_strikes.push(unfinished.add_x(1));
status
.opponent_map
.failed_strikes
.push(unfinished.add_x(-1));
let next_fire = status.continue_attack_boat();
assert!(next_fire.is_some());
assert!(possible_next_strikes.contains(&next_fire.unwrap()))
}
}

View File

@ -11,6 +11,12 @@ pub struct GameRules {
pub bot_type: BotType, pub bot_type: BotType,
} }
impl Default for GameRules {
fn default() -> Self {
Self::random_players_rules()
}
}
impl GameRules { impl GameRules {
pub fn random_players_rules() -> Self { pub fn random_players_rules() -> Self {
Self { Self {