Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Add second airport to the map (#46)
Browse files Browse the repository at this point in the history
The map and the systems working with it have been refactored to support
multiple airports in the game. The map now has a list of airports, both
in its internal representation and in the API message type. The map has
two airports by default, one in blue and one in red. Airplanes are
spawned with either color with a distribution of 50/50.
  • Loading branch information
jdno authored Mar 24, 2022
1 parent 4be1d86 commit 7a4d5f9
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 117 deletions.
2 changes: 1 addition & 1 deletion api/atc/v1/map.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ message Airport {
}

message Map {
Airport airport = 1;
repeated Airport airports = 1;
repeated Node routing_grid = 2;
}

Expand Down
28 changes: 19 additions & 9 deletions game/src/api/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,25 @@ mod tests {
let map = response.into_inner().map.unwrap();

assert_eq!(
Airport {
node: Some(Node {
longitude: 0,
latitude: 0,
restricted: false
}),
tag: Tag::Red.into()
},
map.airport.unwrap()
vec![
Airport {
node: Some(Node {
longitude: -2,
latitude: -2,
restricted: false
}),
tag: Tag::Red.into()
},
Airport {
node: Some(Node {
longitude: 1,
latitude: 4,
restricted: false
}),
tag: Tag::Blue.into()
}
],
map.airports
);
}

Expand Down
2 changes: 2 additions & 0 deletions game/src/components/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::api::AsApi;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Component)]
pub enum Tag {
Blue,
Red,
}

