blob: 6f8f64f89fb0a626912be356b5f7740ecd5631e6 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use core::f64::consts::PI;
use fidl_fuchsia_game_tennis as fidl_tennis;
use fidl_fuchsia_game_tennis::{GameState, PaddleProxy};
use fuchsia_syslog::fx_log_info;
use parking_lot::Mutex;
use std::f64;
use std::sync::Arc;
const TIME_SCALE_FACTOR: f64 = 0.02; // Time scale factor: scales all movements/speeds by this amount.
const BOARD_HEIGHT: f64 = 10.0;
const BOARD_WIDTH: f64 = 20.0;
const PADDLE_SPEED: f64 = 0.4 * TIME_SCALE_FACTOR; // distance paddle travels per step
const PADDLE_SIZE: f64 = 1.0; // vertical height of paddle
const PADDLE_MAX_ANGLE: f64 = std::f64::consts::PI / 4.0; // Maximum paddle angle
const BALL_SPEEDUP_MULTIPLIER: f64 = 1.05; // speed multiplier applied on every paddle bounce
#[allow(unused)]
const MAX_BOUNCE_ANGLE: f64 = 1.3; // in radians, bounce angle when hitting very top edge of paddle
pub struct Game {
state: GameState,
player_1: Option<Player>,
player_2: Option<Player>,
ball_dx: f64,
ball_dy: f64,
}
#[derive(Clone)]
struct Player {
pub name: String,
pub state: Arc<Mutex<PlayerState>>,
pub proxy: PaddleProxy,
}
#[derive(Clone)]
pub enum PlayerState {
Up,
Down,
Stop,
Disconnected,
}
fn calc_paddle_movement(pos: &mut f64, state: &PlayerState) -> bool {
let player_delta = match state {
PlayerState::Up => PADDLE_SPEED * -1.0,
PlayerState::Down => PADDLE_SPEED,
PlayerState::Stop => 0.0,
PlayerState::Disconnected => return false,
};
let mut new_paddle_location = *pos + player_delta;
new_paddle_location = new_paddle_location.max(PADDLE_SIZE / 2.0);
new_paddle_location = new_paddle_location.min(BOARD_HEIGHT - PADDLE_SIZE / 2.0);
*pos = new_paddle_location;
true
}
fn calc_paddle_bounce(dx: &mut f64, dy: &mut f64, ball_y: f64, paddle_y: f64) {
let speed = ((*dx) * (*dx) + (*dy) * (*dy)).sqrt() * BALL_SPEEDUP_MULTIPLIER;
let paddle_angle = PADDLE_MAX_ANGLE * (ball_y - paddle_y) / (PADDLE_SIZE / 2.0);
let new_ball_dx = -dx.signum() * speed * paddle_angle.cos();
let new_ball_dy = speed * paddle_angle.sin();
if (*dx) * new_ball_dx >= 0.0 {
panic!("Ball bouncing in the same direction!");
}
*dx = new_ball_dx;
*dy = new_ball_dy;
}
impl Game {
/// return clone of internal state
pub fn state(&self) -> GameState {
fidl_tennis::GameState {
ball_x: self.state.ball_x,
ball_y: self.state.ball_y,
player_1_y: self.state.player_1_y,
player_2_y: self.state.player_2_y,
player_1_score: self.state.player_1_score,
player_2_score: self.state.player_2_score,
player_1_name: self.state.player_1_name.clone(),
player_2_name: self.state.player_2_name.clone(),
time: self.state.time,
game_num: self.state.game_num,
}
}
pub fn new() -> Game {
Game {
player_1: None,
player_2: None,
ball_dx: 0.0,
ball_dy: 0.0,
state: GameState {
ball_x: 0.0,
ball_y: 0.0,
game_num: 0,
player_1_y: 0.0,
player_2_y: 0.0,
player_1_score: 0,
player_2_score: 0,
player_1_name: "".to_string(),
player_2_name: "".to_string(),
time: 0,
},
}
}
pub fn time_scale_factor(&self) -> f64 {
TIME_SCALE_FACTOR
}
pub fn players_ready(&self) -> bool {
return self.player_1.is_some() && self.player_2.is_some();
}
pub fn register_new_paddle(
&mut self, player_name: String, paddle_proxy: PaddleProxy,
) -> Arc<Mutex<PlayerState>> {
let paddle = Player {
name: player_name.clone(),
state: Arc::new(Mutex::new(PlayerState::Stop)),
proxy: paddle_proxy,
};
let res = paddle.state.clone();
if self.player_1.is_none() {
self.player_1 = Some(paddle);
self.state.player_1_name = player_name;
} else if self.player_2.is_none() {
self.player_2 = Some(paddle);
self.state.player_2_name = player_name;
} else {
panic!("too many clients connected");
}
return res;
}
pub fn step(&mut self) {
if self.players_ready() && self.state.game_num == 0 {
self.new_game();
} else if !self.players_ready() {
// game has not started yet
return;
}
self.state.time += 1;
if !calc_paddle_movement(
&mut self.state.player_1_y,
&self.player_1.as_mut().unwrap().state.lock(),
) {
self.player_1 = None;
self.new_game();
self.state.game_num = 0;
self.state.player_1_score = 0;
self.state.player_2_score = 0;
return;
}
if !calc_paddle_movement(
&mut self.state.player_2_y,
&self.player_2.as_mut().unwrap().state.lock(),
) {
self.player_2 = None;
self.new_game();
self.state.game_num = 0;
self.state.player_1_score = 0;
self.state.player_2_score = 0;
return;
}
let mut new_ball_x = self.state.ball_x + self.ball_dx;
let mut new_ball_y = self.state.ball_y + self.ball_dy;
// reflect off the top/bottom of the board
if new_ball_y <= 0.0 || new_ball_y > BOARD_HEIGHT {
self.ball_dy = -self.ball_dy;
if new_ball_y <= 0.0 {
new_ball_y = new_ball_y.abs()
} else {
// Y = H-(y-H)
new_ball_y = 2.0 * BOARD_HEIGHT - new_ball_y;
}
fx_log_info!("bounce off top or bottom");
}
// reflect off the left/right of the board, if a paddle is in the way
if new_ball_x <= 0.0 {
//
// X+dx*t=0 => t=-X/dx
// Y+dy*t=NY => NY=Y-X*dy/dx
//
new_ball_y = self.state.ball_y - self.state.ball_x * self.ball_dy / self.ball_dx;
// we're about to go off of the left side
if new_ball_y > self.state.player_1_y + (PADDLE_SIZE / 2.0)
|| new_ball_y < self.state.player_1_y - (PADDLE_SIZE / 2.0)
{
// player 1 missed, so player 2 gets a point and we reset
fx_log_info!(
"ball {} {}, new y {}, paddle {}",
self.state.ball_x,
self.state.ball_y,
new_ball_y,
self.state.player_1_y
);
self.state.player_2_score += 1;
self.new_game();
return;
} else {
calc_paddle_bounce(
&mut self.ball_dx,
&mut self.ball_dy,
new_ball_y,
self.state.player_1_y,
);
new_ball_x = new_ball_x.abs();
if self.ball_dx < 0.0 {
panic!("Ball going in the wrong direction");
}
fx_log_info!("bounce off left");
}
}
if new_ball_x > BOARD_WIDTH {
//
// X+dx*t=BOARD_WIDTH => t=(BOARD_WIDTH-X)/dx
// Y+dy*t=NY => NY=Y+(BOARD_WIDTH-X)*dy/dx
//
new_ball_y =
self.state.ball_y + (BOARD_WIDTH - self.state.ball_x) * self.ball_dy / self.ball_dx;
// we're about to go off of the right side
if new_ball_y > self.state.player_2_y + (PADDLE_SIZE / 2.0)
|| new_ball_y < self.state.player_2_y - (PADDLE_SIZE / 2.0)
{
// player 2 missed, so player 1 gets a point and we reset
fx_log_info!(
"ball {} {}, new y {}, paddle {}",
self.state.ball_x,
self.state.ball_y,
new_ball_y,
self.state.player_2_y
);
self.state.player_1_score += 1;
self.new_game();
return;
} else {
calc_paddle_bounce(
&mut self.ball_dx,
&mut self.ball_dy,
new_ball_y,
self.state.player_2_y,
);
// X = W-(x-W)
new_ball_x = 2.0 * BOARD_WIDTH - new_ball_x;
if self.ball_dx > 0.0 {
panic!("Ball going in the wrong direction");
}
fx_log_info!("bounce off right");
}
}
self.state.ball_x = new_ball_x;
self.state.ball_y = new_ball_y;
}
fn new_game(&mut self) {
self.player_1.as_mut().map(|player| {
*player.state.lock() = PlayerState::Stop;
let _ = player.proxy.new_game(false);
});
self.player_2.as_mut().map(|player| {
*player.state.lock() = PlayerState::Stop;
let _ = player.proxy.new_game(true);
});
self.ball_dx = 0.0;
while self.ball_dx.abs() < 0.2 {
let angle: f64 = rand::random::<f64>() * 2.0 * PI;
self.ball_dx = angle.sin();
self.ball_dy = angle.cos();
}
self.ball_dx *= TIME_SCALE_FACTOR;
self.ball_dy *= TIME_SCALE_FACTOR;
self.state.ball_x = BOARD_WIDTH / 2.0;
self.state.ball_y = BOARD_HEIGHT / 2.0;
self.state.game_num += 1;
self.state.player_1_y = BOARD_HEIGHT / 2.0;
self.state.player_2_y = BOARD_HEIGHT / 2.0;
self.state.time = 0;
}
}