Can have intelligent strike after first hit
This commit is contained in:
parent
d1e8efc764
commit
dd0ca5941a
@ -97,6 +97,26 @@ impl Coordinates {
|
||||
&& 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 {
|
||||
format!(
|
||||
"{}:{}",
|
||||
@ -111,9 +131,9 @@ impl Coordinates {
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct BoatPosition {
|
||||
start: Coordinates,
|
||||
len: usize,
|
||||
direction: BoatDirection,
|
||||
pub start: Coordinates,
|
||||
pub len: usize,
|
||||
pub direction: BoatDirection,
|
||||
}
|
||||
|
||||
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>);
|
||||
|
||||
impl BoatsLayout {
|
||||
|
@ -4,7 +4,7 @@ use crate::data::{
|
||||
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 boats: BoatsLayout,
|
||||
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 rules: GameRules,
|
||||
pub your_map: CurrentGameMapStatus,
|
||||
@ -93,6 +93,107 @@ impl CurrentGameStatus {
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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()))
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,12 @@ pub struct GameRules {
|
||||
pub bot_type: BotType,
|
||||
}
|
||||
|
||||
impl Default for GameRules {
|
||||
fn default() -> Self {
|
||||
Self::random_players_rules()
|
||||
}
|
||||
}
|
||||
|
||||
impl GameRules {
|
||||
pub fn random_players_rules() -> Self {
|
||||
Self {
|
||||
|
Loading…
Reference in New Issue
Block a user