From dd96f8e7b0fd0a73e238fdae272211f066ba3984 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Tue, 21 May 2024 23:29:51 +0900 Subject: [PATCH 01/17] feat: display "FREE PLAY" if price is zero --- game/src/game/common.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/src/game/common.rs b/game/src/game/common.rs index 91f3230..df9dd33 100644 --- a/game/src/game/common.rs +++ b/game/src/game/common.rs @@ -39,7 +39,9 @@ pub(crate) fn render_common(context: &mut GameCommonContext) { .expect("Unable to load font"); // render a surface, and convert it to a texture bound to the canvas - let text = if context.price == 1 { + let text = if context.price == 0 { + "FREE PLAY".to_string() + } else if context.price == 1 { format!("CREDIT: {}", context.coins) } else { format!( From 0b3fe4af1e4ec210d0a65749ad7fb903d204bf66 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Tue, 21 May 2024 23:42:12 +0900 Subject: [PATCH 02/17] refactor: use atomic shared variable for coins --- game/src/game/common.rs | 16 ++++++---------- game/src/game/display_result.rs | 2 +- game/src/game/game_common_context.rs | 7 +++++-- game/src/game/game_player.rs | 4 ++-- game/src/game/init.rs | 10 ++++++++-- game/src/game/select_song.rs | 2 +- game/src/game/title.rs | 11 +++++++---- game/src/game/tutorial.rs | 10 +++++----- game/src/game/tutorial/greetings.rs | 2 +- game/src/game/tutorial/learn_stick_note.rs | 4 ++-- 10 files changed, 38 insertions(+), 30 deletions(-) diff --git a/game/src/game/common.rs b/game/src/game/common.rs index df9dd33..5356f23 100644 --- a/game/src/game/common.rs +++ b/game/src/game/common.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::Ordering; + use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect, render::TextureQuery}; use super::{ @@ -8,19 +10,13 @@ use crate::constants::{ DEFAULT_FONT_COLOR, DEFAULT_FONT_OUTLINE_COLOR, DEFAULT_FONT_PATH as FONT_PATH, }; -pub(crate) fn event_loop_common(event: &Event, coins: &mut u32) -> bool { +pub(crate) fn event_loop_common(event: &Event) -> bool { match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return true, - Event::KeyDown { - keycode: Some(Keycode::C), - .. - } => { - *coins += 1; - } _ => {} } @@ -42,12 +38,12 @@ pub(crate) fn render_common(context: &mut GameCommonContext) { let text = if context.price == 0 { "FREE PLAY".to_string() } else if context.price == 1 { - format!("CREDIT: {}", context.coins) + format!("CREDIT: {}", context.coins.load(Ordering::Relaxed)) } else { format!( "CREDIT: {} ({}/{})", - context.coins / context.price, - context.coins % context.price, + context.coins.load(Ordering::Relaxed) / context.price, + context.coins.load(Ordering::Relaxed) % context.price, context.price ) }; diff --git a/game/src/game/display_result.rs b/game/src/game/display_result.rs index b18a669..4144aaa 100644 --- a/game/src/game/display_result.rs +++ b/game/src/game/display_result.rs @@ -81,7 +81,7 @@ pub(crate) fn display_result( .unwrap(); loop { for event in common_context.event_pump.poll_iter() { - if event_loop_common(&event, &mut common_context.coins) { + if event_loop_common(&event) { return; } match event { diff --git a/game/src/game/game_common_context.rs b/game/src/game/game_common_context.rs index ad41437..e33e715 100644 --- a/game/src/game/game_common_context.rs +++ b/game/src/game/game_common_context.rs @@ -1,5 +1,8 @@ use std::{ - sync::{atomic::AtomicU8, Arc}, + sync::{ + atomic::{AtomicU32, AtomicU8}, + Arc, + }, time::Instant, }; @@ -10,7 +13,7 @@ use crate::serial::parse_janggu_bits; use bidrum_data_struct_lib::janggu::JangguInputState; pub(crate) struct GameCommonContext { - pub(crate) coins: u32, + pub(crate) coins: Arc, pub(crate) price: u32, pub(crate) sdl_context: sdl2::Sdl, pub(crate) audio_manager: AudioManager, diff --git a/game/src/game/game_player.rs b/game/src/game/game_player.rs index c1d6f9c..0998f99 100644 --- a/game/src/game/game_player.rs +++ b/game/src/game/game_player.rs @@ -71,7 +71,7 @@ pub(crate) fn play_song( let sound_data = loop { // process input events for event in common_context.event_pump.poll_iter() { - if event_loop_common(&event, &mut common_context.coins) { + if event_loop_common(&event) { return None; } } @@ -151,7 +151,7 @@ pub(crate) fn play_song( 'running: loop { let tick_now = clock.time().ticks as i128 - start_tick.ticks as i128; for event in common_context.event_pump.poll_iter() { - if event_loop_common(&event, &mut common_context.coins) { + if event_loop_common(&event) { handle.stop(Tween::default()).expect("Failed to stop song"); break 'running; } diff --git a/game/src/game/init.rs b/game/src/game/init.rs index 8297ac4..400f22c 100644 --- a/game/src/game/init.rs +++ b/game/src/game/init.rs @@ -1,5 +1,8 @@ use std::{ - sync::{atomic::AtomicU8, Arc}, + sync::{ + atomic::{AtomicU32, AtomicU8}, + Arc, + }, time::Instant, }; @@ -93,9 +96,12 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { // create freetype library let freetype_library = cairo::freetype::Library::init().expect("Failed to init FreeType"); + // create coin variable + let coins = Arc::new(AtomicU32::new(0)); + // create GameCommonContext object let mut context = GameCommonContext { - coins: 0, + coins: coins, price: 2, canvas: canvas, dpi: dpi, diff --git a/game/src/game/select_song.rs b/game/src/game/select_song.rs index c5e8d16..933ff0c 100644 --- a/game/src/game/select_song.rs +++ b/game/src/game/select_song.rs @@ -115,7 +115,7 @@ pub(crate) fn select_song( 'running: loop { // waiting user input for event in common_context.event_pump.poll_iter() { - if event_loop_common(&event, &mut common_context.coins) { + if event_loop_common(&event) { break 'running; } diff --git a/game/src/game/title.rs b/game/src/game/title.rs index 8b12304..9d879ec 100644 --- a/game/src/game/title.rs +++ b/game/src/game/title.rs @@ -1,3 +1,4 @@ +use std::sync::atomic::Ordering; use std::{path::Path, time::Duration}; use num_rational::Rational64; @@ -71,7 +72,7 @@ fn render_text(common_context: &mut GameCommonContext) { let mut texture = create_font_texture( &texture_creator, &mut font, - if common_context.coins >= common_context.price { + if common_context.coins.load(Ordering::Relaxed) >= common_context.price { "장구를 쳐서 시작하세요!" } else { "동전을 넣어주세요" @@ -132,7 +133,7 @@ pub(crate) fn render_title(common_context: &mut GameCommonContext) -> TitleResul .expect("Failed to create texture for title background video"); loop { for event in common_context.event_pump.poll_iter() { - if event_loop_common(&event, &mut common_context.coins) { + if event_loop_common(&event) { return TitleResult::Exit; } match event { @@ -140,8 +141,10 @@ pub(crate) fn render_title(common_context: &mut GameCommonContext) -> TitleResul keycode: Some(Keycode::Return), .. } => { - if common_context.coins >= common_context.price { - common_context.coins -= common_context.price; + if common_context.coins.load(Ordering::Relaxed) >= common_context.price { + common_context + .coins + .fetch_sub(common_context.price, Ordering::Relaxed); return TitleResult::StartGame; } } diff --git a/game/src/game/tutorial.rs b/game/src/game/tutorial.rs index a1dfbef..ccb87c3 100644 --- a/game/src/game/tutorial.rs +++ b/game/src/game/tutorial.rs @@ -56,7 +56,7 @@ fn ask_for_tutorial(common_context: &mut GameCommonContext) -> bool { } for i in common_context.event_pump.poll_iter() { - if event_loop_common(&i, &mut common_context.coins) { + if event_loop_common(&i) { background_video.stop_decoding(); return false; } @@ -93,7 +93,7 @@ fn ask_for_tutorial(common_context: &mut GameCommonContext) -> bool { } for i in common_context.event_pump.poll_iter() { - if event_loop_common(&i, &mut common_context.coins) { + if event_loop_common(&i) { background_video.stop_decoding(); return false; } @@ -134,7 +134,7 @@ fn ask_for_tutorial(common_context: &mut GameCommonContext) -> bool { let tick = dialog_started_at.elapsed().as_millis(); for i in common_context.event_pump.poll_iter() { - if event_loop_common(&i, &mut common_context.coins) { + if event_loop_common(&i) { selected = Some(false); break 'running; } @@ -207,7 +207,7 @@ fn ask_for_tutorial(common_context: &mut GameCommonContext) -> bool { } for i in common_context.event_pump.poll_iter() { - if event_loop_common(&i, &mut common_context.coins) { + if event_loop_common(&i) { background_video.stop_decoding(); return false; } @@ -299,7 +299,7 @@ pub(self) fn display_tutorial_messages( loop { let tick = janggu_state_and_tutorial_start_time.1.elapsed().as_millis() as i128; for event in common_context.event_pump.poll_iter() { - event_loop_common(&event, &mut common_context.coins); + event_loop_common(&event); } janggu_state_and_tutorial_start_time diff --git a/game/src/game/tutorial/greetings.rs b/game/src/game/tutorial/greetings.rs index 804399b..e002bd5 100644 --- a/game/src/game/tutorial/greetings.rs +++ b/game/src/game/tutorial/greetings.rs @@ -48,7 +48,7 @@ pub(crate) fn do_tutorial_greetings( } let tick = janggu_state_and_tutorial_start_time.1.elapsed().as_millis() as i128; for event in common_context.event_pump.poll_iter() { - event_loop_common(&event, &mut common_context.coins); + event_loop_common(&event); } janggu_state_and_tutorial_start_time diff --git a/game/src/game/tutorial/learn_stick_note.rs b/game/src/game/tutorial/learn_stick_note.rs index f2c3707..e04c318 100644 --- a/game/src/game/tutorial/learn_stick_note.rs +++ b/game/src/game/tutorial/learn_stick_note.rs @@ -81,7 +81,7 @@ fn display_animated_example_note( loop { for event in common_context.event_pump.poll_iter() { - event_loop_common(&event, &mut common_context.coins); + event_loop_common(&event); } if voice_started_at.elapsed() >= std::cmp::max(total_note_duration, message.1.duration()) { @@ -223,7 +223,7 @@ fn display_tryitout_notes( loop { for event in common_context.event_pump.poll_iter() { - event_loop_common(&event, &mut common_context.coins); + event_loop_common(&event); } // If tutorial ends, return From 143562d65222e26e372633faef5c2b2f340acc97 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Wed, 22 May 2024 00:07:21 +0900 Subject: [PATCH 03/17] refactor: separated thread for coin input processing --- game/src/game/init.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/game/src/game/init.rs b/game/src/game/init.rs index 400f22c..3498e2b 100644 --- a/game/src/game/init.rs +++ b/game/src/game/init.rs @@ -3,9 +3,10 @@ use std::{ atomic::{AtomicU32, AtomicU8}, Arc, }, - time::Instant, + time::{Duration, Instant}, }; +use device_query::{DeviceQuery, DeviceState, Keycode}; use kira::manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings}; use super::{game_common_context::GameCommonContext, start::start_game, title::render_title}; @@ -98,6 +99,24 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { // create coin variable let coins = Arc::new(AtomicU32::new(0)); + { + let coins_for_thread = coins.clone(); + + std::thread::spawn(move || { + let device_state = DeviceState::new(); + let mut pressed = false; + loop { + let new_pressed = device_state.get_keys().contains(&Keycode::C); + if new_pressed && !pressed { + // increase one on keydown + coins_for_thread.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + + pressed = new_pressed; + std::thread::sleep(Duration::from_millis(10)); + } + }); + } // create GameCommonContext object let mut context = GameCommonContext { From 563d1b9674948666750047ec469b3bf4a1f64072 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Wed, 22 May 2024 00:13:19 +0900 Subject: [PATCH 04/17] feat: add price parameter --- game/src/game/init.rs | 3 ++- game/src/main.rs | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/game/src/game/init.rs b/game/src/game/init.rs index 3498e2b..b4daa3d 100644 --- a/game/src/game/init.rs +++ b/game/src/game/init.rs @@ -16,6 +16,7 @@ pub struct InitGameOptions { pub height: Option, pub fullscreen: bool, pub vsync: bool, + pub price: u32, } pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { @@ -121,7 +122,7 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { // create GameCommonContext object let mut context = GameCommonContext { coins: coins, - price: 2, + price: options.price, canvas: canvas, dpi: dpi, sdl_context: sdl_context, diff --git a/game/src/main.rs b/game/src/main.rs index ce18440..5f0a36f 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -33,6 +33,9 @@ struct Args { /// Enables vsync or not? (Default: enabled in macos, disabled otherwise) #[arg(long)] vsync: Option, + /// Price + #[arg(long, default_value_t = 2)] + price: u32, } fn main() { @@ -79,6 +82,7 @@ fn main() { } else { false }), + price: args.price, }, ); } From d952d2d024db689ae3208177e222dc5d7fb13740 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Wed, 22 May 2024 00:26:00 +0900 Subject: [PATCH 05/17] feat: add uncommercial feature --- game/Cargo.toml | 4 ++++ game/src/game/common.rs | 6 +++++- game/src/main.rs | 21 ++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/game/Cargo.toml b/game/Cargo.toml index e33342f..65ad94c 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -7,6 +7,10 @@ description = "Rhythm game with korean traditional drum(janggu)-like controller" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +uncommercial = [] + [dependencies] clap = { version = "4.4.13", features = ["derive"] } ffmpeg-next = "7.0.0" diff --git a/game/src/game/common.rs b/game/src/game/common.rs index 5356f23..6a2fd25 100644 --- a/game/src/game/common.rs +++ b/game/src/game/common.rs @@ -36,7 +36,11 @@ pub(crate) fn render_common(context: &mut GameCommonContext) { // render a surface, and convert it to a texture bound to the canvas let text = if context.price == 0 { - "FREE PLAY".to_string() + if cfg!(feature = "uncommercial") { + "FREE PLAY (UNCOMMERCIAL)".to_string() + } else { + "FREE PLAY".to_string() + } } else if context.price == 1 { format!("CREDIT: {}", context.coins.load(Ordering::Relaxed)) } else { diff --git a/game/src/main.rs b/game/src/main.rs index 5f0a36f..01ccedf 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -34,11 +34,30 @@ struct Args { #[arg(long)] vsync: Option, /// Price + #[cfg(not(feature = "uncommercial"))] #[arg(long, default_value_t = 2)] price: u32, } +#[cfg(feature = "uncommercial")] +macro_rules! price { + ($args: expr) => { + 0 + }; +} + +#[cfg(not(feature = "uncommercial"))] +macro_rules! price { + ($args: expr) => { + $args.price + }; +} + fn main() { + if cfg!(feature = "uncommercial") { + println!("This is uncommercial version, only free play is available."); + } + let args = Args::parse(); let bits = AtomicU8::new(0); let bits_arc = Arc::new(bits); @@ -82,7 +101,7 @@ fn main() { } else { false }), - price: args.price, + price: price!(args), }, ); } From d5bf9f4efb78da102750910683993dd4033ec99b Mon Sep 17 00:00:00 2001 From: LiteHell Date: Wed, 22 May 2024 00:26:11 +0900 Subject: [PATCH 06/17] refactor: no coin input thread when price is zero --- game/src/game/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/game/init.rs b/game/src/game/init.rs index b4daa3d..1748847 100644 --- a/game/src/game/init.rs +++ b/game/src/game/init.rs @@ -100,7 +100,7 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { // create coin variable let coins = Arc::new(AtomicU32::new(0)); - { + if options.price != 0 { let coins_for_thread = coins.clone(); std::thread::spawn(move || { From 8495f29d9b1e57b40f13c397f49edfeeee21e0f7 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Tue, 4 Jun 2024 23:26:41 +0900 Subject: [PATCH 07/17] feat: be ready for coin/bill acceptor hardware --- Cargo.lock | 13 +- Cargo.toml | 3 +- controller-lib/Cargo.toml | 11 ++ controller-lib/src/keyboard.rs | 2 + controller-lib/src/keyboard/coin_device.rs | 70 ++++++++++ controller-lib/src/keyboard/janggu_device.rs | 94 +++++++++++++ controller-lib/src/lib.rs | 17 +++ controller-lib/src/serial.rs | 15 +++ controller-lib/src/serial/coin_device.rs | 72 ++++++++++ controller-lib/src/serial/janggu_device.rs | 44 +++++++ controller-lib/src/serial/serial_reader.rs | 92 +++++++++++++ game/Cargo.toml | 2 +- game/src/controller_wrapper.rs | 123 ++++++++++++++++++ game/src/game/common.rs | 8 +- game/src/game/display_result.rs | 1 - game/src/game/game_common_context.rs | 19 +-- .../game/game_player/effect_sound_player.rs | 2 +- game/src/game/init.rs | 39 +----- game/src/game/select_song.rs | 4 +- game/src/game/title.rs | 9 +- game/src/janggu_keyboard.rs | 26 ---- game/src/main.rs | 73 +++-------- game/src/serial.rs | 48 ------- 23 files changed, 592 insertions(+), 195 deletions(-) create mode 100644 controller-lib/Cargo.toml create mode 100644 controller-lib/src/keyboard.rs create mode 100644 controller-lib/src/keyboard/coin_device.rs create mode 100644 controller-lib/src/keyboard/janggu_device.rs create mode 100644 controller-lib/src/lib.rs create mode 100644 controller-lib/src/serial.rs create mode 100644 controller-lib/src/serial/coin_device.rs create mode 100644 controller-lib/src/serial/janggu_device.rs create mode 100644 controller-lib/src/serial/serial_reader.rs create mode 100644 game/src/controller_wrapper.rs delete mode 100644 game/src/janggu_keyboard.rs delete mode 100644 game/src/serial.rs diff --git a/Cargo.lock b/Cargo.lock index ba1d0d9..08605ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "bidrum" version = "0.1.0" dependencies = [ + "bidrum-controller-lib", "bidrum-data-struct-lib", "cairo-rs", "clap", @@ -116,6 +117,14 @@ dependencies = [ "sdl2", "serde", "serde_json", +] + +[[package]] +name = "bidrum-controller-lib" +version = "0.1.0" +dependencies = [ + "bidrum-data-struct-lib", + "device_query", "serialport", ] @@ -690,9 +699,9 @@ dependencies = [ [[package]] name = "io-kit-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ "core-foundation-sys", "mach2", diff --git a/Cargo.toml b/Cargo.toml index 32ca1c9..0997b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "game", "data-struct-lib", - "chart-recorder" + "chart-recorder", + "controller-lib" ] [profile.dev.package.kira] diff --git a/controller-lib/Cargo.toml b/controller-lib/Cargo.toml new file mode 100644 index 0000000..6a57dac --- /dev/null +++ b/controller-lib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bidrum-controller-lib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bidrum-data-struct-lib = { path = "../data-struct-lib" } +serialport = "4.3.0" +device_query = "2.0.0" \ No newline at end of file diff --git a/controller-lib/src/keyboard.rs b/controller-lib/src/keyboard.rs new file mode 100644 index 0000000..e41bb43 --- /dev/null +++ b/controller-lib/src/keyboard.rs @@ -0,0 +1,2 @@ +pub mod coin_device; +pub mod janggu_device; diff --git a/controller-lib/src/keyboard/coin_device.rs b/controller-lib/src/keyboard/coin_device.rs new file mode 100644 index 0000000..a5842c9 --- /dev/null +++ b/controller-lib/src/keyboard/coin_device.rs @@ -0,0 +1,70 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU32, Ordering}, + Arc, + }, + time::Duration, +}; + +use device_query::{DeviceQuery, DeviceState, Keycode}; + +use crate::CoinInputDevice; + +/// United bidrum controller of Janggu and Coin/Bill acceptor +pub struct KeyboardCoinDevice { + unconsumed_coins: Arc, + stopping: Arc, +} + +impl Drop for KeyboardCoinDevice { + fn drop(&mut self) { + self.stopping.store(true, Ordering::Relaxed); + } +} + +impl CoinInputDevice for KeyboardCoinDevice { + fn get_unconsumed_coins(&self) -> u32 { + self.unconsumed_coins.load(Ordering::Relaxed) + } + + fn consume_coins(&mut self, coins: u32) { + self.unconsumed_coins.fetch_sub(coins, Ordering::Relaxed); + } +} + +impl KeyboardCoinDevice { + pub fn new() -> KeyboardCoinDevice { + let unconsumed_coins = Arc::new(AtomicU32::new(0)); + let stopping = Arc::new(AtomicBool::new(false)); + + { + let unconsumed_coins = unconsumed_coins.clone(); + + let _stopping = stopping.clone(); + + std::thread::spawn(move || { + let mut pressed = false; + let device_state = DeviceState::new(); + loop { + let new_pressed = device_state.get_keys().contains(&Keycode::C); + if new_pressed && !pressed { + // increase one on keydown + unconsumed_coins.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + + pressed = new_pressed; + std::thread::sleep(Duration::from_millis(10)); + } + }); + } + + KeyboardCoinDevice { + unconsumed_coins: unconsumed_coins, + stopping: stopping, + } + } +} + +fn has_coin_bit(bits: u8) -> bool { + bits & 16 != 0 +} diff --git a/controller-lib/src/keyboard/janggu_device.rs b/controller-lib/src/keyboard/janggu_device.rs new file mode 100644 index 0000000..04bc162 --- /dev/null +++ b/controller-lib/src/keyboard/janggu_device.rs @@ -0,0 +1,94 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU8, Ordering}, + Arc, RwLock, + }, + thread, +}; + +use bidrum_data_struct_lib::janggu::{JangguFace, JangguInputState}; +use device_query::{DeviceQuery, DeviceState, Keycode}; + +use crate::JangguDevice; + +pub struct KeyboardJangguDevice { + stopping: Arc, + // Using RwLock is too slow + state: Arc, +} + +impl KeyboardJangguDevice { + pub fn new() -> KeyboardJangguDevice { + let stopping = Arc::new(AtomicBool::new(false)); + let state = Arc::new(AtomicU8::new(0)); + + { + let stopping = stopping.clone(); + let state = state.clone(); + + thread::spawn(move || { + let mut device_states = DeviceState::new(); + loop { + if stopping.load(Ordering::Relaxed) { + break; + } + + state.store(keyboard_to_bits(&mut device_states), Ordering::Relaxed); + } + }); + } + + KeyboardJangguDevice { + state: state, + stopping: stopping, + } + } +} + +impl Drop for KeyboardJangguDevice { + fn drop(&mut self) { + self.stopping.store(true, Ordering::Relaxed); + } +} + +impl JangguDevice for KeyboardJangguDevice { + fn read_janggu_input_state(&self) -> JangguInputState { + bits_to_janggu_input_state(self.state.load(Ordering::Relaxed)) + } +} + +fn bits_to_janggu_input_state(bits: u8) -> JangguInputState { + JangguInputState { + 궁채: if bits & 1 != 0 { + Some(JangguFace::궁편) + } else if bits & 2 != 0 { + Some(JangguFace::열편) + } else { + None + }, + 열채: if bits & 4 != 0 { + Some(JangguFace::궁편) + } else if bits & 8 != 0 { + Some(JangguFace::열편) + } else { + None + }, + } +} + +fn keyboard_to_bits(device_states: &mut DeviceState) -> u8 { + let keys = device_states.get_keys(); + let mut bits = 0; + if keys.contains(&Keycode::D) { + bits |= 1; + } else if keys.contains(&Keycode::F) { + bits |= 2; + } + if keys.contains(&Keycode::J) { + bits |= 4; + } else if keys.contains(&Keycode::K) { + bits |= 8; + } + + return bits; +} diff --git a/controller-lib/src/lib.rs b/controller-lib/src/lib.rs new file mode 100644 index 0000000..763eab2 --- /dev/null +++ b/controller-lib/src/lib.rs @@ -0,0 +1,17 @@ +pub mod keyboard; +pub mod serial; + +use bidrum_data_struct_lib::janggu::JangguInputState; + +/// Bidrum Janggu Controller +pub trait JangguDevice { + /// Reads janggu controller input state + fn read_janggu_input_state(&self) -> JangguInputState; +} + +/// Bidrum Coin/Bill Acceptor +pub trait CoinInputDevice { + /// Reads unconsumed coin count + fn get_unconsumed_coins(&self) -> u32; + fn consume_coins(&mut self, coins: u32); +} diff --git a/controller-lib/src/serial.rs b/controller-lib/src/serial.rs new file mode 100644 index 0000000..c59d347 --- /dev/null +++ b/controller-lib/src/serial.rs @@ -0,0 +1,15 @@ +mod coin_device; +mod janggu_device; +mod serial_reader; + +use coin_device::SerialCoinDevice; +use janggu_device::SerialJangguDevice; +use serial_reader::BidrumSerialReader; + +pub fn new(serial_port: String) -> (SerialJangguDevice, SerialCoinDevice) { + let serial_reader = BidrumSerialReader::new(serial_port); + return ( + SerialJangguDevice::new(serial_reader.clone()), + SerialCoinDevice::new(serial_reader.clone()), + ); +} diff --git a/controller-lib/src/serial/coin_device.rs b/controller-lib/src/serial/coin_device.rs new file mode 100644 index 0000000..25388bf --- /dev/null +++ b/controller-lib/src/serial/coin_device.rs @@ -0,0 +1,72 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU32, Ordering}, + Arc, + }, + thread::{self}, +}; + +use crate::CoinInputDevice; + +use super::serial_reader::BidrumSerialReader; + +/// United bidrum controller of Janggu and Coin/Bill acceptor +pub struct SerialCoinDevice { + serial_reader: BidrumSerialReader, + unconsumed_coins: Arc, + stopping: Arc, +} + +impl Drop for SerialCoinDevice { + fn drop(&mut self) { + self.stopping.store(true, Ordering::Relaxed); + } +} + +impl CoinInputDevice for SerialCoinDevice { + fn get_unconsumed_coins(&self) -> u32 { + self.unconsumed_coins.load(Ordering::Relaxed) + } + + fn consume_coins(&mut self, coins: u32) { + self.unconsumed_coins.fetch_sub(coins, Ordering::Relaxed); + } +} + +impl SerialCoinDevice { + pub(super) fn new(serial_reader: BidrumSerialReader) -> SerialCoinDevice { + let unconsumed_coins = Arc::new(AtomicU32::new(0)); + let stopping = Arc::new(AtomicBool::new(false)); + + { + let unconsumed_coins = unconsumed_coins.clone(); + let bits = serial_reader.bits.clone(); + let stopping = stopping.clone(); + thread::spawn(move || { + let mut prev = false; + loop { + if stopping.load(Ordering::Relaxed) { + break; + } + + let current = has_coin_bit(bits.load(Ordering::Relaxed)); + if !prev && current { + unconsumed_coins.fetch_add(1, Ordering::Relaxed); + } + + prev = current; + } + }); + } + + SerialCoinDevice { + serial_reader: serial_reader, + unconsumed_coins: unconsumed_coins, + stopping: stopping, + } + } +} + +fn has_coin_bit(bits: u8) -> bool { + bits & 16 != 0 +} diff --git a/controller-lib/src/serial/janggu_device.rs b/controller-lib/src/serial/janggu_device.rs new file mode 100644 index 0000000..8113e8e --- /dev/null +++ b/controller-lib/src/serial/janggu_device.rs @@ -0,0 +1,44 @@ +use std::sync::atomic::Ordering; + +use bidrum_data_struct_lib::janggu::{JangguFace, JangguInputState}; + +use crate::JangguDevice; + +use super::serial_reader::BidrumSerialReader; + +pub struct SerialJangguDevice { + serial_reader: BidrumSerialReader, +} + +impl SerialJangguDevice { + pub(super) fn new(serial_reader: BidrumSerialReader) -> SerialJangguDevice { + SerialJangguDevice { + serial_reader: serial_reader, + } + } +} + +impl JangguDevice for SerialJangguDevice { + fn read_janggu_input_state(&self) -> JangguInputState { + return parse_janggu_bits(self.serial_reader.bits.load(Ordering::Relaxed)); + } +} + +fn parse_janggu_bits(bits: u8) -> JangguInputState { + return JangguInputState { + 궁채: if bits & 1 != 0 { + Some(JangguFace::궁편) + } else if bits & 2 != 0 { + Some(JangguFace::열편) + } else { + None + }, + 열채: if bits & 4 != 0 { + Some(JangguFace::궁편) + } else if bits & 8 != 0 { + Some(JangguFace::열편) + } else { + None + }, + }; +} diff --git a/controller-lib/src/serial/serial_reader.rs b/controller-lib/src/serial/serial_reader.rs new file mode 100644 index 0000000..bc94923 --- /dev/null +++ b/controller-lib/src/serial/serial_reader.rs @@ -0,0 +1,92 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}, + Arc, + }, + thread::{self, sleep}, + time::Duration, +}; + +use serialport::SerialPort; + +/// United bidrum controller of Janggu and Coin/Bill acceptor +pub(super) struct BidrumSerialReader { + stopping: Arc, + pub(super) bits: Arc, + counter: Arc, +} + +impl Clone for BidrumSerialReader { + fn clone(&self) -> Self { + self.counter.fetch_add(1, Ordering::Relaxed); + + Self { + stopping: self.stopping.clone(), + bits: self.bits.clone(), + counter: self.counter.clone(), + } + } +} + +impl Drop for BidrumSerialReader { + fn drop(&mut self) { + self.counter.fetch_sub(1, Ordering::Relaxed); + + if self.counter.load(Ordering::Relaxed) == 0 { + self.stopping.store(true, Ordering::Relaxed); + } + } +} + +impl BidrumSerialReader { + pub(super) fn new(controller_port: String) -> BidrumSerialReader { + println!("Openning serial port {}", controller_port); + + let mut port = serialport::new(controller_port, 9600) + .timeout(Duration::from_millis(20)) + .open() + .expect("Failed to open port"); + + println!("Waiting 3 seconds (Arduino needs time for serial initialization)"); + sleep(Duration::from_millis(3000)); + println!("Waited 3 seconds!"); + + let stopping = Arc::new(AtomicBool::new(false)); + let bits = Arc::new(AtomicU8::new(0)); + let counter = Arc::new(AtomicU32::new(1)); + { + let stopping = stopping.clone(); + let bits = bits.clone(); + + thread::spawn(move || loop { + if stopping.load(Ordering::Relaxed) { + break; + } + read_serial(&mut port, &bits); + }); + } + + BidrumSerialReader { + stopping: stopping, + counter: counter, + bits: bits, + } + } +} + +/// Read serial inputs from port and emulates key inputs +fn read_serial(port: &mut Box, bits_data: &Arc) { + loop { + let available_bytes: u32 = port.bytes_to_read().expect("Failed to read buffer size"); + if available_bytes > 0 { + break; + } + sleep(Duration::from_millis(10)); + } + let mut message: [u8; 1] = [0]; + + port.read_exact(message.as_mut_slice()) + .expect("Controller reading failure!"); + + bits_data.store(message[0], std::sync::atomic::Ordering::Relaxed); +} diff --git a/game/Cargo.toml b/game/Cargo.toml index 65ad94c..42e309a 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -19,8 +19,8 @@ num-rational = "0.4.1" sdl2 = { version = "0.36.0", features = ["image"] } serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" -serialport = "4.3.0" bidrum-data-struct-lib = { path = "../data-struct-lib" } +bidrum-controller-lib = { path = "../controller-lib" } device_query = "2.0.0" ezing = "0.2.1" const_format = "0.2.32" diff --git a/game/src/controller_wrapper.rs b/game/src/controller_wrapper.rs new file mode 100644 index 0000000..631056a --- /dev/null +++ b/game/src/controller_wrapper.rs @@ -0,0 +1,123 @@ +use std::{ + sync::{ + atomic::{AtomicBool, AtomicU32, Ordering}, + Arc, RwLock, + }, + thread, +}; + +use bidrum_controller_lib::{keyboard, serial, CoinInputDevice, JangguDevice}; +use bidrum_data_struct_lib::janggu::JangguInputState; + +/// Wrapper of Coin/Janggu controller +/// to avoid cumbersome ownership/borrow/lifetime problems +pub struct ControllerWrapper { + janggu_state: Arc>, + coins: Arc, + coins_to_consume: Arc, + stopping: Arc, +} + +impl Drop for ControllerWrapper { + fn drop(&mut self) { + self.stopping.store(true, Ordering::Relaxed); + } +} + +impl ControllerWrapper { + pub fn read_janggu_state(&self) -> JangguInputState { + *self.janggu_state.read().expect("Failed to get lock") + } + pub fn get_coins(&self) -> u32 { + self.coins.load(Ordering::Relaxed) + } + pub fn consume_coins(&mut self, coins: u32) { + self.coins_to_consume.fetch_add(coins, Ordering::Relaxed); + } + pub fn keyboard() -> ControllerWrapper { + let coins = Arc::new(AtomicU32::new(0)); + let coins_to_consume = Arc::new(AtomicU32::new(0)); + let stopping = Arc::new(AtomicBool::new(false)); + let janggu_state = Arc::new(RwLock::new(JangguInputState { + 궁채: None, + 열채: None, + })); + { + let coins = coins.clone(); + let coins_to_consume = coins_to_consume.clone(); + let stopping = stopping.clone(); + let janggu_state = janggu_state.clone(); + + thread::spawn(move || { + let mut coin_device = keyboard::coin_device::KeyboardCoinDevice::new(); + let janggu_device = keyboard::janggu_device::KeyboardJangguDevice::new(); + + loop { + if stopping.load(Ordering::Relaxed) { + break; + } + + let consume = coins_to_consume.load(Ordering::Relaxed); + if consume > 0 { + coin_device.consume_coins(consume); + coins_to_consume.fetch_sub(consume, Ordering::Relaxed); + } + + coins.store(coin_device.get_unconsumed_coins(), Ordering::Relaxed); + let mut janggu_state = janggu_state.write().expect("Failed to get lock"); + *janggu_state = janggu_device.read_janggu_input_state(); + } + }); + } + + ControllerWrapper { + janggu_state: janggu_state, + coins: coins, + coins_to_consume: coins_to_consume, + stopping: stopping, + } + } + + pub fn serial(controller_port: String) -> ControllerWrapper { + let coins = Arc::new(AtomicU32::new(0)); + let coins_to_consume = Arc::new(AtomicU32::new(0)); + let stopping = Arc::new(AtomicBool::new(false)); + let janggu_state = Arc::new(RwLock::new(JangguInputState { + 궁채: None, + 열채: None, + })); + { + let coins = coins.clone(); + let coins_to_consume = coins_to_consume.clone(); + let stopping = stopping.clone(); + let janggu_state = janggu_state.clone(); + + thread::spawn(move || { + let (janggu_device, mut coin_device) = serial::new(controller_port); + + loop { + if stopping.load(Ordering::Relaxed) { + break; + } + + let consume = coins_to_consume.load(Ordering::Relaxed); + if consume > 0 { + coin_device.consume_coins(consume); + coins_to_consume.fetch_sub(consume, Ordering::Relaxed); + } + + coins.store(coin_device.get_unconsumed_coins(), Ordering::Relaxed); + let mut janggu_state = janggu_state.write().expect("Failed to get lock"); + *janggu_state = janggu_device.read_janggu_input_state(); + } + }); + } + + ControllerWrapper { + janggu_state: janggu_state, + coins: coins, + coins_to_consume: coins_to_consume, + stopping: stopping, + } + } +} diff --git a/game/src/game/common.rs b/game/src/game/common.rs index 6a2fd25..2b0d2f6 100644 --- a/game/src/game/common.rs +++ b/game/src/game/common.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::Ordering; - use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect, render::TextureQuery}; use super::{ @@ -42,12 +40,12 @@ pub(crate) fn render_common(context: &mut GameCommonContext) { "FREE PLAY".to_string() } } else if context.price == 1 { - format!("CREDIT: {}", context.coins.load(Ordering::Relaxed)) + format!("CREDIT: {}", context.coin_and_janggu.get_coins()) } else { format!( "CREDIT: {} ({}/{})", - context.coins.load(Ordering::Relaxed) / context.price, - context.coins.load(Ordering::Relaxed) % context.price, + context.coin_and_janggu.get_coins() / context.price, + context.coin_and_janggu.get_coins() % context.price, context.price ) }; diff --git a/game/src/game/display_result.rs b/game/src/game/display_result.rs index 4144aaa..45f596d 100644 --- a/game/src/game/display_result.rs +++ b/game/src/game/display_result.rs @@ -3,7 +3,6 @@ use std::path::Path; use sdl2::{ event::Event, keyboard::Keycode, - pixels::Color, rect::Rect, render::{Canvas, TextureQuery}, video::Window, diff --git a/game/src/game/game_common_context.rs b/game/src/game/game_common_context.rs index e33e715..67e460e 100644 --- a/game/src/game/game_common_context.rs +++ b/game/src/game/game_common_context.rs @@ -1,25 +1,19 @@ -use std::{ - sync::{ - atomic::{AtomicU32, AtomicU8}, - Arc, - }, - time::Instant, -}; +use std::time::Instant; use kira::manager::AudioManager; use sdl2::{render::Canvas, video::Window, EventPump}; -use crate::serial::parse_janggu_bits; use bidrum_data_struct_lib::janggu::JangguInputState; +use crate::controller_wrapper::ControllerWrapper; + pub(crate) struct GameCommonContext { - pub(crate) coins: Arc, + pub(crate) coin_and_janggu: ControllerWrapper, pub(crate) price: u32, pub(crate) sdl_context: sdl2::Sdl, pub(crate) audio_manager: AudioManager, pub(crate) canvas: Canvas, pub(crate) event_pump: EventPump, - pub(crate) janggu_bits_ptr: Arc, /// ddpi, hdpi, vdpi pub(crate) dpi: (f32, f32, f32), pub(crate) game_initialized_at: Instant, @@ -28,9 +22,6 @@ pub(crate) struct GameCommonContext { impl GameCommonContext { pub(crate) fn read_janggu_state(&self) -> JangguInputState { - return parse_janggu_bits( - self.janggu_bits_ptr - .load(std::sync::atomic::Ordering::Relaxed), - ); + self.coin_and_janggu.read_janggu_state() } } diff --git a/game/src/game/game_player/effect_sound_player.rs b/game/src/game/game_player/effect_sound_player.rs index 96ba469..27d6708 100644 --- a/game/src/game/game_player/effect_sound_player.rs +++ b/game/src/game/game_player/effect_sound_player.rs @@ -7,7 +7,7 @@ use kira::{ }; use rand::seq::SliceRandom; -use crate::{constants::DEFAULT_SOUND_PATH as SOUND_PATH}; +use crate::constants::DEFAULT_SOUND_PATH as SOUND_PATH; use super::{game_result::GameResult, janggu_state_with_tick::JangguStateWithTick}; diff --git a/game/src/game/init.rs b/game/src/game/init.rs index 1748847..1834ffb 100644 --- a/game/src/game/init.rs +++ b/game/src/game/init.rs @@ -1,14 +1,9 @@ -use std::{ - sync::{ - atomic::{AtomicU32, AtomicU8}, - Arc, - }, - time::{Duration, Instant}, -}; - -use device_query::{DeviceQuery, DeviceState, Keycode}; +use std::time::Instant; + use kira::manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings}; +use crate::controller_wrapper::ControllerWrapper; + use super::{game_common_context::GameCommonContext, start::start_game, title::render_title}; pub struct InitGameOptions { @@ -19,7 +14,7 @@ pub struct InitGameOptions { pub price: u32, } -pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { +pub(crate) fn init_game(controller_wrapper: ControllerWrapper, options: InitGameOptions) { // init sdl let sdl_context = sdl2::init().expect("sdl context initialization Fail"); @@ -98,30 +93,9 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { // create freetype library let freetype_library = cairo::freetype::Library::init().expect("Failed to init FreeType"); - // create coin variable - let coins = Arc::new(AtomicU32::new(0)); - if options.price != 0 { - let coins_for_thread = coins.clone(); - - std::thread::spawn(move || { - let device_state = DeviceState::new(); - let mut pressed = false; - loop { - let new_pressed = device_state.get_keys().contains(&Keycode::C); - if new_pressed && !pressed { - // increase one on keydown - coins_for_thread.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - } - - pressed = new_pressed; - std::thread::sleep(Duration::from_millis(10)); - } - }); - } - // create GameCommonContext object let mut context = GameCommonContext { - coins: coins, + coin_and_janggu: controller_wrapper, price: options.price, canvas: canvas, dpi: dpi, @@ -129,7 +103,6 @@ pub(crate) fn init_game(janggu_bits: Arc, options: InitGameOptions) { event_pump: event_pump, audio_manager: AudioManager::::new(AudioManagerSettings::default()) .expect("AudioManager initialization failure"), - janggu_bits_ptr: janggu_bits, game_initialized_at: Instant::now(), freetype_library: freetype_library, }; diff --git a/game/src/game/select_song.rs b/game/src/game/select_song.rs index 933ff0c..d2a10ae 100644 --- a/game/src/game/select_song.rs +++ b/game/src/game/select_song.rs @@ -1,9 +1,7 @@ use std::{path::Path, time::Instant}; use bidrum_data_struct_lib::song::GameSong; -use sdl2::{ - event::Event, image::LoadTexture, keyboard::Keycode, pixels::Color, rect::Rect, render::Texture, -}; +use sdl2::{event::Event, image::LoadTexture, keyboard::Keycode, rect::Rect, render::Texture}; use crate::constants::DEFAULT_FONT_PATH as FONT_PATH; use crate::constants::DEFAULT_IMG_PATH as IMG_PATH; diff --git a/game/src/game/title.rs b/game/src/game/title.rs index 9d879ec..b695c08 100644 --- a/game/src/game/title.rs +++ b/game/src/game/title.rs @@ -1,4 +1,3 @@ -use std::sync::atomic::Ordering; use std::{path::Path, time::Duration}; use num_rational::Rational64; @@ -72,7 +71,7 @@ fn render_text(common_context: &mut GameCommonContext) { let mut texture = create_font_texture( &texture_creator, &mut font, - if common_context.coins.load(Ordering::Relaxed) >= common_context.price { + if common_context.coin_and_janggu.get_coins() >= common_context.price { "장구를 쳐서 시작하세요!" } else { "동전을 넣어주세요" @@ -141,10 +140,10 @@ pub(crate) fn render_title(common_context: &mut GameCommonContext) -> TitleResul keycode: Some(Keycode::Return), .. } => { - if common_context.coins.load(Ordering::Relaxed) >= common_context.price { + if common_context.coin_and_janggu.get_coins() >= common_context.price { common_context - .coins - .fetch_sub(common_context.price, Ordering::Relaxed); + .coin_and_janggu + .consume_coins(common_context.price); return TitleResult::StartGame; } } diff --git a/game/src/janggu_keyboard.rs b/game/src/janggu_keyboard.rs deleted file mode 100644 index d3ef090..0000000 --- a/game/src/janggu_keyboard.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::sync::{atomic::AtomicU8, Arc}; - -use device_query::{DeviceQuery, DeviceState, Keycode}; - -/// Read keyboard inputs and save janggu input bits -pub(crate) fn read_janggu_key_loop(bits_data: Arc) { - loop { - let mut bits: u8 = 0; - let device_states = DeviceState::new(); - let keys = device_states.get_keys(); - if keys.contains(&Keycode::D) { - bits |= 1; - } - if keys.contains(&Keycode::F) { - bits |= 2; - } - if keys.contains(&Keycode::J) { - bits |= 4; - } - if keys.contains(&Keycode::K) { - bits |= 8; - } - - bits_data.store(bits, std::sync::atomic::Ordering::Relaxed); - } -} diff --git a/game/src/main.rs b/game/src/main.rs index 01ccedf..f341641 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,19 +1,10 @@ mod constants; +mod controller_wrapper; mod game; -mod janggu_keyboard; -mod serial; - -use std::{ - sync::{atomic::AtomicU8, Arc}, - thread::{self, sleep}, - time::Duration, -}; use clap::Parser; +use controller_wrapper::ControllerWrapper; use game::init::{init_game, InitGameOptions}; -use janggu_keyboard::read_janggu_key_loop; - -use crate::serial::read_serial_loop; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -59,49 +50,21 @@ fn main() { } let args = Args::parse(); - let bits = AtomicU8::new(0); - let bits_arc = Arc::new(bits); - match args.controller_port { - Some(controller_port) => { - println!("Openning serial port {}", controller_port); - - let port = serialport::new(controller_port, 9600) - .timeout(Duration::from_millis(20)) - .open() - .expect("Failed to open port"); - - println!("Waiting 3 seconds (Arduino needs time for serial initialization)"); - sleep(Duration::from_millis(3000)); - println!("Waited 3 seconds!"); - - let ptr = bits_arc.clone(); - thread::spawn(move || { - read_serial_loop(port, ptr); - }); - } - _ => { - println!("Controller port not provided! Reading keyboard...."); - - let ptr = bits_arc.clone(); - thread::spawn(move || { - read_janggu_key_loop(ptr); - }); - } - } + let options = InitGameOptions { + fullscreen: !args.windowed, + height: args.window_height, + width: args.window_width, + vsync: args.vsync.unwrap_or(if cfg!(target_os = "macos") { + true + } else { + false + }), + price: price!(args), + }; - let ptr = bits_arc.clone(); - init_game( - ptr, - InitGameOptions { - fullscreen: !args.windowed, - height: args.window_height, - width: args.window_width, - vsync: args.vsync.unwrap_or(if cfg!(target_os = "macos") { - true - } else { - false - }), - price: price!(args), - }, - ); + let controller_wrapper = match args.controller_port { + Some(controller_port) => ControllerWrapper::serial(controller_port), + _ => ControllerWrapper::keyboard(), + }; + init_game(controller_wrapper, options); } diff --git a/game/src/serial.rs b/game/src/serial.rs deleted file mode 100644 index cc99874..0000000 --- a/game/src/serial.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::{ - sync::{atomic::AtomicU8, Arc}, - thread::sleep, - time::Duration, -}; - -use bidrum_data_struct_lib::janggu::JangguFace; -use serialport::SerialPort; - -use bidrum_data_struct_lib::janggu::JangguInputState; - -pub(crate) fn parse_janggu_bits(bits: u8) -> JangguInputState { - JangguInputState { - 궁채: if bits & 1 != 0 { - Some(JangguFace::궁편) - } else if bits & 2 != 0 { - Some(JangguFace::열편) - } else { - None - }, - 열채: if bits & 4 != 0 { - Some(JangguFace::궁편) - } else if bits & 8 != 0 { - Some(JangguFace::열편) - } else { - None - }, - } -} - -/// Read serial inputs from port and emulates key inputs -pub(crate) fn read_serial_loop(mut port: Box, bits_data: Arc) { - loop { - loop { - let available_bytes: u32 = port.bytes_to_read().expect("Failed to read buffer size"); - if available_bytes > 0 { - break; - } - sleep(Duration::from_millis(10)); - } - let mut message: [u8; 1] = [0]; - - port.read_exact(message.as_mut_slice()) - .expect("Controller reading failure!"); - - bits_data.store(message[0], std::sync::atomic::Ordering::Relaxed); - } -} From 1e0fa869de1b5659f0607d612a4516e785b17a33 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Fri, 7 Jun 2024 16:12:05 +0900 Subject: [PATCH 08/17] feat: add beat guideline --- game/src/game/display_result.rs | 1 - game/src/game/game_player/chart_player.rs | 45 +++++++++++++++- game/src/game/game_player/chart_player_ui.rs | 57 +++++++++++++++++--- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/game/src/game/display_result.rs b/game/src/game/display_result.rs index b18a669..214dd04 100644 --- a/game/src/game/display_result.rs +++ b/game/src/game/display_result.rs @@ -3,7 +3,6 @@ use std::path::Path; use sdl2::{ event::Event, keyboard::Keycode, - pixels::Color, rect::Rect, render::{Canvas, TextureQuery}, video::Window, diff --git a/game/src/game/game_player/chart_player.rs b/game/src/game/game_player/chart_player.rs index 344145c..bf335c1 100644 --- a/game/src/game/game_player/chart_player.rs +++ b/game/src/game/game_player/chart_player.rs @@ -2,6 +2,7 @@ use bidrum_data_struct_lib::{ janggu::JangguFace, song::{GameChart, GameNote}, }; +use num_rational::Rational64; use sdl2::{render::Canvas, video::Window}; use crate::constants::{ACCURACY_DISPLAY_DURATION, DEFAULT_BPM}; @@ -11,7 +12,9 @@ use crate::game::game_player::{ }; use super::{ - chart_player_ui::{disappearing_note_effect::DisapearingNoteEffect, ChartPlayerUI}, + chart_player_ui::{ + disappearing_note_effect::DisapearingNoteEffect, BeatGuideline, ChartPlayerUI, + }, game_result::GameResult, janggu_state_with_tick::JangguStateWithTick, timing_judge::TimingJudge, @@ -152,6 +155,45 @@ impl ChartPlayer<'_> { display_notes } + fn beat_guideline(&self, tick: i128) -> Option { + if tick < 0 { + return None; + } + + // bpm = beat / minute + // minute-per-beat = 1 / bpm + // timing-in-minute = beat * minute-per-beat + // timing-in-millisecond = timing-in-minute (minute) * ( 60000(millisecond) / 1(minute) ) + // timing = timing-in-millisecond + let timing_of_one_beat = Rational64::new(60000, self.chart.bpm as i64); + + // beat_per_millisecond = (display_bpm / 60000) + // millisecond_per_beat = 1/ beat_per_millisecond + // speed = 1 / millisecond_per_beat + let speed_ratio = Rational64::new((self.chart.bpm * DEFAULT_BPM) as i64, 60000); + + // convert the ratio into floating value + let speed = *speed_ratio.numer() as f64 / *speed_ratio.denom() as f64; + + let length = + (*timing_of_one_beat.numer() as f64 / *timing_of_one_beat.denom() as f64) * speed; + + let position = { + let timing_of_one_beat = + *timing_of_one_beat.numer() as f64 / *timing_of_one_beat.denom() as f64; + let mut position = tick % timing_of_one_beat as i128; + while position < 0 { + position += timing_of_one_beat as i128; + } + + position = length as i128 - position; + length as f64 * (position as f64 / timing_of_one_beat) + }; + + println!("beat_guideline: len = {}, pos = {}", length, position); + Some(BeatGuideline { length, position }) + } + pub fn draw( &mut self, tick: i128, @@ -180,6 +222,7 @@ impl ChartPlayer<'_> { self.ui.disappearing_note_effects.update_base_tick(tick); self.ui.input_effect.update(janggu_state_with_tick, tick); self.ui.notes = self.get_display_notes(tick as u64); + self.ui.beat_guideline = self.beat_guideline(tick); } self.ui.overall_effect_tick = overall_tick; self.ui.draw(canvas); diff --git a/game/src/game/game_player/chart_player_ui.rs b/game/src/game/game_player/chart_player_ui.rs index 33417bc..dce5264 100644 --- a/game/src/game/game_player/chart_player_ui.rs +++ b/game/src/game/game_player/chart_player_ui.rs @@ -29,6 +29,11 @@ use self::{ use super::timing_judge::NoteAccuracy; +pub struct BeatGuideline { + pub position: f64, + pub length: f64, +} + pub struct ChartPlayerUI<'a> { pub notes: Vec, pub accuracy: Option, @@ -37,6 +42,7 @@ pub struct ChartPlayerUI<'a> { pub input_effect: InputEffect, pub overall_effect_tick: u128, pub disappearing_note_effects: DisapearingNoteEffect, + pub beat_guideline: Option, resources: ChartPlayerUIResources<'a>, } @@ -56,6 +62,7 @@ impl ChartPlayerUI<'_> { input_effect: InputEffect::new(), overall_effect_tick: 0, disappearing_note_effects: DisapearingNoteEffect::new(), + beat_guideline: None, resources: resources, }; } @@ -140,10 +147,11 @@ impl ChartPlayerUI<'_> { let background_width = (viewport.width() - janggu_width_min) / 2; let background_y = (canvas.viewport().height() as i32 - (background_height_without_border as i32)) / 2; - for background_x in [ + let background_x = [ 0, /* x coordinate of left background */ background_width as i32 + janggu_width_min as i32, /* x coordinate of right background */ - ] { + ]; + for background_x in background_x { let background_alpha = { // is the face hitted? let hitting = if background_x == 0 { @@ -203,7 +211,7 @@ impl ChartPlayerUI<'_> { .unwrap(); } - // draw judgement line + // judgement line info / max note width is required for beat guideline let judgement_line_height = max_stick_note_height; let judgement_line_padding_px = 20; let judgement_line_width = ((judgement_line_texture.query().width as f32 @@ -214,6 +222,46 @@ impl ChartPlayerUI<'_> { background_width as i32 - judgement_line_width as i32 - judgement_line_padding_px, /* left judgement line */ background_width as i32 + janggu_width_min as i32 + judgement_line_padding_px, /* right judgement line */ ]; + let note_width_max = std::cmp::max(left_stick_note_width, right_stick_note_width); + + // draw beat guideline + if let Some(beat_guideline) = &self.beat_guideline { + let mut position = beat_guideline.position % beat_guideline.length; + while position < 0.0 { + position += beat_guideline.length; + } + let thickness: u32 = 2; + loop { + let distance_between_centers = (position * note_width_max as f64) as i32; + if distance_between_centers + > (background_width + (judgement_line_width + thickness) / 2) as i32 + { + break; + } + for line_x in [ + judgement_line_xposes[0] - distance_between_centers + + (judgement_line_width / 2 + thickness / 2) as i32, + judgement_line_xposes[1] + + distance_between_centers + + (judgement_line_width / 2 - thickness / 2) as i32, + ] { + canvas.set_draw_color(Color::RGBA(200, 200, 200, 200)); + canvas + .fill_rect(Rect::new( + line_x, + background_y, + thickness, + background_height_without_border, + )) + .unwrap(); + println!("Drawing at {}", line_x) + } + + position += beat_guideline.length; + } + } + + // draw judgement line for judgement_line_xpos in judgement_line_xposes { canvas .copy( @@ -229,9 +277,6 @@ impl ChartPlayerUI<'_> { .unwrap(); } - // load textures for the notes and accuracy - let note_width_max = std::cmp::max(left_stick_note_width, right_stick_note_width); - // draw note let mut draw_note = |i: &DisplayedSongNote, disappearing_effect: Option| { let note_texture = match i.stick { From 3ebbd077448cba2c4cbdaff8e2658143569ddf4c Mon Sep 17 00:00:00 2001 From: LiteHell Date: Fri, 7 Jun 2024 16:36:15 +0900 Subject: [PATCH 09/17] fix: inaccurate beat guideline position bug - fix a bug that beat guideline position gets less accurate as time passes --- game/src/game/game_player/chart_player.rs | 26 +++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/game/src/game/game_player/chart_player.rs b/game/src/game/game_player/chart_player.rs index bf335c1..33b0ac8 100644 --- a/game/src/game/game_player/chart_player.rs +++ b/game/src/game/game_player/chart_player.rs @@ -172,22 +172,20 @@ impl ChartPlayer<'_> { // speed = 1 / millisecond_per_beat let speed_ratio = Rational64::new((self.chart.bpm * DEFAULT_BPM) as i64, 60000); - // convert the ratio into floating value - let speed = *speed_ratio.numer() as f64 / *speed_ratio.denom() as f64; - - let length = - (*timing_of_one_beat.numer() as f64 / *timing_of_one_beat.denom() as f64) * speed; + let length_ratio = timing_of_one_beat * speed_ratio; + let length = *length_ratio.numer() as f64 / *length_ratio.denom() as f64; let position = { - let timing_of_one_beat = - *timing_of_one_beat.numer() as f64 / *timing_of_one_beat.denom() as f64; - let mut position = tick % timing_of_one_beat as i128; - while position < 0 { - position += timing_of_one_beat as i128; - } - - position = length as i128 - position; - length as f64 * (position as f64 / timing_of_one_beat) + //position_ratio = tick % timing_of_one_beat as i128; + let mut position_ratio = Rational64::new( + (*timing_of_one_beat.denom() * tick as i64) % *timing_of_one_beat.numer(), + *timing_of_one_beat.denom(), + ); + + position_ratio = length_ratio - position_ratio; + position_ratio = position_ratio / timing_of_one_beat; + position_ratio *= length_ratio; + *position_ratio.numer() as f64 / *position_ratio.denom() as f64 }; println!("beat_guideline: len = {}, pos = {}", length, position); From 4e61d5b729a08889f4ed36738b84a41c7975ae97 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Fri, 7 Jun 2024 16:36:27 +0900 Subject: [PATCH 10/17] fix: delete debugging logs --- game/src/game/game_player/chart_player.rs | 1 - game/src/game/game_player/chart_player_ui.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/game/src/game/game_player/chart_player.rs b/game/src/game/game_player/chart_player.rs index 33b0ac8..0a1c260 100644 --- a/game/src/game/game_player/chart_player.rs +++ b/game/src/game/game_player/chart_player.rs @@ -188,7 +188,6 @@ impl ChartPlayer<'_> { *position_ratio.numer() as f64 / *position_ratio.denom() as f64 }; - println!("beat_guideline: len = {}, pos = {}", length, position); Some(BeatGuideline { length, position }) } diff --git a/game/src/game/game_player/chart_player_ui.rs b/game/src/game/game_player/chart_player_ui.rs index dce5264..f4aec20 100644 --- a/game/src/game/game_player/chart_player_ui.rs +++ b/game/src/game/game_player/chart_player_ui.rs @@ -254,7 +254,6 @@ impl ChartPlayerUI<'_> { background_height_without_border, )) .unwrap(); - println!("Drawing at {}", line_x) } position += beat_guideline.length; From cd31b9106ea94cc832398b0cbfb5db543b5eae45 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Fri, 7 Jun 2024 16:40:06 +0900 Subject: [PATCH 11/17] chore(beat-guideline): change color --- game/src/game/game_player/chart_player_ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/src/game/game_player/chart_player_ui.rs b/game/src/game/game_player/chart_player_ui.rs index f4aec20..ac3571b 100644 --- a/game/src/game/game_player/chart_player_ui.rs +++ b/game/src/game/game_player/chart_player_ui.rs @@ -245,7 +245,7 @@ impl ChartPlayerUI<'_> { + distance_between_centers + (judgement_line_width / 2 - thickness / 2) as i32, ] { - canvas.set_draw_color(Color::RGBA(200, 200, 200, 200)); + canvas.set_draw_color(Color::RGBA(255, 255, 255, 220)); canvas .fill_rect(Rect::new( line_x, From 7ebac7526b19539f34ee784769444ebb80b03d30 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Sun, 9 Jun 2024 15:51:20 +0900 Subject: [PATCH 12/17] feat(beat_guideline): adjust color and use two colors for guideline --- game/src/game/game_player/chart_player.rs | 9 ++++++++- game/src/game/game_player/chart_player_ui.rs | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/game/src/game/game_player/chart_player.rs b/game/src/game/game_player/chart_player.rs index 0a1c260..86fdd13 100644 --- a/game/src/game/game_player/chart_player.rs +++ b/game/src/game/game_player/chart_player.rs @@ -175,6 +175,9 @@ impl ChartPlayer<'_> { let length_ratio = timing_of_one_beat * speed_ratio; let length = *length_ratio.numer() as f64 / *length_ratio.denom() as f64; + let even_beat = + ((*timing_of_one_beat.denom() * tick as i64) / *timing_of_one_beat.numer()) % 2 == 0; + let position = { //position_ratio = tick % timing_of_one_beat as i128; let mut position_ratio = Rational64::new( @@ -188,7 +191,11 @@ impl ChartPlayer<'_> { *position_ratio.numer() as f64 / *position_ratio.denom() as f64 }; - Some(BeatGuideline { length, position }) + Some(BeatGuideline { + length, + position, + even_beat, + }) } pub fn draw( diff --git a/game/src/game/game_player/chart_player_ui.rs b/game/src/game/game_player/chart_player_ui.rs index ac3571b..bbf9a3b 100644 --- a/game/src/game/game_player/chart_player_ui.rs +++ b/game/src/game/game_player/chart_player_ui.rs @@ -32,6 +32,7 @@ use super::timing_judge::NoteAccuracy; pub struct BeatGuideline { pub position: f64, pub length: f64, + pub even_beat: bool, } pub struct ChartPlayerUI<'a> { @@ -230,9 +231,17 @@ impl ChartPlayerUI<'_> { while position < 0.0 { position += beat_guideline.length; } - let thickness: u32 = 2; + let thicknesses: [u32; 2] = [4, 4]; + let mut white = beat_guideline.even_beat; loop { let distance_between_centers = (position * note_width_max as f64) as i32; + let thickness = thicknesses[if white { 0 } else { 1 }]; + let color = if white { + Color::RGBA(255, 255, 255, 60) + } else { + Color::RGBA(255, 255, 255, 30) + }; + if distance_between_centers > (background_width + (judgement_line_width + thickness) / 2) as i32 { @@ -245,7 +254,7 @@ impl ChartPlayerUI<'_> { + distance_between_centers + (judgement_line_width / 2 - thickness / 2) as i32, ] { - canvas.set_draw_color(Color::RGBA(255, 255, 255, 220)); + canvas.set_draw_color(color); canvas .fill_rect(Rect::new( line_x, @@ -257,6 +266,7 @@ impl ChartPlayerUI<'_> { } position += beat_guideline.length; + white = !white; } } From df71cc752751a39f72c37d3507d0d0dd3212e702 Mon Sep 17 00:00:00 2001 From: Habin Song Date: Mon, 10 Jun 2024 21:35:50 +0900 Subject: [PATCH 13/17] feat: change volume ch im sae 0, hit sound up --- game/src/game/game_player/effect_sound_player.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/game/src/game/game_player/effect_sound_player.rs b/game/src/game/game_player/effect_sound_player.rs index 27d6708..fbd0fa4 100644 --- a/game/src/game/game_player/effect_sound_player.rs +++ b/game/src/game/game_player/effect_sound_player.rs @@ -127,7 +127,11 @@ impl EffectSoundPlayer { }; if play_sound { let new_handle = audio_manager - .play(kung_sound_data.clone()) + .play( + kung_sound_data + .clone() + .with_settings(StaticSoundSettings::default().volume(5.0)), + ) .expect("Failed to play kung sound"); self.effect_sound_play_handles.left_stick = Some(new_handle); } @@ -141,7 +145,11 @@ impl EffectSoundPlayer { if play_sound { let new_handle = audio_manager - .play(deok_sound_data.clone()) + .play( + deok_sound_data + .clone() + .with_settings(StaticSoundSettings::default().volume(5.0)), + ) .expect("Failed to play deok sound"); self.effect_sound_play_handles.right_stick = Some(new_handle); } @@ -156,7 +164,7 @@ impl EffectSoundPlayer { .play( combo_sound .clone() - .with_settings(StaticSoundSettings::default().volume(20.0)), + .with_settings(StaticSoundSettings::default().volume(0.0)), ) .expect("Failed to play combo sound"); self.combo_sound_played = true; From db432c03e23be9a3f8cd0f8953cf8939c2fa40e5 Mon Sep 17 00:00:00 2001 From: Habin Song Date: Mon, 10 Jun 2024 21:49:48 +0900 Subject: [PATCH 14/17] feat: change select song direction --- game/src/game/select_song.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game/src/game/select_song.rs b/game/src/game/select_song.rs index b484595..3792c5c 100644 --- a/game/src/game/select_song.rs +++ b/game/src/game/select_song.rs @@ -142,9 +142,9 @@ pub(crate) fn select_song( break 'running; } } else if (janggu_state.궁채.is_keydown_now - && matches!(janggu_state.궁채.face, Some(JangguFace::궁편))) + && matches!(janggu_state.궁채.face, Some(JangguFace::열편))) || (janggu_state.열채.is_keydown_now - && matches!(janggu_state.열채.face, Some(JangguFace::궁편))) + && matches!(janggu_state.열채.face, Some(JangguFace::열편))) { // to prevent changing direction when moving if moving_direction == MovingDirection::Stop { @@ -155,9 +155,9 @@ pub(crate) fn select_song( } } } else if (janggu_state.궁채.is_keydown_now - && matches!(janggu_state.궁채.face, Some(JangguFace::열편))) + && matches!(janggu_state.궁채.face, Some(JangguFace::궁편))) || (janggu_state.열채.is_keydown_now - && matches!(janggu_state.열채.face, Some(JangguFace::열편))) + && matches!(janggu_state.열채.face, Some(JangguFace::궁편))) { // to prevent changing direction when moving if moving_direction == MovingDirection::Stop { From 5f27e4e85ebfb1c78770593514c7fbd4f043a068 Mon Sep 17 00:00:00 2001 From: Homin Lee Date: Mon, 10 Jun 2024 23:31:22 +0900 Subject: [PATCH 15/17] fix: controller.ino fix GND noise --- controller/controller.ino | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/controller/controller.ino b/controller/controller.ino index 70580ce..a7da8fc 100644 --- a/controller/controller.ino +++ b/controller/controller.ino @@ -1,10 +1,10 @@ #define BILL_PIN 2 // 지폐기 #define COIN_PIN 3 // 코인기 -#define INPUT_PIN_2 4 // 열편 -#define OUTPUT_PIN_2 5 // 열채 -#define INPUT_PIN_1 6 // 궁편 -#define OUTPUT_PIN_1 7 // 궁채 -#define RELAY_DELAY 50 // 딜레이 50ms +#define INPUT_PIN_1 4 // 열편 +#define OUTPUT_PIN_1 5 // 열채 +#define INPUT_PIN_2 6 // 궁편 +#define OUTPUT_PIN_2 7 // 궁채 +#define RELAY_DELAY 50 // 딜레이 50μs int step; int pin1ConnectedTo, pin2ConnectedTo; @@ -58,9 +58,9 @@ void loop() digitalWrite(OUTPUT_PIN_1, LOW); digitalWrite(OUTPUT_PIN_2, HIGH); delayMicroseconds(RELAY_DELAY); - if(digitalRead(INPUT_PIN_1) == LOW) { + if(digitalRead(INPUT_PIN_1) == HIGH) { pin2ConnectedTo = INPUT_PIN_1; - } else if (digitalRead(INPUT_PIN_2) == LOW) { + } else if (digitalRead(INPUT_PIN_2) == HIGH) { pin2ConnectedTo = INPUT_PIN_2; } else { pin2ConnectedTo = 0; @@ -70,9 +70,9 @@ void loop() digitalWrite(OUTPUT_PIN_2, LOW); digitalWrite(OUTPUT_PIN_1, HIGH); delayMicroseconds(RELAY_DELAY); - if(digitalRead(INPUT_PIN_1) == LOW) { + if(digitalRead(INPUT_PIN_1) == HIGH) { pin1ConnectedTo = INPUT_PIN_1; - } else if (digitalRead(INPUT_PIN_2) == LOW) { + } else if (digitalRead(INPUT_PIN_2) == HIGH) { pin1ConnectedTo = INPUT_PIN_2; } else { pin1ConnectedTo = 0; From bad58bc2d99cc0b0cd71b180c8dfbb947c84f589 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Tue, 11 Jun 2024 00:01:38 +0900 Subject: [PATCH 16/17] fix: remove use of RwLock in controller wrapper --- game/src/controller_wrapper.rs | 64 +++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/game/src/controller_wrapper.rs b/game/src/controller_wrapper.rs index 631056a..fe12906 100644 --- a/game/src/controller_wrapper.rs +++ b/game/src/controller_wrapper.rs @@ -1,23 +1,57 @@ use std::{ sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, + atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}, Arc, RwLock, }, thread, }; use bidrum_controller_lib::{keyboard, serial, CoinInputDevice, JangguDevice}; -use bidrum_data_struct_lib::janggu::JangguInputState; +use bidrum_data_struct_lib::janggu::{JangguFace, JangguInputState}; /// Wrapper of Coin/Janggu controller /// to avoid cumbersome ownership/borrow/lifetime problems pub struct ControllerWrapper { - janggu_state: Arc>, + janggu_state: Arc, coins: Arc, coins_to_consume: Arc, stopping: Arc, } +fn janggu_state_to_u8(state: JangguInputState) -> u8 { + let mut result: u8 = 0; + result |= match state.궁채 { + Some(JangguFace::궁편) => 1, + Some(JangguFace::열편) => 2, + _ => 0, + }; + result |= match state.열채 { + Some(JangguFace::궁편) => 4, + Some(JangguFace::열편) => 8, + _ => 0, + }; + return result; +} + +fn u8_to_janggu_state(bits: u8) -> JangguInputState { + JangguInputState { + 궁채: if (bits & 1) != 0 { + Some(JangguFace::궁편) + } else if (bits & 2) != 0 { + Some(JangguFace::열편) + } else { + None + }, + 열채: if (bits & 4) != 0 { + Some(JangguFace::궁편) + } else if (bits & 8) != 0 { + Some(JangguFace::열편) + } else { + None + }, + } +} + impl Drop for ControllerWrapper { fn drop(&mut self) { self.stopping.store(true, Ordering::Relaxed); @@ -26,7 +60,7 @@ impl Drop for ControllerWrapper { impl ControllerWrapper { pub fn read_janggu_state(&self) -> JangguInputState { - *self.janggu_state.read().expect("Failed to get lock") + u8_to_janggu_state(self.janggu_state.load(Ordering::Relaxed)) } pub fn get_coins(&self) -> u32 { self.coins.load(Ordering::Relaxed) @@ -38,10 +72,7 @@ impl ControllerWrapper { let coins = Arc::new(AtomicU32::new(0)); let coins_to_consume = Arc::new(AtomicU32::new(0)); let stopping = Arc::new(AtomicBool::new(false)); - let janggu_state = Arc::new(RwLock::new(JangguInputState { - 궁채: None, - 열채: None, - })); + let janggu_state = Arc::new(AtomicU8::new(0)); { let coins = coins.clone(); let coins_to_consume = coins_to_consume.clone(); @@ -64,8 +95,10 @@ impl ControllerWrapper { } coins.store(coin_device.get_unconsumed_coins(), Ordering::Relaxed); - let mut janggu_state = janggu_state.write().expect("Failed to get lock"); - *janggu_state = janggu_device.read_janggu_input_state(); + janggu_state.store( + janggu_state_to_u8(janggu_device.read_janggu_input_state()), + Ordering::Relaxed, + ); } }); } @@ -82,10 +115,7 @@ impl ControllerWrapper { let coins = Arc::new(AtomicU32::new(0)); let coins_to_consume = Arc::new(AtomicU32::new(0)); let stopping = Arc::new(AtomicBool::new(false)); - let janggu_state = Arc::new(RwLock::new(JangguInputState { - 궁채: None, - 열채: None, - })); + let janggu_state = Arc::new(AtomicU8::new(0)); { let coins = coins.clone(); let coins_to_consume = coins_to_consume.clone(); @@ -107,8 +137,10 @@ impl ControllerWrapper { } coins.store(coin_device.get_unconsumed_coins(), Ordering::Relaxed); - let mut janggu_state = janggu_state.write().expect("Failed to get lock"); - *janggu_state = janggu_device.read_janggu_input_state(); + janggu_state.store( + janggu_state_to_u8(janggu_device.read_janggu_input_state()), + Ordering::Relaxed, + ); } }); } From 3fa4343c673cf5a7dddd277ab306f892a7dacb00 Mon Sep 17 00:00:00 2001 From: LiteHell Date: Tue, 11 Jun 2024 00:01:07 +0900 Subject: [PATCH 17/17] timing: adjust judge timings easier --- game/src/constants.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/game/src/constants.rs b/game/src/constants.rs index 312e21e..fe05604 100644 --- a/game/src/constants.rs +++ b/game/src/constants.rs @@ -13,10 +13,10 @@ pub const DEFAULT_VIDEO_PATH: &str = concatcp!(DEFAULT_ASSET_PATH, "/video"); // judgement_time pub const OVERCHAOS_TIMING: i64 = 10; -pub const PERFECT_TIMING: i64 = 44; +pub const PERFECT_TIMING: i64 = 40; pub const GREAT_TIMING: i64 = 70; -pub const GOOD_TIMING: i64 = 95; -pub const BAD_TIMING: i64 = 160; +pub const GOOD_TIMING: i64 = 200; +pub const BAD_TIMING: i64 = 600; // add combo value pub const OVERCHAOS_COMBO: u64 = 1;