diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 93cdd10..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index cb8cd6d..41d7d09 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,7 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk - # Added by cargo - /target /dist .vercel diff --git a/.vscode/settings.json b/.vscode/settings.json index d808090..1ef7643 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,8 @@ "./Cargo.toml", "./Cargo.toml", "./Cargo.toml", - "./Cargo.toml" - ] + "./Cargo.toml", + "./packages/shared/Cargo.toml" + ], + "rust-analyzer.showUnlinkedFileNotification": false } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b2e219e..7c26031 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,37 @@ -[package] -name = "rust-2048" -version = "0.1.0" -description = "It's a 2048 game written in Rust." -authors = ["liu9293 "] -edition = "2021" +# [package] +# name = "rust-2048" +# version = "0.1.0" +# description = "It's a 2048 game written in Rust." +# authors = ["liu9293 "] +# edition = "2021" -[dependencies] -dioxus-router = "0.3.0" -rand = "0.8.5" -getrandom = { version = "0.2", features = ["js"] } +# [dependencies] +# dioxus-router = "0.3.0" +# rand = "0.8.5" +# getrandom = { version = "0.2", features = ["js"] } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -dioxus = "0.3.0" -dioxus-desktop = "0.3.0" +# [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +# dioxus = "0.3.0" +# dioxus-desktop = "0.3.0" -[target.'cfg(target_arch = "wasm32")'.dependencies] -dioxus = "0.3.2" -dioxus-web = "0.3.2" +# [target.'cfg(target_arch = "wasm32")'.dependencies] +# dioxus = "0.3.2" +# dioxus-web = "0.3.2" -[package.metadata.bundle] -name = "Rust 2048" -identifier = "com.rust.kaixiaozao" -version = "0.0.1" -copyright = "Copyright (c) KaiXiaoZao 2023. All rights reserved." -category = "Game" -short_description = "2048 game" -long_description = """ -It's a 2048 game written in Rust. -""" +# [package.metadata.bundle] +# name = "Rust 2048" +# identifier = "com.rust.kaixiaozao" +# version = "0.0.1" +# copyright = "Copyright (c) KaiXiaoZao 2023. All rights reserved." +# category = "Game" +# short_description = "2048 game" +# long_description = """ +# It's a 2048 game written in Rust. +# """ + +[workspace] +members = [ + "packages/client", + "packages/shared", + "packages/server" +] \ No newline at end of file diff --git a/Dioxus.toml b/Dioxus.toml index 9214189..bb2087f 100644 --- a/Dioxus.toml +++ b/Dioxus.toml @@ -1,8 +1,9 @@ [application] name = "rust-2048" default_platform = "web" -out_dir = "dist" -asset_dir = "public" +out_dir = "../../dist" +asset_dir = "../../public" +sub_package = "packages/client" [web.app] title = "rust-2048" diff --git a/README.md b/README.md index 2c9e1e7..c12f3b6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Live Demo -> https://rust-2048.vercel.app/ ### Test and run ``` +1. Run server +cargo run -p server + +2. Run client // web dioxus serve --platform web @@ -33,6 +37,8 @@ dioxus serve --platform desktop - [ ] CI/CD * Web -> Vercel, done * Desktop? +- [x] Server, record game progress +- [ ] Server, record progress for multiple users ### Server diff --git a/index.html b/index.html index ce93ee2..6413e16 100644 --- a/index.html +++ b/index.html @@ -9,20 +9,12 @@ {style_include}
- diff --git a/packages/client/Cargo.toml b/packages/client/Cargo.toml new file mode 100644 index 0000000..a49351d --- /dev/null +++ b/packages/client/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "client" +version = "0.1.0" +description = "It's a 2048 game written in Rust." +authors = ["liu9293 "] +edition = "2021" + +[dependencies] +dioxus-router = "0.3.0" +rand = "0.8.5" +getrandom = { version = "0.2", features = ["js"] } +futures = "0.3" +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +log = "0.4" +shared = { path = "../shared" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +dioxus = "0.3.0" +dioxus-desktop = "0.3.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +dioxus = "0.3.2" +dioxus-web = "0.3.2" +wasm-logger = "0.2.0" +console_error_panic_hook = "0.1.7" + +[package.metadata.bundle] +name = "Rust 2048" +identifier = "com.rust.kaixiaozao" +version = "0.0.1" +copyright = "Copyright (c) KaiXiaoZao 2023. All rights reserved." +category = "Game" +short_description = "2048 game" +long_description = """ +It's a 2048 game written in Rust. +""" diff --git a/src/components/cell.rs b/packages/client/src/components/cell.rs similarity index 100% rename from src/components/cell.rs rename to packages/client/src/components/cell.rs diff --git a/src/components/mod.rs b/packages/client/src/components/mod.rs similarity index 100% rename from src/components/mod.rs rename to packages/client/src/components/mod.rs diff --git a/src/components/row.rs b/packages/client/src/components/row.rs similarity index 100% rename from src/components/row.rs rename to packages/client/src/components/row.rs diff --git a/src/main.rs b/packages/client/src/main.rs similarity index 74% rename from src/main.rs rename to packages/client/src/main.rs index f1d0d0d..317d431 100644 --- a/src/main.rs +++ b/packages/client/src/main.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] mod pages; mod components; -mod utils; +// mod utils; use dioxus::prelude::*; use dioxus_router::{Route, Router}; use crate::pages::game::Game; @@ -24,15 +24,8 @@ fn main() { @@ -45,12 +38,10 @@ fn main() { return } if (counter === 1) { - counter = 0 - new_e = new e.constructor(e.type, e); - gamearea.dispatchEvent(new_e); - setTimeout(function() { - counter = 1 - }, 100); + counter = 0 + new_e = new e.constructor(e.type, e); + gamearea.dispatchEvent(new_e); + setTimeout(function() { counter = 1 }, 20); } }); @@ -63,7 +54,6 @@ fn main() { #[cfg(target_arch = "wasm32")] // wasm_logger::init(wasm_logger::Config::default()); - // console_error_panic_hook::set_once(); dioxus_web::launch(app); } diff --git a/src/pages/game.rs b/packages/client/src/pages/game.rs similarity index 72% rename from src/pages/game.rs rename to packages/client/src/pages/game.rs index df2eb41..7b1e539 100644 --- a/src/pages/game.rs +++ b/packages/client/src/pages/game.rs @@ -1,14 +1,43 @@ use dioxus::prelude::*; use dioxus::html::input_data::keyboard_types::Key; use dioxus::html::KeyboardEvent; +use shared::types::{Board, GameStatus, ProgressReqeust}; +use reqwest; +use shared::logic::{get_initial_board_data, add_random, move_up, move_down, move_left, move_right, check_and_do_next}; +use log; use crate::components::row::Row; -use crate::utils::types::{Board, GameStatus}; -use crate::utils::logic::{get_initial_board_data, add_random, move_up, move_down, move_left, move_right, check_and_do_next}; pub fn Game(cx: Scope) -> Element { let game_status = use_state(cx, || GameStatus::Playing); let board_data: &UseState = use_state(cx, || get_initial_board_data()); - + let is_first_load = use_state(cx, || true); + + use_effect(cx, (is_first_load, board_data), |(is_first_load, board_data)| async move { + if !is_first_load.get() { + return; + } + + let client = reqwest::Client::new(); + let res = client.get("http://localhost:3000/progress").send().await; + match res { + Ok(response) => { + let payload = response.json::().await; + match payload { + Ok(data) => { + is_first_load.set(false); + board_data.set(data.board); + }, + Err(err) => { + log::error!("Failed to parse JSON: {}", err); + } + } + }, + Err(err) => { + log::error!("Failed to send request: {}", err); + } + } + }); + let handle_key_down_event = move |evt: KeyboardEvent| -> () { if *game_status.get() != GameStatus::Playing { return; @@ -36,7 +65,26 @@ pub fn Game(cx: Scope) -> Element { game_status.set(GameStatus::Fail); }, GameStatus::Playing => { board_data.set(new_data); }, - } + } + + cx.spawn({ + async move { + let client = reqwest::Client::new(); + let res = client.post("http://localhost:3000/progress") + .json(&ProgressReqeust { + board: new_data + }) + .send() + .await; + + match res { + Ok(_) => {}, + Err(err) => { + log::error!("Failed to record progress: {}", err); + } + } + } + }); }; let total_score = board_data.get().iter().flatten().sum::(); diff --git a/src/pages/homepage.rs b/packages/client/src/pages/homepage.rs similarity index 100% rename from src/pages/homepage.rs rename to packages/client/src/pages/homepage.rs diff --git a/src/pages/mod.rs b/packages/client/src/pages/mod.rs similarity index 100% rename from src/pages/mod.rs rename to packages/client/src/pages/mod.rs diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml new file mode 100644 index 0000000..eaef21d --- /dev/null +++ b/packages/server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +axum = { version = "0.6" } +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.3.0", features = ["cors"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +shared = { path = "../shared" } \ No newline at end of file diff --git a/packages/server/src/main.rs b/packages/server/src/main.rs new file mode 100644 index 0000000..f856622 --- /dev/null +++ b/packages/server/src/main.rs @@ -0,0 +1,89 @@ +use axum::{ + response::Json, + response::IntoResponse, + routing::get, + extract::{State}, + Router, + http::StatusCode +}; +use std::net::SocketAddr; +use std::{collections::HashMap, sync::{Arc, RwLock}}; +use tower_http::cors::{Any}; +use shared::types::{Board, ProgressResponse, ProgressReqeust}; + +type Db = Arc>>; + +#[tokio::main] +async fn main() { + let shared_state = Db::default(); + + let app = Router::new() + .route("/", get(ping)) + .route("/progress", get(get_user_progress).post(save_user_progress)) + .layer( + tower_http::cors::CorsLayer::new() + .allow_origin(Any) + .allow_headers(Any) + .allow_methods(Any), + ) + .with_state(Arc::clone(&shared_state)); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .expect("server should listen on port 3000"); +} + +async fn ping() -> &'static str { + "Hello World!" +} + +async fn save_user_progress( + State(db): State, + Json(input): Json +) -> impl IntoResponse { + let board: Board = input.board; + match db.write() { + Ok(mut db_instance) => { + db_instance.insert("test".to_string(), board); + (StatusCode::CREATED, Json(ProgressResponse{ + success: true, + board: Some(board) + })) + }, + Err(err) => { + println!("Error: {}", err); + (StatusCode::INTERNAL_SERVER_ERROR, Json(ProgressResponse{ + success: false, + board: None + })) + } + } +} + +async fn get_user_progress(State(db): State) -> impl IntoResponse { + match db.read() { + Ok(db_instance) => { + let board: Option = db_instance.get("test").cloned(); + match board { + Some(b) => (StatusCode::OK, Json(ProgressResponse { + success: true, + board: Some(b) + })), + None => (StatusCode::NOT_FOUND, Json(ProgressResponse{ + success: false, + board: None + })) + } + }, + Err(err) => { + println!("Error: {}", err); + (StatusCode::INTERNAL_SERVER_ERROR, Json(ProgressResponse{ + success: false, + board: None + })) + } + } +} \ No newline at end of file diff --git a/packages/shared/Cargo.toml b/packages/shared/Cargo.toml new file mode 100644 index 0000000..1abc36e --- /dev/null +++ b/packages/shared/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "shared" +version = "0.1.0" +description = "Shared mods across client and server" +authors = ["liu9293 "] +edition = "2021" + +[dependencies] +rand = "0.8.5" +getrandom = { version = "0.2", features = ["js"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" \ No newline at end of file diff --git a/src/utils/mod.rs b/packages/shared/src/lib.rs similarity index 100% rename from src/utils/mod.rs rename to packages/shared/src/lib.rs diff --git a/src/utils/logic.rs b/packages/shared/src/logic.rs similarity index 99% rename from src/utils/logic.rs rename to packages/shared/src/logic.rs index a5acde2..19d8b77 100644 --- a/src/utils/logic.rs +++ b/packages/shared/src/logic.rs @@ -1,6 +1,6 @@ use rand::seq::SliceRandom; use rand::Rng; -use crate::utils::types::{Board, GameStatus}; +use crate::types::{Board, GameStatus}; pub fn get_initial_board_data () -> [[i32; 4]; 4] { let mut init_board_data: [[i32; 4]; 4] = [[0; 4]; 4]; diff --git a/packages/shared/src/mod.rs b/packages/shared/src/mod.rs new file mode 100644 index 0000000..35e25a6 --- /dev/null +++ b/packages/shared/src/mod.rs @@ -0,0 +1,2 @@ +pub mod logic; +pub mod types; \ No newline at end of file diff --git a/packages/shared/src/types.rs b/packages/shared/src/types.rs new file mode 100644 index 0000000..4716dce --- /dev/null +++ b/packages/shared/src/types.rs @@ -0,0 +1,23 @@ +use serde::{Serialize, Deserialize}; + +pub type Row = [i32; 4]; +pub type Board = [Row; 4]; + +#[derive(PartialEq)] +pub enum GameStatus { + Playing, + Win, + Fail, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ProgressResponse { + pub success: bool, + pub board: Option, +} + + +#[derive(Debug, Serialize, Deserialize)] +pub struct ProgressReqeust { + pub board: Board, +} diff --git a/src/utils/types.rs b/src/utils/types.rs deleted file mode 100644 index 982af98..0000000 --- a/src/utils/types.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub type Row = [i32; 4]; - -pub type Board = [Row; 4]; - -#[derive(PartialEq)] -pub enum GameStatus { - Playing, - Win, - Fail, -} \ No newline at end of file