Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matej's player #5

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 172 additions & 10 deletions player/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,191 @@
use std::collections::{HashMap, HashSet};

use bomber_lib::{
self,
world::{Enemy, Object, Tile, Direction},
Action, Player,
world::{Direction, Enemy, Object, Tile, TileOffset},
Action, Player,
};
use bomber_macro::wasm_export;

/// Player struct. Can contain any arbitrary data, which will carry over between turns.
#[derive(Default)]
struct MyPlayer;
struct MatejBot {
rotation: usize,
ticks: u32,
}

/// The `Player` implementation block must be decorated with `wasm_export`
/// in order to export the right shims to interface with the bevy `wasm` runtime
#[wasm_export]
impl Player for MyPlayer {
impl Player for MatejBot {
fn act(
&mut self,
_surroundings: Vec<(Tile, Option<Object>, Option<Enemy>, bomber_lib::world::TileOffset)>,
surroundings: Vec<(Tile, Option<Object>, Option<Enemy>, bomber_lib::world::TileOffset)>,
) -> Action {
// TODO - Observe your surroundings and return an Action to do this turn
Action::Move(Direction::West)
use Direction::*;
let mut allowed_directions = Direction::all();
allowed_directions.rotate_right(self.rotation % 4);
let mut allowed_directions: HashSet<Direction> = allowed_directions.into_iter().collect();

// Remove non-floors
allowed_directions.retain(|direction| {
let tile = surroundings
.iter()
.find(|s| s.3 == direction.extend(1))
.expect("direction in surroundings");

if tile.0 == Tile::Wall {
return false;
}
if matches!(tile.1, Some(Object::Bomb { .. } | Object::Crate)) {
return false;
}
if matches!(&tile.2, Some(_enemy)) {
return false;
}
true
});

// Remove bomb directions
allowed_directions.retain(|direction| !bomb_in_direction(*direction, &surroundings));

// TODO: more precise and less repetitive formula, also don't run into bombs one-tile offset but further away.
if bomb_at_offset(1, 1, &surroundings) {
allowed_directions.remove(&North);
allowed_directions.remove(&East);
}
if bomb_at_offset(1, -1, &surroundings) {
allowed_directions.remove(&South);
allowed_directions.remove(&East);
}
if bomb_at_offset(-1, 1, &surroundings) {
allowed_directions.remove(&North);
allowed_directions.remove(&West);
}
if bomb_at_offset(-1, -1, &surroundings) {
allowed_directions.remove(&South);
allowed_directions.remove(&West);
}

let mut preferred_directions: HashMap<Direction, f32> =
Direction::all().into_iter().map(|d| (d, 0.0)).collect();
for &(tile, object, _, offset) in surroundings.iter() {
// TODO: only add bonus for actually reachable (path exists) tiles.
let mut score = 0.0;
if tile == Tile::Hill {
score += 100.0;
}
if matches!(object, Some(Object::PowerUp(_))) {
score += 10.0;
}
if matches!(object, Some(Object::Crate)) {
score += 1.0;
}
if score == 0.0 {
continue;
}
score /= offset.taxicab_distance() as f32;

let TileOffset(x, y) = offset;
if x > 0 {
*preferred_directions.get_mut(&East).unwrap() += score;
}
if x < 0 {
*preferred_directions.get_mut(&West).unwrap() += score;
}
if y > 0 {
*preferred_directions.get_mut(&North).unwrap() += score;
}
if y < 0 {
*preferred_directions.get_mut(&South).unwrap() += score;
}
}

let mut preferred_directions: Vec<_> =
preferred_directions.into_iter().map(|(d, score)| (score, d)).collect();
preferred_directions.sort_by_key(|&(score, _)| (-score * 1_000_000.0) as i64);

let mut direction = None;
for (_, d) in preferred_directions.into_iter().filter(|&(s, _)| s > 0.0) {
if allowed_directions.contains(&d) {
direction = Some(d);
break;
}
}
if direction.is_none() {
direction = allowed_directions.into_iter().next()
}

// Drops a bomb every once in a while.
let drop_bomb = crate_or_player_close(&surroundings);

if (self.ticks as f64).log(2.0).fract().abs() < 0.001 {
self.rotation += 1;
}

// Finalization
self.ticks += 1;

match (drop_bomb, direction) {
(true, Some(direction)) => Action::DropBombAndMove(direction),
(true, None) => Action::DropBomb,
(false, Some(direction)) => Action::Move(direction),
(false, None) => Action::StayStill,
}
}

fn name(&self) -> String {
"noob".to_owned()
"MatBomber".into()
}

fn team_name() -> String {
"noob team".to_owned()
"Prague Bombers".into()
}
}

fn bomb_in_direction(
direction: Direction,
surroundings: &[(Tile, Option<Object>, Option<Enemy>, bomber_lib::world::TileOffset)],
) -> bool {
// TODO: only count bombs that are not behind walls, count their range..
for &(_, object, _, offset) in surroundings.iter() {
if !matches!(object, Some(Object::Bomb { .. })) {
continue;
}

if let Ok(offset_direction) = Direction::try_from(offset) {
if offset_direction == direction {
return true;
}
}
}
false
}

fn bomb_at_offset(
x: i32,
y: i32,
surroundings: &[(Tile, Option<Object>, Option<Enemy>, bomber_lib::world::TileOffset)],
) -> bool {
let tile = surroundings
.iter()
.find(|&(_, _, _, offset)| offset == &TileOffset(x, y))
.expect("tile exists");

let &(_, object, _, _) = tile;
matches!(object, Some(Object::Bomb { .. }))
}

fn crate_or_player_close(
surroundings: &[(Tile, Option<Object>, Option<Enemy>, bomber_lib::world::TileOffset)],
) -> bool {
surroundings
.iter()
.filter(|&(_, _, _, offset)| offset.is_orthogonally_adjacent())
.any(|(_, object, _, _)| matches!(object, Some(Object::Crate)))
|| surroundings
.iter()
.filter(|&(_, _, _, offset)| {
(offset.0 == 0 || offset.1 == 1) && offset.taxicab_distance() <= 2
})
.any(|(_, _, enemy, _)| enemy.is_some())
}