Compare commits
138 Commits
0.1.0
...
79bfeb2597
Author | SHA1 | Date | |
---|---|---|---|
79bfeb2597 | |||
52f16f5c33 | |||
d75a5d7d5b | |||
473e6c0d1e | |||
9a93a29804 | |||
dad50314b0 | |||
b51aa8b7cb | |||
5a2fd31fa0 | |||
efcf33c539 | |||
652a6d162b | |||
2ab3b5e55d | |||
f52dc84b45 | |||
567473a223 | |||
e95830f644 | |||
5d2b3e55ef | |||
c1f5f5f624 | |||
c5b549244f | |||
7a93a1e3c6 | |||
1fff258248 | |||
0494847f2e | |||
c9dd2fce12 | |||
a2b629d218 | |||
5379b84470 | |||
ef8772be97 | |||
007dae6fae | |||
29012b0f32 | |||
bf4aaada69 | |||
c58c782219 | |||
90ae4a5193 | |||
b9085771a6 | |||
e7629f50e3 | |||
00603e4386 | |||
09eaabe43c | |||
09b2058934 | |||
c6143b3bec | |||
f48c34c234 | |||
f1a179e12d | |||
067332b116 | |||
1257a637b1 | |||
e0822e3585 | |||
42bea6bba4 | |||
50e2db5256 | |||
7ef779d804 | |||
13bb37fa51 | |||
144848563b | |||
99bcf6e5ac | |||
daafca93a9 | |||
04c4813cee | |||
764f6f5112 | |||
0cfd2fc3f2 | |||
46be9732de | |||
6b4bd56684 | |||
9a19272ba6 | |||
c7f0be52cd | |||
37f7c20e36 | |||
75d55b4a23 | |||
0c394f301a | |||
c87a549a8a | |||
98cc8884af | |||
03c842017c | |||
d87e5d816c | |||
eaf8d1c24e | |||
c4aa47f199 | |||
9a623633cb | |||
d1a28a0802 | |||
f6aa9977ba | |||
7dd5464391 | |||
9f58d98a69 | |||
841c6709cc | |||
e63566c6a9 | |||
ac6f8987f4 | |||
356fa75604 | |||
563f33971f | |||
8f0db5cbe6 | |||
5442536768 | |||
f6b0962ec3 | |||
2909bbc1c9 | |||
fd1025c4a8 | |||
ccadddaf15 | |||
2a0987defd | |||
cb6ca75515 | |||
964c90e0d8 | |||
81a05b8f66 | |||
d2801c6b50 | |||
1328baaba7 | |||
d06585ad71 | |||
3ab8201c13 | |||
c0232f602e | |||
5f2b7654be | |||
08a449352b | |||
50fbbe50bf | |||
f4356c656b | |||
b12657ef3a | |||
ad868a7961 | |||
221d1dfa13 | |||
33ed27b892 | |||
9e6df3c78d | |||
6faf38003f | |||
cee12d89f6 | |||
887dd849c6 | |||
2c8686e9d2 | |||
7d92555a85 | |||
0a7e9b9661 | |||
3af6fd730e | |||
b532324654 | |||
de218d2ba1 | |||
bb902cda9e | |||
77d5b18f79 | |||
7125076f1f | |||
4ed1c9c200 | |||
fa4b0bcdc2 | |||
40bff4f8e4 | |||
283ea7d422 | |||
c746313c04 | |||
86d45ad992 | |||
5ba2e78fd0 | |||
1265d7f099 | |||
b713117a70 | |||
a001017821 | |||
5acbe069c2 | |||
6defd8edd2 | |||
ef598dbff4 | |||
2abe1c95d0 | |||
f7bb0ddda2 | |||
d1114f0295 | |||
f94a51027b | |||
f5f2efcfde | |||
eb9999b85b | |||
df1d678ab9 | |||
e88d64ff63 | |||
3ca6c43c9a | |||
d4223be8b4 | |||
83d0780954 | |||
6be3eae863 | |||
c763a24ca9 | |||
10c099e03b | |||
eea2ecbf63 | |||
915426849b |
@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"ignorePaths": ["**/flutter/**", "**/react/**"]
|
"ignorePaths": ["**/flutter/**", "**/react/**"],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
1259
rust/Cargo.lock
generated
1259
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sea_battle_backend"
|
name = "sea_battle_backend"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
description = "A Sea Battle game backend server"
|
description = "A Sea Battle game backend server"
|
||||||
@ -12,23 +12,24 @@ categories = [ "games" ]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.0.15", features = ["derive"] }
|
clap = { version = "4.4.1", features = ["derive"] }
|
||||||
log = "0.4.17"
|
log = "0.4.20"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.10.0"
|
||||||
serde = { version = "1.0.145", features = ["derive"] }
|
serde = { version = "1.0.186", features = ["derive"] }
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.105"
|
||||||
actix-web = "4.1.0"
|
actix-web = "4.1.0"
|
||||||
actix-cors = "0.6.2"
|
actix-cors = "0.6.2"
|
||||||
actix = "0.13.0"
|
actix = "0.13.0"
|
||||||
actix-web-actors = "4.1.0"
|
actix-web-actors = "4.1.0"
|
||||||
actix-rt = "2.7.0"
|
actix-rt = "2.9.0"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde_with = "2.0.1"
|
serde_with = "3.3.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
semver = "1.0.18"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
#reqwest = { version = "0.11.11", default-features = false, features = ["json", "rustls-tls"] }
|
#reqwest = { version = "0.11.11", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
tokio-tungstenite = "0.17.2"
|
tokio-tungstenite = "0.20.0"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
futures = "0.3.23"
|
futures = "0.3.23"
|
||||||
|
@ -12,3 +12,14 @@ pub struct Args {
|
|||||||
#[clap(short, long, value_parser)]
|
#[clap(short, long, value_parser)]
|
||||||
pub cors: Option<String>,
|
pub cors: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::args::Args;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
Args::command().debug_assert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
//! # Project constants
|
//! # Project constants
|
||||||
|
|
||||||
|
pub const MIN_REQUIRED_VERSION: &str = "0.1.0";
|
||||||
|
pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub const MIN_BOATS_NUMBER: usize = 1;
|
pub const MIN_BOATS_NUMBER: usize = 1;
|
||||||
pub const MAX_BOATS_NUMBER: usize = 10;
|
pub const MAX_BOATS_NUMBER: usize = 10;
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@ impl BoatsLayout {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::data::boats_layout::{BoatDirection, BoatPosition, BoatsLayout, Coordinates};
|
use crate::data::boats_layout::{BoatDirection, BoatPosition, BoatsLayout, Coordinates};
|
||||||
use crate::data::game_map::GameMap;
|
use crate::data::game_map::GameMap;
|
||||||
use crate::data::{BotType, GameRules, PlayConfiguration, PrintableMap};
|
use crate::data::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dist_coordinates_eq() {
|
fn dist_coordinates_eq() {
|
||||||
@ -488,9 +488,7 @@ mod test {
|
|||||||
map_height: 5,
|
map_height: 5,
|
||||||
boats_str: "1,1".to_string(),
|
boats_str: "1,1".to_string(),
|
||||||
boats_can_touch: false,
|
boats_can_touch: false,
|
||||||
player_continue_on_hit: false,
|
..Default::default()
|
||||||
strike_timeout: None,
|
|
||||||
bot_type: BotType::Random,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut boats = BoatsLayout(vec![
|
let mut boats = BoatsLayout(vec![
|
||||||
|
@ -4,6 +4,7 @@ pub use game_map::*;
|
|||||||
pub use game_rules::*;
|
pub use game_rules::*;
|
||||||
pub use play_config::*;
|
pub use play_config::*;
|
||||||
pub use printable_map::*;
|
pub use printable_map::*;
|
||||||
|
pub use version::*;
|
||||||
|
|
||||||
mod boats_layout;
|
mod boats_layout;
|
||||||
mod current_game_status;
|
mod current_game_status;
|
||||||
@ -11,3 +12,4 @@ mod game_map;
|
|||||||
mod game_rules;
|
mod game_rules;
|
||||||
mod play_config;
|
mod play_config;
|
||||||
mod printable_map;
|
mod printable_map;
|
||||||
|
mod version;
|
||||||
|
32
rust/sea_battle_backend/src/data/version.rs
Normal file
32
rust/sea_battle_backend/src/data/version.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//! # Version Info
|
||||||
|
//!
|
||||||
|
//! Contains server version requirements information
|
||||||
|
|
||||||
|
use crate::consts;
|
||||||
|
use crate::utils::res_utils::Res;
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct VersionInfo {
|
||||||
|
current: String,
|
||||||
|
min_required: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionInfo {
|
||||||
|
pub fn load_static() -> Self {
|
||||||
|
Self {
|
||||||
|
current: consts::CURRENT_VERSION.to_string(),
|
||||||
|
min_required: consts::MIN_REQUIRED_VERSION.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if builtin version is compatible with a remote version or not
|
||||||
|
pub fn is_compatible_with_static_version(&self) -> Res<bool> {
|
||||||
|
let static_version = Self::load_static();
|
||||||
|
|
||||||
|
let local_current = Version::parse(&static_version.current)?;
|
||||||
|
let min_required = Version::parse(&self.min_required)?;
|
||||||
|
|
||||||
|
Ok(min_required <= local_current)
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};
|
|||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
|
|
||||||
use crate::args::Args;
|
use crate::args::Args;
|
||||||
use crate::data::{GameRules, PlayConfiguration};
|
use crate::data::{BoatsLayout, GameRules, PlayConfiguration, VersionInfo};
|
||||||
use crate::dispatcher_actor::DispatcherActor;
|
use crate::dispatcher_actor::DispatcherActor;
|
||||||
use crate::human_player_ws::{HumanPlayerWS, StartMode};
|
use crate::human_player_ws::{HumanPlayerWS, StartMode};
|
||||||
|
|
||||||
@ -18,11 +18,45 @@ async fn not_found() -> impl Responder {
|
|||||||
HttpResponse::NotFound().json("You missed your strike lol")
|
HttpResponse::NotFound().json("You missed your strike lol")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get bot configuration
|
/// Get version information
|
||||||
|
async fn version_information() -> impl Responder {
|
||||||
|
HttpResponse::Ok().json(VersionInfo::load_static())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get game configuration
|
||||||
async fn game_configuration() -> impl Responder {
|
async fn game_configuration() -> impl Responder {
|
||||||
HttpResponse::Ok().json(PlayConfiguration::default())
|
HttpResponse::Ok().json(PlayConfiguration::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get default game rules
|
||||||
|
async fn default_game_rules() -> impl Responder {
|
||||||
|
HttpResponse::Ok().json(GameRules::random_players_rules())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate game rules
|
||||||
|
async fn validate_game_rules(rules: web::Json<GameRules>) -> impl Responder {
|
||||||
|
HttpResponse::Ok().json(rules.get_errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate random boats layout
|
||||||
|
async fn gen_boats_layout(rules: web::Json<GameRules>) -> impl Responder {
|
||||||
|
let errors = rules.get_errors();
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return HttpResponse::BadRequest().json(errors);
|
||||||
|
}
|
||||||
|
match BoatsLayout::gen_random_for_rules(&rules) {
|
||||||
|
Ok(l) => HttpResponse::Ok().json(l),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to generate boats layout for valid game rules: {} ! / Rules: {:?}",
|
||||||
|
e,
|
||||||
|
rules
|
||||||
|
);
|
||||||
|
HttpResponse::InternalServerError().json("Failed to generate random layout!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)]
|
||||||
pub struct BotPlayQuery {
|
pub struct BotPlayQuery {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@ -130,6 +164,7 @@ async fn start_random(
|
|||||||
log::info!("New random play");
|
log::info!("New random play");
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_server(args: Args) -> std::io::Result<()> {
|
pub async fn start_server(args: Args) -> std::io::Result<()> {
|
||||||
log::info!("Start to listen on {}", args.listen_address);
|
log::info!("Start to listen on {}", args.listen_address);
|
||||||
|
|
||||||
@ -148,7 +183,11 @@ pub async fn start_server(args: Args) -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(dispatcher_actor.clone()))
|
.app_data(web::Data::new(dispatcher_actor.clone()))
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
|
.route("/version", web::get().to(version_information))
|
||||||
.route("/config", web::get().to(game_configuration))
|
.route("/config", web::get().to(game_configuration))
|
||||||
|
.route("/game_rules/default", web::get().to(default_game_rules))
|
||||||
|
.route("/game_rules/validate", web::post().to(validate_game_rules))
|
||||||
|
.route("/generate_boats_layout", web::post().to(gen_boats_layout))
|
||||||
.route("/play/bot", web::get().to(start_bot_play))
|
.route("/play/bot", web::get().to(start_bot_play))
|
||||||
.route("/play/create_invite", web::get().to(start_create_invite))
|
.route("/play/create_invite", web::get().to(start_create_invite))
|
||||||
.route("/play/accept_invite", web::get().to(start_accept_invite))
|
.route("/play/accept_invite", web::get().to(start_accept_invite))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sea_battle_cli_player"
|
name = "sea_battle_cli_player"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
description = "A Sea Battle game shell client"
|
description = "A Sea Battle game shell client"
|
||||||
@ -12,22 +12,23 @@ categories = [ "games" ]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sea_battle_backend = { path = "../sea_battle_backend", version = "0.1.0" }
|
sea_battle_backend = { path = "../sea_battle_backend", version = "0.2.0" }
|
||||||
clap = { version = "4.0.15", features = ["derive"] }
|
clap = { version = "4.4.1", features = ["derive"] }
|
||||||
log = "0.4.17"
|
log = "0.4.20"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.10.0"
|
||||||
tui = "0.19.0"
|
tui = "0.19.0"
|
||||||
crossterm = "0.25.0"
|
crossterm = "0.27.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
tokio = "1.21.2"
|
tokio = "1.32.0"
|
||||||
num = "0.4.0"
|
num = "0.4.1"
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.16"
|
||||||
num-derive = "0.3.3"
|
num-derive = "0.4.0"
|
||||||
textwrap = "0.15.1"
|
textwrap = "0.16.0"
|
||||||
tokio-tungstenite = { version = "0.17.2", features = ["__rustls-tls", "rustls-tls-native-roots"] }
|
tokio-tungstenite = { version = "0.20.0", features = ["__rustls-tls", "rustls-tls-native-roots"] }
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
futures = "0.3.23"
|
futures = "0.3.23"
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.105"
|
||||||
hostname = "0.3.1"
|
hostname = "0.3.1"
|
||||||
rustls = "0.20.6"
|
rustls = "0.21.6"
|
||||||
hyper-rustls = { version = "0.23.0", features = ["rustls-native-certs"] }
|
hyper-rustls = { version = "0.24.1", features = ["rustls-native-certs"] }
|
||||||
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
|
@ -20,7 +20,7 @@ pub struct CliArgs {
|
|||||||
value_parser,
|
value_parser,
|
||||||
default_value = "https://seabattleapi.communiquons.org"
|
default_value = "https://seabattleapi.communiquons.org"
|
||||||
)]
|
)]
|
||||||
pub remote_server_uri: String,
|
pub remote_server: String,
|
||||||
|
|
||||||
/// Local server listen address
|
/// Local server listen address
|
||||||
#[clap(short, long, default_value = "127.0.0.1:5679")]
|
#[clap(short, long, default_value = "127.0.0.1:5679")]
|
||||||
@ -61,3 +61,14 @@ lazy_static::lazy_static! {
|
|||||||
pub fn cli_args() -> &'static CliArgs {
|
pub fn cli_args() -> &'static CliArgs {
|
||||||
&ARGS
|
&ARGS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::cli_args::CliArgs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
CliArgs::command().debug_assert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,12 +3,13 @@ use crate::server;
|
|||||||
use futures::stream::{SplitSink, SplitStream};
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use hyper_rustls::ConfigBuilderExt;
|
use hyper_rustls::ConfigBuilderExt;
|
||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::*;
|
||||||
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
|
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
|
||||||
use sea_battle_backend::server::{
|
use sea_battle_backend::server::{
|
||||||
AcceptInviteQuery, BotPlayQuery, CreateInviteQuery, PlayRandomQuery,
|
AcceptInviteQuery, BotPlayQuery, CreateInviteQuery, PlayRandomQuery,
|
||||||
};
|
};
|
||||||
use sea_battle_backend::utils::res_utils::{boxed_error, Res};
|
use sea_battle_backend::utils::res_utils::{boxed_error, Res};
|
||||||
|
use std::error::Error;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::sync::mpsc::TryRecvError;
|
use std::sync::mpsc::TryRecvError;
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
@ -18,6 +19,11 @@ use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
|||||||
|
|
||||||
type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||||
|
|
||||||
|
pub enum GetRemoteVersionError {
|
||||||
|
ConnectionFailed,
|
||||||
|
Other(Box<dyn Error>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Connection client
|
/// Connection client
|
||||||
///
|
///
|
||||||
/// This structure acts as a wrapper around websocket connection that handles automatically parsing
|
/// This structure acts as a wrapper around websocket connection that handles automatically parsing
|
||||||
@ -28,6 +34,24 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
/// Get remote server version
|
||||||
|
pub async fn get_server_version() -> Result<VersionInfo, GetRemoteVersionError> {
|
||||||
|
let url = format!("{}/version", cli_args().remote_server);
|
||||||
|
log::debug!("Getting remote information from {} ...", url);
|
||||||
|
|
||||||
|
let res = match reqwest::get(url).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) if e.is_timeout() || e.is_connect() => {
|
||||||
|
return Err(GetRemoteVersionError::ConnectionFailed)
|
||||||
|
}
|
||||||
|
Err(e) => return Err(GetRemoteVersionError::Other(Box::new(e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| GetRemoteVersionError::Other(Box::new(e)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Start to play against a bot
|
/// Start to play against a bot
|
||||||
///
|
///
|
||||||
/// When playing against a bot, local server is always used
|
/// When playing against a bot, local server is always used
|
||||||
@ -38,7 +62,7 @@ impl Client {
|
|||||||
&cli_args().local_server_address(),
|
&cli_args().local_server_address(),
|
||||||
&format!(
|
&format!(
|
||||||
"/play/bot?{}",
|
"/play/bot?{}",
|
||||||
serde_urlencoded::to_string(&BotPlayQuery {
|
serde_urlencoded::to_string(BotPlayQuery {
|
||||||
rules: rules.clone(),
|
rules: rules.clone(),
|
||||||
player_name: "Human".to_string()
|
player_name: "Human".to_string()
|
||||||
})
|
})
|
||||||
@ -51,10 +75,10 @@ impl Client {
|
|||||||
/// Start to play against a random player
|
/// Start to play against a random player
|
||||||
pub async fn start_random_play<D: Display>(player_name: D) -> Res<Self> {
|
pub async fn start_random_play<D: Display>(player_name: D) -> Res<Self> {
|
||||||
Self::connect_url(
|
Self::connect_url(
|
||||||
&cli_args().remote_server_uri,
|
&cli_args().remote_server,
|
||||||
&format!(
|
&format!(
|
||||||
"/play/random?{}",
|
"/play/random?{}",
|
||||||
serde_urlencoded::to_string(&PlayRandomQuery {
|
serde_urlencoded::to_string(PlayRandomQuery {
|
||||||
player_name: player_name.to_string()
|
player_name: player_name.to_string()
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -66,10 +90,10 @@ impl Client {
|
|||||||
/// Start a play by creating an invite
|
/// Start a play by creating an invite
|
||||||
pub async fn start_create_invite<D: Display>(rules: &GameRules, player_name: D) -> Res<Self> {
|
pub async fn start_create_invite<D: Display>(rules: &GameRules, player_name: D) -> Res<Self> {
|
||||||
Self::connect_url(
|
Self::connect_url(
|
||||||
&cli_args().remote_server_uri,
|
&cli_args().remote_server,
|
||||||
&format!(
|
&format!(
|
||||||
"/play/create_invite?{}",
|
"/play/create_invite?{}",
|
||||||
serde_urlencoded::to_string(&CreateInviteQuery {
|
serde_urlencoded::to_string(CreateInviteQuery {
|
||||||
rules: rules.clone(),
|
rules: rules.clone(),
|
||||||
player_name: player_name.to_string()
|
player_name: player_name.to_string()
|
||||||
})
|
})
|
||||||
@ -82,10 +106,10 @@ impl Client {
|
|||||||
/// Start a play by accepting an invite
|
/// Start a play by accepting an invite
|
||||||
pub async fn start_accept_invite<D: Display>(code: String, player_name: D) -> Res<Self> {
|
pub async fn start_accept_invite<D: Display>(code: String, player_name: D) -> Res<Self> {
|
||||||
Self::connect_url(
|
Self::connect_url(
|
||||||
&cli_args().remote_server_uri,
|
&cli_args().remote_server,
|
||||||
&format!(
|
&format!(
|
||||||
"/play/accept_invite?{}",
|
"/play/accept_invite?{}",
|
||||||
serde_urlencoded::to_string(&AcceptInviteQuery {
|
serde_urlencoded::to_string(AcceptInviteQuery {
|
||||||
code,
|
code,
|
||||||
player_name: player_name.to_string()
|
player_name: player_name.to_string()
|
||||||
})
|
})
|
||||||
@ -109,7 +133,7 @@ impl Client {
|
|||||||
.with_no_client_auth();
|
.with_no_client_auth();
|
||||||
let connector = tokio_tungstenite::Connector::Rustls(Arc::new(config));
|
let connector = tokio_tungstenite::Connector::Rustls(Arc::new(config));
|
||||||
|
|
||||||
tokio_tungstenite::connect_async_tls_with_config(ws_url, None, Some(connector)).await?
|
tokio_tungstenite::connect_async_tls_with_config(ws_url, None, false,Some(connector)).await?
|
||||||
} else {
|
} else {
|
||||||
// Perform an unsecure connection
|
// Perform an unsecure connection
|
||||||
tokio_tungstenite::connect_async(ws_url).await?
|
tokio_tungstenite::connect_async(ws_url).await?
|
||||||
|
@ -2,7 +2,7 @@ extern crate core;
|
|||||||
|
|
||||||
pub mod cli_args;
|
pub mod cli_args;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod constants;
|
pub mod consts;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod ui_screens;
|
pub mod ui_screens;
|
||||||
pub mod ui_widgets;
|
pub mod ui_widgets;
|
||||||
|
@ -18,7 +18,7 @@ use sea_battle_backend::consts::{
|
|||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::GameRules;
|
||||||
use sea_battle_backend::utils::res_utils::Res;
|
use sea_battle_backend::utils::res_utils::Res;
|
||||||
use sea_battle_cli_player::cli_args::{cli_args, TestDevScreen};
|
use sea_battle_cli_player::cli_args::{cli_args, TestDevScreen};
|
||||||
use sea_battle_cli_player::client::Client;
|
use sea_battle_cli_player::client::{Client, GetRemoteVersionError};
|
||||||
use sea_battle_cli_player::server::run_server;
|
use sea_battle_cli_player::server::run_server;
|
||||||
use sea_battle_cli_player::ui_screens::configure_game_rules::GameRulesConfigurationScreen;
|
use sea_battle_cli_player::ui_screens::configure_game_rules::GameRulesConfigurationScreen;
|
||||||
use sea_battle_cli_player::ui_screens::game_screen::GameScreen;
|
use sea_battle_cli_player::ui_screens::game_screen::GameScreen;
|
||||||
@ -35,10 +35,10 @@ async fn run_dev<B: Backend>(
|
|||||||
d: TestDevScreen,
|
d: TestDevScreen,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let res = match d {
|
let res = match d {
|
||||||
TestDevScreen::Popup => popup_screen::PopupScreen::new("Welcome there!!")
|
TestDevScreen::Popup => PopupScreen::new("Welcome there!!")
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string(),
|
.as_string(),
|
||||||
TestDevScreen::Input => input_screen::InputScreen::new("What it your name ?")
|
TestDevScreen::Input => InputScreen::new("What it your name ?")
|
||||||
.set_title("A custom title")
|
.set_title("A custom title")
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string(),
|
.as_string(),
|
||||||
@ -50,9 +50,9 @@ async fn run_dev<B: Backend>(
|
|||||||
TestDevScreen::SelectBotType => select_bot_type_screen::SelectBotTypeScreen::default()
|
TestDevScreen::SelectBotType => select_bot_type_screen::SelectBotTypeScreen::default()
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string(),
|
.as_string(),
|
||||||
TestDevScreen::SelectPlayMode => select_play_mode_screen::SelectPlayModeScreen::default()
|
TestDevScreen::SelectPlayMode => {
|
||||||
.show(terminal)?
|
SelectPlayModeScreen::default().show(terminal)?.as_string()
|
||||||
.as_string(),
|
}
|
||||||
TestDevScreen::SetBoatsLayout => {
|
TestDevScreen::SetBoatsLayout => {
|
||||||
let rules = GameRules {
|
let rules = GameRules {
|
||||||
boats_can_touch: true,
|
boats_can_touch: true,
|
||||||
@ -64,7 +64,7 @@ async fn run_dev<B: Backend>(
|
|||||||
.as_string()
|
.as_string()
|
||||||
}
|
}
|
||||||
TestDevScreen::ConfigureGameRules => {
|
TestDevScreen::ConfigureGameRules => {
|
||||||
configure_game_rules::GameRulesConfigurationScreen::new(GameRules::default())
|
GameRulesConfigurationScreen::new(GameRules::default())
|
||||||
.show(terminal)?
|
.show(terminal)?
|
||||||
.as_string()
|
.as_string()
|
||||||
}
|
}
|
||||||
@ -99,14 +99,41 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Res {
|
|||||||
return run_dev(terminal, d).await;
|
return run_dev(terminal, d).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut checked_online_compatibility = false;
|
||||||
|
|
||||||
let mut rules = GameRules::default();
|
let mut rules = GameRules::default();
|
||||||
|
|
||||||
let mut username = "".to_string();
|
let mut username = "".to_string();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
terminal.clear()?;
|
||||||
let choice = SelectPlayModeScreen::default().show(terminal)?;
|
let choice = SelectPlayModeScreen::default().show(terminal)?;
|
||||||
|
|
||||||
if let ScreenResult::Ok(c) = choice {
|
if let ScreenResult::Ok(c) = choice {
|
||||||
|
// Check compatibility
|
||||||
|
if c.is_online_play_mode() && !checked_online_compatibility {
|
||||||
|
PopupScreen::new("🖥 Checking remote server version...").show_once(terminal)?;
|
||||||
|
let valid_version = match Client::get_server_version().await {
|
||||||
|
Ok(v) => v.is_compatible_with_static_version().unwrap_or(false),
|
||||||
|
Err(GetRemoteVersionError::ConnectionFailed) => {
|
||||||
|
PopupScreen::new("❌ Could not connect to remote server!")
|
||||||
|
.show(terminal)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(GetRemoteVersionError::Other(e)) => {
|
||||||
|
log::error!("Could not load remote server information! {:?}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !valid_version {
|
||||||
|
PopupScreen::new("❌ Unfortunately, it seems that your version of Sea Battle Cli player is too old to be used online...\n\nPlease update it before trying to play online...").show(terminal)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
checked_online_compatibility = true;
|
||||||
|
}
|
||||||
|
|
||||||
if c.need_player_name() && username.is_empty() {
|
if c.need_player_name() && username.is_empty() {
|
||||||
username = query_player_name(terminal)?;
|
username = query_player_name(terminal)?;
|
||||||
}
|
}
|
||||||
@ -123,17 +150,17 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Res {
|
|||||||
|
|
||||||
let client = match choice {
|
let client = match choice {
|
||||||
ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => {
|
ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => {
|
||||||
Client::start_random_play(&username).await?
|
Client::start_random_play(&username).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play against bot
|
// Play against bot
|
||||||
ScreenResult::Ok(SelectPlayModeResult::PlayAgainstBot) => {
|
ScreenResult::Ok(SelectPlayModeResult::PlayAgainstBot) => {
|
||||||
Client::start_bot_play(&rules).await?
|
Client::start_bot_play(&rules).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create invite
|
// Create invite
|
||||||
ScreenResult::Ok(SelectPlayModeResult::CreateInvite) => {
|
ScreenResult::Ok(SelectPlayModeResult::CreateInvite) => {
|
||||||
Client::start_create_invite(&rules, &username).await?
|
Client::start_create_invite(&rules, &username).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join invite
|
// Join invite
|
||||||
@ -149,15 +176,23 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Res {
|
|||||||
};
|
};
|
||||||
|
|
||||||
PopupScreen::new("🔌 Connecting...").show_once(terminal)?;
|
PopupScreen::new("🔌 Connecting...").show_once(terminal)?;
|
||||||
Client::start_accept_invite(code, &username).await?
|
Client::start_accept_invite(code, &username).await
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenResult::Canceled | ScreenResult::Ok(SelectPlayModeResult::Exit) => return Ok(()),
|
ScreenResult::Canceled | ScreenResult::Ok(SelectPlayModeResult::Exit) => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match client {
|
||||||
|
Ok(client) => {
|
||||||
// Display game screen
|
// Display game screen
|
||||||
GameScreen::new(client).show(terminal).await?;
|
GameScreen::new(client).show(terminal).await?;
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to connect to server: {}", e);
|
||||||
|
PopupScreen::new("❌ Failed to connect to server!").show(terminal)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -16,7 +16,7 @@ use tui::{Frame, Terminal};
|
|||||||
|
|
||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::GameRules;
|
||||||
|
|
||||||
use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
use crate::consts::{HIGHLIGHT_COLOR, TICK_RATE};
|
||||||
use crate::ui_screens::popup_screen::show_screen_too_small_popup;
|
use crate::ui_screens::popup_screen::show_screen_too_small_popup;
|
||||||
use crate::ui_screens::select_bot_type_screen::SelectBotTypeScreen;
|
use crate::ui_screens::select_bot_type_screen::SelectBotTypeScreen;
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
@ -25,7 +25,7 @@ use crate::ui_widgets::button_widget::ButtonWidget;
|
|||||||
use crate::ui_widgets::checkbox_widget::CheckboxWidget;
|
use crate::ui_widgets::checkbox_widget::CheckboxWidget;
|
||||||
use crate::ui_widgets::text_editor_widget::TextEditorWidget;
|
use crate::ui_widgets::text_editor_widget::TextEditorWidget;
|
||||||
|
|
||||||
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Eq, PartialEq)]
|
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Eq, PartialEq, Copy, Clone)]
|
||||||
enum EditingField {
|
enum EditingField {
|
||||||
MapWidth = 0,
|
MapWidth = 0,
|
||||||
MapHeight,
|
MapHeight,
|
||||||
@ -69,7 +69,7 @@ impl GameRulesConfigurationScreen {
|
|||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
// Quit app
|
// Quit app
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
KeyCode::Char('q') | KeyCode::Esc => return Ok(ScreenResult::Canceled),
|
||||||
|
|
||||||
// Navigate between fields
|
// Navigate between fields
|
||||||
KeyCode::Up | KeyCode::Left => cursor_pos -= 1,
|
KeyCode::Up | KeyCode::Left => cursor_pos -= 1,
|
||||||
@ -128,7 +128,7 @@ impl GameRulesConfigurationScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Char(c) if ('0'..='9').contains(&c) => {
|
KeyCode::Char(c) if c.is_ascii_digit() => {
|
||||||
let val = c.to_string().parse::<usize>().unwrap_or_default();
|
let val = c.to_string().parse::<usize>().unwrap_or_default();
|
||||||
|
|
||||||
if self.curr_field == EditingField::MapWidth
|
if self.curr_field == EditingField::MapWidth
|
||||||
@ -283,10 +283,10 @@ impl GameRulesConfigurationScreen {
|
|||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.split(chunks[EditingField::OK as usize]);
|
.split(chunks[EditingField::OK as usize]);
|
||||||
|
|
||||||
let button = ButtonWidget::new("Cancel", self.curr_field == EditingField::Cancel);
|
let button = ButtonWidget::cancel(self.curr_field == EditingField::Cancel);
|
||||||
f.render_widget(button, buttons_chunk[0]);
|
f.render_widget(button, buttons_chunk[0]);
|
||||||
|
|
||||||
let button = ButtonWidget::new("OK", self.curr_field == EditingField::OK)
|
let button = ButtonWidget::ok(self.curr_field == EditingField::OK)
|
||||||
.set_disabled(!self.rules.is_valid());
|
.set_disabled(!self.rules.is_valid());
|
||||||
f.render_widget(button, buttons_chunk[1]);
|
f.render_widget(button, buttons_chunk[1]);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use tui::text::*;
|
|||||||
use tui::widgets::*;
|
use tui::widgets::*;
|
||||||
use tui::{Frame, Terminal};
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::consts::*;
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crate::ui_widgets::button_widget::ButtonWidget;
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
||||||
@ -18,6 +18,7 @@ use crate::ui_widgets::button_widget::ButtonWidget;
|
|||||||
pub fn confirm<B: Backend>(terminal: &mut Terminal<B>, msg: &str) -> bool {
|
pub fn confirm<B: Backend>(terminal: &mut Terminal<B>, msg: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
ConfirmDialogScreen::new(msg)
|
ConfirmDialogScreen::new(msg)
|
||||||
|
.set_can_escape(true)
|
||||||
.show(terminal)
|
.show(terminal)
|
||||||
.unwrap_or(ScreenResult::Canceled),
|
.unwrap_or(ScreenResult::Canceled),
|
||||||
ScreenResult::Ok(true)
|
ScreenResult::Ok(true)
|
||||||
@ -28,19 +29,24 @@ pub struct ConfirmDialogScreen<'a> {
|
|||||||
title: &'a str,
|
title: &'a str,
|
||||||
msg: &'a str,
|
msg: &'a str,
|
||||||
is_confirm: bool,
|
is_confirm: bool,
|
||||||
can_cancel: bool,
|
can_escape: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ConfirmDialogScreen<'a> {
|
impl<'a> ConfirmDialogScreen<'a> {
|
||||||
pub fn new(msg: &'a str) -> Self {
|
pub fn new(msg: &'a str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: "Confirmation Request",
|
title: "❔ Confirmation Request",
|
||||||
msg,
|
msg,
|
||||||
is_confirm: true,
|
is_confirm: true,
|
||||||
can_cancel: false,
|
can_escape: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_can_escape(mut self, v: bool) -> Self {
|
||||||
|
self.can_escape = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn show<B: Backend>(
|
pub fn show<B: Backend>(
|
||||||
mut self,
|
mut self,
|
||||||
terminal: &mut Terminal<B>,
|
terminal: &mut Terminal<B>,
|
||||||
@ -56,7 +62,7 @@ impl<'a> ConfirmDialogScreen<'a> {
|
|||||||
if event::poll(timeout)? {
|
if event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc | KeyCode::Char('q') if self.can_cancel => {
|
KeyCode::Esc | KeyCode::Char('q') if self.can_escape => {
|
||||||
return Ok(ScreenResult::Canceled)
|
return Ok(ScreenResult::Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,10 +123,10 @@ impl<'a> ConfirmDialogScreen<'a> {
|
|||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(chunks[1]);
|
.split(chunks[1]);
|
||||||
|
|
||||||
let cancel_button = ButtonWidget::new("Cancel", true).set_disabled(self.is_confirm);
|
let cancel_button = ButtonWidget::cancel(true).set_disabled(self.is_confirm);
|
||||||
f.render_widget(cancel_button, buttons_area[0]);
|
f.render_widget(cancel_button, buttons_area[0]);
|
||||||
|
|
||||||
let ok_button = ButtonWidget::new("Confirm", true).set_disabled(!self.is_confirm);
|
let ok_button = ButtonWidget::new("✅ Confirm", true).set_disabled(!self.is_confirm);
|
||||||
f.render_widget(ok_button, buttons_area[1]);
|
f.render_widget(ok_button, buttons_area[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ use sea_battle_backend::utils::res_utils::Res;
|
|||||||
use sea_battle_backend::utils::time_utils::time;
|
use sea_battle_backend::utils::time_utils::time;
|
||||||
|
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
use crate::constants::*;
|
use crate::consts::*;
|
||||||
use crate::ui_screens::confirm_dialog_screen::confirm;
|
use crate::ui_screens::confirm_dialog_screen::confirm;
|
||||||
use crate::ui_screens::popup_screen::{show_screen_too_small_popup, PopupScreen};
|
use crate::ui_screens::popup_screen::{show_screen_too_small_popup, PopupScreen};
|
||||||
use crate::ui_screens::set_boats_layout_screen::SetBoatsLayoutScreen;
|
use crate::ui_screens::set_boats_layout_screen::SetBoatsLayoutScreen;
|
||||||
@ -147,7 +147,7 @@ impl GameScreen {
|
|||||||
|
|
||||||
match key.code {
|
match key.code {
|
||||||
// Leave game
|
// Leave game
|
||||||
KeyCode::Char('q')
|
KeyCode::Char('q') | KeyCode::Esc
|
||||||
if confirm(terminal, "Do you really want to leave game?") =>
|
if confirm(terminal, "Do you really want to leave game?") =>
|
||||||
{
|
{
|
||||||
self.client.close_connection().await;
|
self.client.close_connection().await;
|
||||||
|
@ -10,7 +10,7 @@ use tui::layout::*;
|
|||||||
use tui::widgets::*;
|
use tui::widgets::*;
|
||||||
use tui::{Frame, Terminal};
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::consts::*;
|
||||||
use crate::ui_screens::utils::*;
|
use crate::ui_screens::utils::*;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crate::ui_widgets::button_widget::ButtonWidget;
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
||||||
@ -163,12 +163,12 @@ impl<'a> InputScreen<'a> {
|
|||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(*chunks.last().unwrap());
|
.split(*chunks.last().unwrap());
|
||||||
|
|
||||||
let cancel_button = ButtonWidget::new("Cancel", self.is_cancel_hovered)
|
let cancel_button = ButtonWidget::cancel(self.is_cancel_hovered)
|
||||||
.set_disabled(!self.can_cancel)
|
.set_disabled(!self.can_cancel)
|
||||||
.set_min_width(8);
|
.set_min_width(8);
|
||||||
f.render_widget(cancel_button, buttons_area[0]);
|
f.render_widget(cancel_button, buttons_area[0]);
|
||||||
|
|
||||||
let ok_button = ButtonWidget::new("OK", !self.is_cancel_hovered)
|
let ok_button = ButtonWidget::ok(!self.is_cancel_hovered)
|
||||||
.set_min_width(8)
|
.set_min_width(8)
|
||||||
.set_disabled(error.is_some());
|
.set_disabled(error.is_some());
|
||||||
f.render_widget(ok_button, buttons_area[1]);
|
f.render_widget(ok_button, buttons_area[1]);
|
||||||
|
@ -9,7 +9,7 @@ use tui::text::*;
|
|||||||
use tui::widgets::*;
|
use tui::widgets::*;
|
||||||
use tui::{Frame, Terminal};
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::consts::*;
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crate::ui_widgets::button_widget::ButtonWidget;
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
||||||
@ -52,7 +52,7 @@ impl<'a> PopupScreen<'a> {
|
|||||||
if event::poll(timeout)? {
|
if event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
KeyCode::Char('q') | KeyCode::Esc => return Ok(ScreenResult::Canceled),
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
return Ok(ScreenResult::Ok(()));
|
return Ok(ScreenResult::Ok(()));
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ use tui::{Frame, Terminal};
|
|||||||
|
|
||||||
use sea_battle_backend::data::{BotDescription, BotType, PlayConfiguration};
|
use sea_battle_backend::data::{BotDescription, BotType, PlayConfiguration};
|
||||||
|
|
||||||
use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
use crate::consts::{HIGHLIGHT_COLOR, TICK_RATE};
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ impl SelectBotTypeScreen {
|
|||||||
if event::poll(timeout)? {
|
if event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
KeyCode::Char('q') | KeyCode::Esc => return Ok(ScreenResult::Canceled),
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
return Ok(ScreenResult::Ok(self.types[self.curr_selection].r#type));
|
return Ok(ScreenResult::Ok(self.types[self.curr_selection].r#type));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::constants::{HIGHLIGHT_COLOR, TICK_RATE};
|
use crate::consts::{HIGHLIGHT_COLOR, TICK_RATE};
|
||||||
use crate::ui_screens::utils::centered_rect_size;
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
use crate::ui_screens::ScreenResult;
|
use crate::ui_screens::ScreenResult;
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
@ -33,6 +33,11 @@ impl SelectPlayModeResult {
|
|||||||
pub fn need_custom_rules(&self) -> bool {
|
pub fn need_custom_rules(&self) -> bool {
|
||||||
self == &SelectPlayModeResult::PlayAgainstBot || self == &SelectPlayModeResult::CreateInvite
|
self == &SelectPlayModeResult::PlayAgainstBot || self == &SelectPlayModeResult::CreateInvite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specify whether a play mode is to be played online or not
|
||||||
|
pub fn is_online_play_mode(&self) -> bool {
|
||||||
|
self != &SelectPlayModeResult::PlayAgainstBot && self != &SelectPlayModeResult::Exit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -87,7 +92,7 @@ impl SelectPlayModeScreen {
|
|||||||
if crossterm::event::poll(timeout)? {
|
if crossterm::event::poll(timeout)? {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
KeyCode::Char('q') | KeyCode::Esc => return Ok(ScreenResult::Canceled),
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
return Ok(ScreenResult::Ok(
|
return Ok(ScreenResult::Ok(
|
||||||
AVAILABLE_PLAY_MODES[self.curr_selection].value,
|
AVAILABLE_PLAY_MODES[self.curr_selection].value,
|
||||||
|
@ -12,7 +12,7 @@ use tui::{Frame, Terminal};
|
|||||||
|
|
||||||
use sea_battle_backend::data::*;
|
use sea_battle_backend::data::*;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::consts::*;
|
||||||
use crate::ui_screens::confirm_dialog_screen::confirm;
|
use crate::ui_screens::confirm_dialog_screen::confirm;
|
||||||
use crate::ui_screens::popup_screen::show_screen_too_small_popup;
|
use crate::ui_screens::popup_screen::show_screen_too_small_popup;
|
||||||
use crate::ui_screens::utils::{centered_rect_size, centered_text};
|
use crate::ui_screens::utils::{centered_rect_size, centered_text};
|
||||||
@ -66,7 +66,7 @@ impl<'a> SetBoatsLayoutScreen<'a> {
|
|||||||
let event = event::read()?;
|
let event = event::read()?;
|
||||||
if let Event::Key(key) = &event {
|
if let Event::Key(key) = &event {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') | KeyCode::Esc => {
|
||||||
if !self.confirm_on_cancel
|
if !self.confirm_on_cancel
|
||||||
|| confirm(terminal, "Do you really want to quit?")
|
|| confirm(terminal, "Do you really want to quit?")
|
||||||
{
|
{
|
||||||
@ -202,7 +202,7 @@ impl<'a> SetBoatsLayoutScreen<'a> {
|
|||||||
.add_colored_cells(current_boat)
|
.add_colored_cells(current_boat)
|
||||||
.add_colored_cells(invalid_boats)
|
.add_colored_cells(invalid_boats)
|
||||||
.add_colored_cells(other_boats)
|
.add_colored_cells(other_boats)
|
||||||
.set_title("Choose your boat layout")
|
.set_title("🛥 Set your boats layout")
|
||||||
.set_yield_func(|c, r| {
|
.set_yield_func(|c, r| {
|
||||||
for i in 0..r.width {
|
for i in 0..r.width {
|
||||||
for j in 0..r.height {
|
for j in 0..r.height {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::constants::HIGHLIGHT_COLOR;
|
use crate::consts::HIGHLIGHT_COLOR;
|
||||||
use tui::buffer::Buffer;
|
use tui::buffer::Buffer;
|
||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
use tui::style::{Color, Style};
|
use tui::style::{Color, Style};
|
||||||
@ -13,6 +13,7 @@ pub struct ButtonWidget {
|
|||||||
label: String,
|
label: String,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
min_width: usize,
|
min_width: usize,
|
||||||
|
hover_bg_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonWidget {
|
impl ButtonWidget {
|
||||||
@ -22,9 +23,18 @@ impl ButtonWidget {
|
|||||||
is_hovered,
|
is_hovered,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
min_width: 0,
|
min_width: 0,
|
||||||
|
hover_bg_color: HIGHLIGHT_COLOR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel(is_hovered: bool) -> Self {
|
||||||
|
Self::new("❌ Cancel", is_hovered).set_hover_bg_color(Color::Red)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ok(is_hovered: bool) -> Self {
|
||||||
|
Self::new("✅ OK", is_hovered)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_disabled(mut self, disabled: bool) -> Self {
|
pub fn set_disabled(mut self, disabled: bool) -> Self {
|
||||||
self.disabled = disabled;
|
self.disabled = disabled;
|
||||||
self
|
self
|
||||||
@ -35,6 +45,11 @@ impl ButtonWidget {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_hover_bg_color(mut self, v: Color) -> Self {
|
||||||
|
self.hover_bg_color = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn estimated_size(&self) -> (u16, u16) {
|
pub fn estimated_size(&self) -> (u16, u16) {
|
||||||
((self.label.len() + 2).max(self.min_width) as u16, 1)
|
((self.label.len() + 2).max(self.min_width) as u16, 1)
|
||||||
}
|
}
|
||||||
@ -55,7 +70,7 @@ impl Widget for ButtonWidget {
|
|||||||
let input = Paragraph::new(label.as_ref()).style(match (self.disabled, self.is_hovered) {
|
let input = Paragraph::new(label.as_ref()).style(match (self.disabled, self.is_hovered) {
|
||||||
(true, _) => Style::default(),
|
(true, _) => Style::default(),
|
||||||
(_, false) => Style::default().bg(Color::DarkGray),
|
(_, false) => Style::default().bg(Color::DarkGray),
|
||||||
(_, true) => Style::default().fg(Color::White).bg(HIGHLIGHT_COLOR),
|
(_, true) => Style::default().fg(Color::White).bg(self.hover_bg_color),
|
||||||
});
|
});
|
||||||
|
|
||||||
input.render(area, buf);
|
input.render(area, buf);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::constants::HIGHLIGHT_COLOR;
|
use crate::consts::HIGHLIGHT_COLOR;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use tui::buffer::Buffer;
|
use tui::buffer::Buffer;
|
||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::constants::HIGHLIGHT_COLOR;
|
use crate::consts::HIGHLIGHT_COLOR;
|
||||||
use tui::buffer::Buffer;
|
use tui::buffer::Buffer;
|
||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
use tui::style::*;
|
use tui::style::*;
|
||||||
|
Reference in New Issue
Block a user