diff --git a/README.md b/README.md index d222b1e..7e85050 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,9 @@ We also have a pr check for clippy and fmt, so if it fails, please use ### To run in debug mode with the wiz_debug feature enabled, run `cargo run --features wiz_debug` This will allow you to start the game with 1 player. + +### build.sh usage +- Allows building release executable files for windows/linux/mac with `./build.sh -t `, also allows to build with a `wiz_debug` feature flag with `./build.sh -t -f` +- To build you would need to install targets first +- To correctly copy the assets, you would need to run the script from the root of the project +- The script copies the needed files as a release in your specified folder (in `DEST_PATH`) \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f8edeab --- /dev/null +++ b/build.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +TARGET="linux" +DEST_PATH="/mnt/c/Users/capos/Downloads" +BUILD_TYPE="release" +FEATURES="" + +while getopts "t:fh" opt; do + case $opt in + t) + TARGET="$OPTARG" + ;; + f) + FEATURES="wiz_debug $FEATURES" + ;; + h) + echo "Usage: $0 [options]" + echo "Options:" + echo " -t Build target (windows, linux, macos) [default: windows]" + echo " -f Enable debug features" + echo " -h Show help" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +case $TARGET in + windows) + TARGET_NAME="x86_64-pc-windows-gnu" + EXE_EXT=".exe" + ;; + linux) + TARGET_NAME="x86_64-unknown-linux-gnu" + EXE_EXT="" + ;; + macos) + TARGET_NAME="aarch64-apple-darwin" + EXE_EXT="" + ;; + *) + echo "Unknown target: $TARGET" + echo "Available targets: windows, linux, macos" + exit 1 + ;; +esac + +BUILD_CMD="cargo build --target $TARGET_NAME --release" +if [ -n "$FEATURES" ]; then + BUILD_CMD="$BUILD_CMD --features $FEATURES" +fi + + +$BUILD_CMD && \ + cp "target/$TARGET_NAME/$BUILD_TYPE/wizard$EXE_EXT" "$DEST_PATH/" && \ + cp assets/ "$DEST_PATH/" -r \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 48f623a..63c0436 100644 --- a/src/api.rs +++ b/src/api.rs @@ -77,7 +77,7 @@ pub enum B { RoundStarted { round: usize, cards_per_player: usize, - trump: Option, + trump: Option, }, DealerMustSetTrump { dealer: PlayerId, @@ -130,7 +130,7 @@ pub enum B { ServerShutdown, } -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)] pub struct Card { pub value: Value, pub suit: Suit, @@ -142,7 +142,7 @@ impl Card { } } -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, EnumIter)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash, EnumIter)] pub enum Suit { Red, Yellow, @@ -150,7 +150,7 @@ pub enum Suit { Blue, } -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, EnumIter)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash, EnumIter)] pub enum Value { Jester, Number(u8), // 1-13 diff --git a/src/client/mod.rs b/src/client/mod.rs index 761daaf..e7cf63d 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -7,10 +7,11 @@ use crate::client::views::Button; use crate::gameplay_ui::hand::{HandMessage, ViewableHand}; use crate::gameplay_ui::table::{TableMessage, ViewableTable}; use crate::ui_element_traits::Message; -use iced::{time, window, Size, Subscription, Task}; +use iced::{time, widget::image, window, Size, Subscription, Task}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Duration; +use strum::IntoEnumIterator; pub use update::update; pub use views::view; @@ -92,7 +93,7 @@ pub struct App { pub game_log: Vec, pub hand: Vec, pub current_trick: Vec<(PlayerId, Card)>, - pub trump: Option, + pub trump: Option, pub round_number: usize, pub is_my_turn: bool, pub is_bidding_phase: bool, @@ -127,6 +128,16 @@ pub struct App { pub btn_back_to_menu: crate::client::views::Button, pub btn_ready_owned: crate::client::views::Button, + pub btn_submit_bid: crate::client::views::Button, + + pub img_main_menu: image::Handle, + pub img_lobby_menu: image::Handle, + pub img_background: image::Handle, + pub img_ingame_background: image::Handle, + pub img_menu_container: image::Handle, + + #[allow(dead_code)] + pub card_images: HashMap, } impl Default for App { @@ -195,7 +206,49 @@ impl Default for App { btn_back_to_menu: Button::new_back_to_menu_button(15, 160, 40), btn_ready_owned: Button::new_ready_owned_button(20, 100, 36), + btn_submit_bid: Button::new_submit_bid_button(21, 110, 36), + + img_main_menu: image::Handle::from_path("assets/wizard_main_menu.png"), + img_lobby_menu: image::Handle::from_path("assets/wizard_lobby_menu.png"), + img_background: image::Handle::from_path("assets/background_forall.png"), + img_ingame_background: image::Handle::from_path("assets/ingame_background.png"), + img_menu_container: image::Handle::from_path("assets/menu_container.png"), + + card_images: Self::preload_card_images(), + } + } +} + +impl App { + fn preload_card_images() -> HashMap { + let mut map = HashMap::new(); + + map.insert( + Card::new(Suit::Red, crate::api::Value::Jester), + image::Handle::from_path("assets/cards/variations/jester.png"), + ); + map.insert( + Card::new(Suit::Red, crate::api::Value::Wizard), + image::Handle::from_path("assets/cards/variations/wizard.png"), + ); + + for suit in Suit::iter() { + for num in 1..=13 { + let card = Card::new(suit, crate::api::Value::Number(num)); + let path = crate::api::get_card_path(card); + map.insert(card, image::Handle::from_path(path)); + } } + + map + } + + #[allow(dead_code)] + pub fn get_card_image(&self, card: Card) -> image::Handle { + self.card_images.get(&card).cloned().unwrap_or_else(|| { + //just in case + image::Handle::from_path(crate::api::get_card_path(card)) + }) } } diff --git a/src/client/update.rs b/src/client/update.rs index 0689ef4..2ad72d2 100644 --- a/src/client/update.rs +++ b/src/client/update.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use super::{connect_ws, App, AppMessage, MenuState, PlayerCount}; use crate::api::{Card, Lobby, PlayerId, ServerMessage, Value, B, C, S}; use crate::client::TaskBatcher; +use crate::gameplay_ui::hand::ViewableHand; use crate::ui_element_traits::{Animated, Notifiable, Resizable}; /// Get player name from ID using lobby data @@ -304,7 +305,8 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.btn_send_chat.update_with_msg(btn_msg.clone()), state.btn_start_game.update_with_msg(btn_msg.clone()), state.btn_back_to_menu.update_with_msg(btn_msg.clone()), - state.btn_ready_owned.update_with_msg(btn_msg), + state.btn_ready_owned.update_with_msg(btn_msg.clone()), + state.btn_submit_bid.update_with_msg(btn_msg.clone()), ]); } AppMessage::AnimationTick => { @@ -323,6 +325,7 @@ pub fn update(state: &mut App, msg: AppMessage) -> Task { state.btn_start_game.update_animations(), state.btn_back_to_menu.update_animations(), state.btn_ready_owned.update_animations(), + state.btn_submit_bid.update_animations(), ]); } AppMessage::WindowResized(window_size) => { @@ -403,7 +406,15 @@ fn handle_server_message(state: &mut App, msg: ServerMessage) { } state.game_log.push(log.clone()); state.last_msg = log; - state.hand = cards; + state.hand = cards.clone(); + + // Create viewable cards with preloaded images + let viewable_cards = ViewableHand::create_viewable_cards_static( + &cards, + &state.valid_cards, + state.window_size, + ); + state.viewable_hand.set_cards(viewable_cards); } S::TrumpRequest => { let log = "[SERVER] You must set the trump suit!".to_string(); @@ -548,7 +559,10 @@ fn handle_server_message(state: &mut App, msg: ServerMessage) { println!("{}", log); state.game_log.push(log.clone()); state.last_msg = log; - state.trump = Some(suit); + state.trump = Some(Card { + suit, + value: Value::Number(1), + }); state.must_set_trump = false; } B::BiddingStarted { diff --git a/src/client/views/button.rs b/src/client/views/button.rs index 50b72a5..632f586 100644 --- a/src/client/views/button.rs +++ b/src/client/views/button.rs @@ -193,6 +193,17 @@ impl Button { ) } + pub fn new_submit_bid_button(id: usize, width: u16, height: u16) -> Self { + Self::new( + id, + "Bet", + BUTTON1_PATH, + width, + height, + AppMessage::SubmitBid, + ) + } + pub fn set_on_click(&mut self, on_click: AppMessage) { self.on_click = on_click; } diff --git a/src/client/views/gameplay.rs b/src/client/views/gameplay.rs index 7ff5162..d76c2ba 100644 --- a/src/client/views/gameplay.rs +++ b/src/client/views/gameplay.rs @@ -1,31 +1,66 @@ +use super::utils::{format_card, get_player_name}; +use crate::api::Suit; +use crate::client::{App, AppMessage}; use iced::{ + alignment, widget::{ button, column, container, row, scrollable, stack, text, text_input, Column, Image, Row, }, Border, Color, ContentFit, Element, }; -use super::utils::{format_card, get_player_name}; -use crate::api::Suit; -use crate::client::{App, AppMessage}; +// AI usage: write this function for testing purposes as a placeholder +pub fn display_trump_card<'a>(state: &'a App) -> Element<'a, AppMessage> { + if let Some(trump) = state.trump { + let img_path = state.get_card_image(trump); + container(Image::new(img_path).width(100).height(150)) + .width(iced::Length::Fill) + .center_x(iced::Length::Fill) + .align_x(alignment::Horizontal::Center) + .padding([8, 0]) + .into() + } else { + let img_path = "assets/cards/variations/back.png"; + container(Image::new(img_path).width(100).height(150)) + .width(iced::Length::Fill) + .center_x(iced::Length::Fill) + .align_x(alignment::Horizontal::Center) + .padding([8, 0]) + .into() + } +} pub fn view_gameplay<'a>(state: &'a App) -> Element<'a, AppMessage> { let scoreboard = container(view_scoreboard(state)) - .width(iced::Length::Fixed(250.0)) + .width(iced::Length::Fixed(350.0)) .height(iced::Length::Fill) - .center_y(iced::Length::Fill) - .padding(10); + .align_y(alignment::Vertical::Center) + .padding([24.0, 24.0]); - let main_content = column![ - // Add your main game content here in rows - ] - .width(iced::Length::Fill) - .height(iced::Length::Fill); + let main_content = container(Column::new()) + .width(iced::Length::Fill) + .height(iced::Length::Fill) + .align_x(alignment::Horizontal::Left) + .align_y(alignment::Vertical::Top); - let content = row![main_content, scoreboard,].height(iced::Length::Fill); + let trump_card = display_trump_card(state); + let hand_display = container(build_hand_section_dbg(state)) + .width(iced::Length::Fill) + .height(iced::Length::Shrink) + .align_x(alignment::Horizontal::Center) + .padding([24.0, 24.0]); + + let left_panel = Column::new() + .push(main_content) + .push(trump_card) + .push(hand_display) + .width(iced::Length::Fill) + .height(iced::Length::Fill); + + let content = row![left_panel, scoreboard].height(iced::Length::Fill); stack![ - Image::new("assets/ingame_background.png") + Image::new(state.img_ingame_background.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover), @@ -36,12 +71,74 @@ pub fn view_gameplay<'a>(state: &'a App) -> Element<'a, AppMessage> { .into() } +fn build_bidding_panel<'a>(state: &'a App) -> Element<'a, AppMessage> { + if !state.is_bidding_phase || !state.is_my_turn || state.must_set_trump { + return text("").into(); + } + + let max_bid = state.round_number + 1; + + let panel = column![ + text("Bid:").size(16).color(Color::from_rgb(1.0, 1.0, 1.0)), + row![ + text_input("Enter bid", &state.bid_input) + .on_input(AppMessage::BidInputChanged) + .width(80), + state.btn_submit_bid.view().padding(0), + ] + .spacing(6), + text(format!("(0 to {max_bid})")) + .size(12) + .color(Color::from_rgba(1.0, 1.0, 1.0, 0.7)), + ] + .spacing(6); + + container(panel).padding([8, 0]).into() +} + +fn build_trump_panel<'a>(state: &'a App) -> Element<'a, AppMessage> { + // Show only if dealer and must_set_trump + if !state.must_set_trump || state.dealer != state.my_id { + return text("").into(); + } + + let panel = column![ + text("Select Trump Suit:") + .size(16) + .color(Color::from_rgb(1.0, 1.0, 1.0)), + row![ + button(Image::new("assets/cards/variations/red_1.png")) + .width(80) + .height(120) + .on_press(AppMessage::SetTrump(Suit::Red)) + .padding(0), + button(Image::new("assets/cards/variations/green_1.png")) + .width(80) + .height(120) + .on_press(AppMessage::SetTrump(Suit::Green)) + .padding(0), + button(Image::new("assets/cards/variations/blue_1.png")) + .width(80) + .height(120) + .on_press(AppMessage::SetTrump(Suit::Blue)) + .padding(0), + button(Image::new("assets/cards/variations/yellow_1.png")) + .width(80) + .height(120) + .on_press(AppMessage::SetTrump(Suit::Yellow)) + .padding(0), + ] + .spacing(6), + ] + .spacing(6); + + container(panel).padding([8, 0]).into() +} // AI Usage: overwrite the view so that the scoreboard is placed correctly // and uses rows+cells instead of rows+format strings pub fn view_scoreboard<'a>(state: &'a App) -> Element<'a, AppMessage> { let mut scores_col = Column::new().spacing(2); - // Title scores_col = scores_col.push( container(text("Scoreboard").size(18).color(Color::WHITE)) .width(iced::Length::Fill) @@ -60,7 +157,6 @@ pub fn view_scoreboard<'a>(state: &'a App) -> Element<'a, AppMessage> { .padding([0, 5]), ); - // Header row scores_col = scores_col.push(scoreboard_row("Name", "Pkt", "Won", "Bid", true, false)); for player_id in &state.player_order { @@ -84,7 +180,6 @@ pub fn view_scoreboard<'a>(state: &'a App) -> Element<'a, AppMessage> { )); } - // Footer note scores_col = scores_col.push( container( text("Bids for current round") @@ -96,7 +191,16 @@ pub fn view_scoreboard<'a>(state: &'a App) -> Element<'a, AppMessage> { .padding([8, 0]), ); - // Wrap in a styled container + if state.must_set_trump && state.dealer == state.my_id { + scores_col = scores_col + .push(build_trump_panel(state)) + .align_x(iced::Center); + } else if !state.must_set_trump { + scores_col = scores_col + .push(build_bidding_panel(state)) + .align_x(iced::Center); + } + container(scores_col) .width(iced::Length::Fill) .padding(10) @@ -177,17 +281,6 @@ fn scoreboard_row( .into() } -// bid_panel_footer is no longer needed for gameplay view -#[allow(dead_code)] -fn bid_panel_footer<'a>() -> Option> { - Some( - text("Bids are shown for the current round only.") - .size(11) - .color(Color::from_rgba(1.0, 1.0, 1.0, 0.7)) - .into(), - ) -} - /// ======================================= /// EASTER EGG SECTION: DEBUG GAMEPLAY VIEW /// ======================================= @@ -353,17 +446,29 @@ fn build_hand_section_dbg<'a>(state: &'a App) -> Element<'a, AppMessage> { let mut card_row = Row::new().spacing(5); for card in &state.hand { - let card_text = format_card(card); let is_valid = state.valid_cards.is_empty() || state.valid_cards.contains(card); let can_play = state.is_my_turn && !state.is_bidding_phase && !state.must_set_trump && is_valid; + println!( + "Card: {}, Valid: {}, Can Play: {}", + format_card(card), + is_valid, + can_play + ); + let img_handle = state.get_card_image(*card).clone(); + let card_btn = if can_play { - button(text(card_text).size(11)) - .on_press(AppMessage::PlayCard(*card)) - .padding(8) + stack![ + button(text("").size(11)) + .on_press(AppMessage::PlayCard(*card)) + .padding(8) + .width(80) + .height(120), + Image::new(img_handle).width(80).height(120), + ] } else { - button(text(card_text).size(11)).padding(8) + stack![Image::new(img_handle).width(80).height(120).opacity(0.5),] }; card_row = card_row.push(card_btn); } diff --git a/src/client/views/lobby.rs b/src/client/views/lobby.rs index d2fe854..ece2427 100644 --- a/src/client/views/lobby.rs +++ b/src/client/views/lobby.rs @@ -22,7 +22,7 @@ pub fn view_lobby_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { fn view_not_connected<'a>(state: &'a App) -> Element<'a, AppMessage> { stack![ - Image::new("assets/wizard_lobby_menu.png") + Image::new(state.img_lobby_menu.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover), @@ -40,7 +40,7 @@ fn view_not_connected<'a>(state: &'a App) -> Element<'a, AppMessage> { fn view_no_lobby<'a>(state: &'a App) -> Element<'a, AppMessage> { stack![ - Image::new("assets/wizard_lobby_menu.png") + Image::new(state.img_lobby_menu.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover), @@ -86,7 +86,7 @@ fn view_lobby_content<'a>(state: &'a App, lobby: &'a Lobby) -> Element<'a, AppMe .padding(20); stack![ - Image::new("assets/wizard_lobby_menu.png") + Image::new(state.img_lobby_menu.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover), diff --git a/src/client/views/menu.rs b/src/client/views/menu.rs index 40af4f0..1fda5cf 100644 --- a/src/client/views/menu.rs +++ b/src/client/views/menu.rs @@ -31,7 +31,7 @@ pub fn view_main_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { .align_x(iced::alignment::Horizontal::Right); stack![ - Image::new("assets/wizard_main_menu.png") + Image::new(state.img_main_menu.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover), @@ -88,7 +88,7 @@ pub fn view_host_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { .align_x(iced::alignment::Horizontal::Left); stack![ - background_image("assets/background_forall.png"), + background_image(&state.img_background), menu_panel( state, "Spiel hosten:", @@ -120,7 +120,7 @@ pub fn view_join_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { .align_x(iced::alignment::Horizontal::Left); stack![ - background_image("assets/background_forall.png"), + background_image(&state.img_background), menu_panel( state, "Spiel beitreten:", @@ -188,7 +188,7 @@ pub fn view_rules_menu<'a>(state: &'a App) -> Element<'a, AppMessage> { .width(iced::Length::Fill); stack![ - background_image("assets/background_forall.png"), + background_image(&state.img_background), menu_panel( state, "SPIELREGELN:", diff --git a/src/client/views/utils.rs b/src/client/views/utils.rs index 8469556..5da51be 100644 --- a/src/client/views/utils.rs +++ b/src/client/views/utils.rs @@ -45,9 +45,10 @@ pub fn png_dimensions(path: &str) -> Option<(u32, u32)> { Some((width, height)) } -/// Creates a full-screen background with the specified image. -pub fn background_image(path: &'static str) -> Image { - Image::new(path) +pub fn background_image( + handle: &iced::widget::image::Handle, +) -> Image { + Image::new(handle.clone()) .width(iced::Length::Fill) .height(iced::Length::Fill) .content_fit(ContentFit::Cover) @@ -85,7 +86,7 @@ pub fn menu_panel<'a>( stack![ container( - Image::new("assets/menu_container.png") + Image::new(state.img_menu_container.clone()) .width(menu_w) .height(menu_h) ) diff --git a/src/gamelogic/mod.rs b/src/gamelogic/mod.rs index 0216c6a..2017654 100644 --- a/src/gamelogic/mod.rs +++ b/src/gamelogic/mod.rs @@ -20,7 +20,7 @@ pub enum GameEvent { RoundStarted { round: usize, cards_per_player: usize, - trump: Option, + trump: Option, }, RoundFinished { scores: HashMap, diff --git a/src/gamelogic/round.rs b/src/gamelogic/round.rs index 81076e9..5c59da7 100644 --- a/src/gamelogic/round.rs +++ b/src/gamelogic/round.rs @@ -25,7 +25,7 @@ pub struct Round { pub current_trick: Vec<(PlayerId, Card)>, pub dealer: PlayerId, pub current_player: PlayerId, - pub trump: Option, + pub trump: Option, pub dealer_needs_to_set_trump: bool, pub is_over: bool, pub bidding_phase: bool, @@ -68,8 +68,10 @@ impl Round { let mut dealer_needs_to_set_trump = false; if !deck.is_empty() { let trump_card = draw_random_card(&mut deck); - if trump_card.value != Value::Jester && trump_card.value != Value::Wizard { - trump = Some(trump_card.suit); + if trump_card.value != Value::Wizard { + // since out jester doesnt have a suit, we can show it to players + // to indicate that there is no trump suit this round + trump = Some(trump_card); } dealer_needs_to_set_trump = trump_card.value == Value::Wizard; } @@ -133,7 +135,10 @@ impl Round { if !self.dealer_needs_to_set_trump { return Err("Trump has already been set for this round"); } - self.trump = Some(suit); + self.trump = Some(Card { + suit, + value: Value::Number(1), + }); self.dealer_needs_to_set_trump = false; Ok(()) } @@ -252,7 +257,7 @@ impl Round { Value::Wizard => 100, // Wizard always highest }; - let is_trump = self.trump.map(|t| card.suit == t).unwrap_or(false); + let is_trump = self.trump.map(|t| card.suit == t.suit).unwrap_or(false); let is_lead = lead_suit.map(|l| card.suit == l).unwrap_or(false); // just to be sure :) diff --git a/src/gameplay_ui/hand/hand_card/mod.rs b/src/gameplay_ui/hand/hand_card/mod.rs index 4898d32..fa7d755 100644 --- a/src/gameplay_ui/hand/hand_card/mod.rs +++ b/src/gameplay_ui/hand/hand_card/mod.rs @@ -79,12 +79,12 @@ impl ReplaceUsize for CardMessage { pub struct ViewableHandCard { id: usize, card: Card, - img_path: String, window_size: Size, clickable: bool, playable: bool, show_playable_status: bool, rotation: f32, + draw_animation: DrawAnimation, hover_animation: HoverAnimation, play_animation: PlayAnimation, @@ -92,6 +92,11 @@ pub struct ViewableHandCard { false_played_animation: FalsePlayedAnimation, focus_animation: FocusAnimation, hide_animation: HideAnimation, + + img_handle: iced::widget::image::Handle, + img_frame_playable: iced::widget::image::Handle, + img_frame_playable_focused: iced::widget::image::Handle, + img_false_played: iced::widget::image::Handle, } impl ViewableHandCard { @@ -100,12 +105,12 @@ impl ViewableHandCard { let mut viewable_card: ViewableHandCard = Self { id, card, - img_path: get_card_path(card), window_size, clickable: true, playable, show_playable_status: false, rotation: 0.0, + draw_animation: DrawAnimation::new(10), hover_animation: HoverAnimation::new(5), play_animation: PlayAnimation::new(play_duration), @@ -113,6 +118,13 @@ impl ViewableHandCard { false_played_animation: FalsePlayedAnimation::new(25), focus_animation: FocusAnimation::new(70), hide_animation: HideAnimation::new(play_duration), + + img_handle: iced::widget::image::Handle::from_path(get_card_path(card)), + img_frame_playable: iced::widget::image::Handle::from_path(FRAME_PLAYABLE_PATH), + img_frame_playable_focused: iced::widget::image::Handle::from_path( + FRAME_PLAYABLE_FOCUSED_PATH, + ), + img_false_played: iced::widget::image::Handle::from_path(FALSE_PLAYED_PATH), }; viewable_card .play_animation @@ -275,7 +287,7 @@ impl Viewable for ViewableHandCard { * self.draw_animation.get_scale(); let mut card = stack!(); - let img = image(self.img_path.clone()) + let img = image(self.img_handle.clone()) .content_fit(Fill) .width(width) .height(height) @@ -283,9 +295,10 @@ impl Viewable for ViewableHandCard { .scale(scale) .opacity(img_opacity); card = card.push(img); + if self.playable { if self.show_playable_status { - let playable_effect = image(FRAME_PLAYABLE_PATH) + let playable_effect = image(self.img_frame_playable.clone()) .content_fit(Fill) .width(width) .height(height) @@ -294,7 +307,7 @@ impl Viewable for ViewableHandCard { .opacity(playable_opacity); card = card.push(playable_effect); } - let hover_effect = image(FRAME_PLAYABLE_FOCUSED_PATH) + let hover_effect = image(self.img_frame_playable_focused.clone()) .content_fit(Fill) .width(width) .height(height) @@ -303,7 +316,7 @@ impl Viewable for ViewableHandCard { .opacity(hover_effect_opacity); card = card.push(hover_effect); } else { - let false_played_effect = image(FALSE_PLAYED_PATH) + let false_played_effect = image(self.img_false_played.clone()) .content_fit(Fill) .width(width) .height(height) diff --git a/src/gameplay_ui/hand/mod.rs b/src/gameplay_ui/hand/mod.rs index 67e13df..720aa98 100644 --- a/src/gameplay_ui/hand/mod.rs +++ b/src/gameplay_ui/hand/mod.rs @@ -94,6 +94,22 @@ impl ViewableHand { } } + /// Create ViewableHandCards from game cards (static version for use in update functions) + pub fn create_viewable_cards_static( + game_cards: &[crate::api::Card], + valid_cards: &[crate::api::Card], + window_size: Size, + ) -> Vec { + game_cards + .iter() + .enumerate() + .map(|(id, &card)| { + let playable = valid_cards.contains(&card); + ViewableHandCard::new(id, card, window_size, playable) + }) + .collect() + } + pub fn card_ids(&self) -> Vec { let mut card_ids: Vec = vec![]; for (id, _) in self.cards.iter() { diff --git a/src/main.rs b/src/main.rs index cf69902..9748921 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ -// Enable only in release before the final build. // Disables the console window on Windows. -//#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +#![cfg_attr(not(feature = "wiz_debug"), windows_subsystem = "windows")] mod animation; mod api;