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
|
&& 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 {
|
||||||
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user