Expand All @@ -14,6 +15,7 @@ impl AsApi for Tag {

fn as_api(&self) -> Self::ApiType {
match self {
Tag::Blue => ApiTag::Blue,
Tag::Red => ApiTag::Red,
}
}
Expand Down
46 changes: 29 additions & 17 deletions game/src/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub const MAP_WIDTH_RANGE: RangeInclusive<i32> = -(MAP_WIDTH as i32 / 2)..=(MAP_

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Map {
airport: Airport,
airports: Vec<Airport>,
routing_grid: Vec<Node>,
}

Expand All @@ -42,8 +42,8 @@ impl Map {
Self::default()
}

pub fn airport(&self) -> &Airport {
&self.airport
pub fn airports(&self) -> &[Airport] {
&self.airports
}

pub fn routing_grid(&self) -> &Vec<Node> {
Expand All @@ -53,11 +53,15 @@ impl Map {

impl Default for Map {
fn default() -> Self {
let airport = Airport::new(Node::unrestricted(0, 0), Direction::West, Tag::Red);
let routing_grid = generate_routing_grid(&airport);
let airports = vec![
Airport::new(Node::unrestricted(-2, -2), Direction::West, Tag::Red),
Airport::new(Node::unrestricted(1, 4), Direction::South, Tag::Blue),
];

let routing_grid = generate_routing_grid(&airports);

Self {
airport,
airports,
routing_grid,
}
}
Expand All @@ -68,28 +72,36 @@ impl AsApi for Map {

fn as_api(&self) -> Self::ApiType {
ApiMap {
airport: Some(self.airport.as_api()),
airports: self
.airports
.iter()
.map(|airport| airport.as_api())
.collect(),
routing_grid: self.routing_grid.iter().map(|node| node.as_api()).collect(),
}
}
}

fn generate_routing_grid(airport: &Airport) -> Vec<Node> {
let airport_node = airport.node();
fn generate_routing_grid(airports: &[Airport]) -> Vec<Node> {
let mut nodes = Vec::with_capacity(MAP_WIDTH * MAP_HEIGHT);

for y in MAP_HEIGHT_RANGE {
for x in MAP_WIDTH_RANGE {
let node = Node::unrestricted(x, y);
nodes.push(Node::unrestricted(x, y));
}
}

let direction_to_airport =
Direction::between(&node.as_point(), &airport_node.as_point());
for airport in airports {
let airport_node = airport.node();

let restricted = airport_node != &node
&& airport_node.is_neighbor(&node)
&& direction_to_airport != airport.runway();
for neighbor in airport_node.neighbors() {
let direction_to_airport =
Direction::between(&neighbor.as_point(), &airport_node.as_point());

nodes.push(Node::new(x, y, restricted));
if direction_to_airport != airport.runway() {
*nodes.get_mut(neighbor.as_index()).unwrap() =
Node::restricted(neighbor.longitude(), neighbor.latitude());
}
}
}

Expand All @@ -106,7 +118,7 @@ mod tests {
fn generate_routing_grid_removes_neighbors() {
let map = Map::default();

let airport = map.airport().node();
let airport = map.airports().get(0).unwrap().node();
let neighbors = vec![
Node::restricted(airport.longitude(), airport.latitude() + 1),
Node::restricted(airport.longitude() + 1, airport.latitude() + 1),
Expand Down
34 changes: 23 additions & 11 deletions game/src/map/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use geo::{point, Point};
use atc::v1::Node as ApiNode;

use crate::api::AsApi;
use crate::map::{MAP_HEIGHT_RANGE, MAP_WIDTH_RANGE};
use crate::map::{MAP_HEIGHT, MAP_HEIGHT_RANGE, MAP_WIDTH, MAP_WIDTH_RANGE};
use crate::TILE_SIZE;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
Expand All @@ -18,15 +18,6 @@ pub struct Node {
}

impl Node {
pub fn new(longitude: i32, latitude: i32, restricted: bool) -> Self {
Self {
longitude,
latitude,
restricted,
}
}

#[cfg(test)]
pub fn restricted(longitude: i32, latitude: i32) -> Self {
Self {
longitude,
Expand Down Expand Up @@ -86,6 +77,15 @@ impl Node {
delta_x.abs() <= 1 && delta_y.abs() <= 1
}

pub fn as_index(&self) -> usize {
let x = self.longitude + MAP_WIDTH as i32 / 2;
let y = self.latitude + MAP_HEIGHT as i32 / 2;

let index = (y * MAP_WIDTH as i32) + x;

index as usize
}

pub fn as_point(&self) -> Point<f32> {
let x = (self.longitude * TILE_SIZE) as f32;
let y = (self.latitude * TILE_SIZE) as f32;
Expand Down Expand Up @@ -141,7 +141,7 @@ impl AsApi for Node {
mod tests {
use geo::point;

use crate::map::{MAP_HEIGHT_RANGE, MAP_WIDTH_RANGE};
use crate::map::{MAP_HEIGHT, MAP_HEIGHT_RANGE, MAP_WIDTH, MAP_WIDTH_RANGE};

use super::{Node, TILE_SIZE};

Expand Down Expand Up @@ -220,6 +220,18 @@ mod tests {
assert!(!neighbor.is_neighbor(&node));
}

#[test]
fn as_index_at_bottom_left() {
let node = Node::unrestricted(*MAP_WIDTH_RANGE.start(), *MAP_HEIGHT_RANGE.start());
assert_eq!(0, node.as_index());
}

#[test]
fn as_index_at_top_right() {
let node = Node::unrestricted(*MAP_WIDTH_RANGE.end(), *MAP_HEIGHT_RANGE.end());
assert_eq!((MAP_WIDTH * MAP_HEIGHT) - 1, node.as_index());
}

#[test]
fn trait_display() {
let node = Node::unrestricted(1, 2);
Expand Down
62 changes: 33 additions & 29 deletions game/src/systems/despawn_airplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,48 @@ pub fn despawn_airplane(
mut query: Query<(Entity, &AirplaneId, &mut FlightPlan, &Tag, &Transform), With<Landing>>,
event_bus: Local<EventBus>,
) {
let airport_location = map.airport().node().as_vec3(2.0);
let airport_tag = map.airport().tag();

for (entity, airplane_id, mut flight_plan, tag, transform) in query.iter_mut() {
let airplane_location = transform.translation;
for airport in map.airports() {
let airport_location = airport.node().as_vec3(2.0);
let airport_tag = airport.tag();

if airplane_location != airport_location {
continue;
}
let airplane_location = transform.translation;

if airplane_location != airport_location {
continue;
}

if tag == &airport_tag {
commands.entity(entity).despawn_recursive();

if tag == &airport_tag {
commands.entity(entity).despawn_recursive();
score.increment();

score.increment();
event_bus
.sender()
.send(Event::AirplaneLanded(airplane_id.clone()))
.expect("failed to send event"); // TODO: Handle error
} else {
let go_around = go_around_procedure(airport);

event_bus
.sender()
.send(Event::AirplaneLanded(airplane_id.clone()))
.expect("failed to send event"); // TODO: Handle error
} else {
let go_around = go_around_procedure(map.airport());
*flight_plan = FlightPlan::new(vec![go_around]);

*flight_plan = FlightPlan::new(vec![go_around]);
commands.entity(entity).remove::<Landing>();

commands.entity(entity).remove::<Landing>();
event_bus
.sender()
.send(Event::LandingAborted(airplane_id.clone()))
.expect("failed to send event"); // TODO: Handle error

event_bus
.sender()
.send(Event::LandingAborted(airplane_id.clone()))
.expect("failed to send event"); // TODO: Handle error
event_bus
.sender()
.send(Event::FlightPlanUpdated(
airplane_id.clone(),
flight_plan.clone(),
))
.expect("failed to send event"); // TODO: Handle error
}

event_bus
.sender()
.send(Event::FlightPlanUpdated(
airplane_id.clone(),
flight_plan.clone(),
))
.expect("failed to send event"); // TODO: Handle error
break;
}
}
}
Expand Down
42 changes: 12 additions & 30 deletions game/src/systems/generate_flight_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,52 +43,34 @@ pub fn generate_random_plan(travelled_route: &TravelledRoute, map: &Res<Map>) ->
let mut next_node;

for _ in 0..FLIGHT_PLAN_LENGTH {
next_node = pick_next_tile(&current_node, &previous_node, map.routing_grid());
next_node = pick_next_tile(&current_node, &previous_node, map);
flight_plan.push(next_node);

if &next_node == map.airport().node() {
break;
}

previous_node = Some(current_node);
current_node = next_node;
}

FlightPlan::new(flight_plan.iter().rev().cloned().collect())
}

fn pick_next_tile(
current_tile: &Node,
previous_tile: &Option<Node>,
routing_grid: &[Node],
) -> Node {
let mut potential_tiles = current_tile.neighbors();

// Mustn't be the previous tile
if let Some(previous_tile) = previous_tile {
if let Some(index) = potential_tiles
.iter()
.position(|tile| tile == previous_tile)
{
potential_tiles.remove(index);
}
}

// Mustn't be a restricted node
let potential_tiles: Vec<Node> = potential_tiles
fn pick_next_tile(current_tile: &Node, previous_tile: &Option<Node>, map: &Map) -> Node {
let airports: Vec<&Node> = map
.airports()
.iter()
.filter(|tile| routing_grid.contains(*tile))
.cloned()
.map(|airport| airport.node())
.collect();

// Shouldn't be too close to the edge of the map
let potential_tiles: Vec<Node> = potential_tiles
.iter()
let potential_tiles: Vec<Node> = current_tile
.neighbors()
.into_iter()
.filter(|node| &Some(*node) != previous_tile)
.filter(|node| !airports.contains(&node))
.filter(|node| !node.is_restricted())
.filter(|tile| {
// Shouldn't be too close to the edge of the map
tile.longitude().abs() != *MAP_WIDTH_RANGE.end()
&& tile.latitude().abs() != *MAP_HEIGHT_RANGE.end()
})
.cloned()
.collect();

// Pick random neighbor of the current tile
Expand Down
8 changes: 4 additions & 4 deletions game/src/systems/land_airplane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ pub fn land_airplane(
map: Res<Map>,
query: Query<(Entity, &FlightPlan), Without<Landing>>,
) {
let airport = map.airport();

for (entity, flight_plan) in query.iter() {
if flight_plan.get().len() != 1 {
continue;
}

let final_node = flight_plan.get().get(0).unwrap();

if final_node == airport.node() {
commands.entity(entity).insert(Landing);
for airport in map.airports() {
if final_node == airport.node() {
commands.entity(entity).insert(Landing);
}
}
}
}
Loading

0 comments on commit 7a4d5f9

Please sign in to comment.