Skip to content

Commit

Permalink
add opponent listing
Browse files Browse the repository at this point in the history
  • Loading branch information
randallard committed Nov 30, 2024
1 parent f7677fd commit ca36b0f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod opponent;
pub mod board;
pub mod saved_boards;
pub mod utils;
62 changes: 62 additions & 0 deletions src/components/opponent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use serde::{Serialize, Deserialize};
use web_sys::window;

#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub enum OpponentType {
Human,
Computer,
}

#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Opponent {
pub name: String,
pub id: String,
pub opponent_type: OpponentType,
}

impl Opponent {
pub fn new(name: String, opponent_type: OpponentType) -> Self {
Opponent {
id: format!("{}_{}",
match opponent_type {
OpponentType::Human => "human",
OpponentType::Computer => "cpu",
},
name.to_lowercase().replace(" ", "_")
),
name,
opponent_type,
}
}
}

pub fn delete_opponent(id: &str) -> Result<(), serde_json::Error> {
let storage = window().unwrap().local_storage().unwrap().unwrap();
let mut saved_opponents = load_opponents().unwrap_or_default();
saved_opponents.retain(|o| o.id != id);
let json = serde_json::to_string(&saved_opponents)?;
storage.set_item("saved_opponents", &json).unwrap();
Ok(())
}

pub fn save_opponent(opponent: Opponent) -> Result<Vec<Opponent>, serde_json::Error> {
let storage = window().unwrap().local_storage().unwrap().unwrap();

// Load existing opponents first
let mut saved_opponents = load_opponents().unwrap_or_default();

// Only add if not already present
if !saved_opponents.iter().any(|o| o.id == opponent.id) {
saved_opponents.push(opponent);
let json = serde_json::to_string(&saved_opponents)?;
storage.set_item("saved_opponents", &json).unwrap();
}

Ok(saved_opponents)
}

pub fn load_opponents() -> Option<Vec<Opponent>> {
let storage = window().unwrap().local_storage().unwrap().unwrap();
let data = storage.get_item("saved_opponents").ok()??;
serde_json::from_str(&data).ok()
}
80 changes: 77 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use serde::{Serialize, Deserialize};
mod components;
use components::board::BoardCreator;
use components::saved_boards::SavedBoards;
use components::opponent::{
delete_opponent, Opponent, OpponentType, load_opponents, save_opponent
};

#[derive(Serialize, Deserialize)]
struct UserData {
Expand Down Expand Up @@ -41,6 +44,12 @@ fn App() -> impl IntoView {
let (greeting, set_greeting) = signal(String::new());
let (show_form, set_show_form) = signal(true);
let (show_board_creator, set_show_board_creator) = signal(false);
let opponent_to_delete = RwSignal::new(None::<Opponent>);
let opponents_trigger = RwSignal::new(false);
let opponents = Memo::new(move |_| {
opponents_trigger.get();
load_opponents().unwrap_or_default()
});

if let Some(data) = load_user_data() {
set_name.set(data.name);
Expand Down Expand Up @@ -96,10 +105,75 @@ fn App() -> impl IntoView {
})}
{move || (!show_form.get()).then(|| view! {
<div class="grid grid-cols-2 gap-8 w-full max-w-4xl px-4">
<div>
<h2 class="text-2xl font-bold mb-4">"Friends"</h2>
<a href="#" class="text-blue-400 hover:text-blue-300 block mb-2">"+ Invite a Friend"</a>
<div>
<h2 class="text-2xl font-bold mb-4">"Opponents"</h2>
<div class="flex flex-col gap-2">
<button
class="text-blue-400 hover:text-blue-300 text-left"
on:click=move |_| {
let opponent = Opponent::new("Random CPU".to_string(), OpponentType::Computer);
let _ = save_opponent(opponent);
opponents_trigger.update(|v| *v = !*v);
}
>
"+ Add CPU Opponent"
</button>
<For
each=move || opponents.get()
key=|opponent| opponent.id.clone()
children=move |opponent: Opponent| {
view! {
<div class="flex items-center justify-between p-2 bg-slate-800 rounded">
<div class="flex items-center gap-2 text-gray-300">
<span class="w-4 h-4 rounded-full bg-blue-600 flex items-center justify-center text-xs">
{if matches!(opponent.opponent_type, OpponentType::Computer) { "C" } else { "H" }}
</span>
{opponent.name.clone()}
</div>
<button
class="text-red-400 hover:text-red-300 opacity-50 hover:opacity-100 transition-opacity"
on:click=move |_| opponent_to_delete.set(Some(opponent.clone()))
>
"Remove"
</button>
</div>
}
}
/>
</div>

// Confirmation Dialog
{move || opponent_to_delete.get().map(|opponent| view! {
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="bg-slate-800 p-6 rounded-lg shadow-xl max-w-md w-full mx-4">
<h3 class="text-xl font-bold mb-4">"Confirm Removal"</h3>
<p class="text-gray-300 mb-6">
"Are you sure you want to remove "
<span class="font-semibold">{opponent.name.clone()}</span>
" from your opponents list?"
</p>
<div class="flex justify-end gap-4">
<button
class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded"
on:click=move |_| opponent_to_delete.set(None)
>
"Cancel"
</button>
<button
class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded"
on:click=move |_| {
let _ = delete_opponent(&opponent.id);
opponents_trigger.update(|v| *v = !*v);
opponent_to_delete.set(None);
}
>
"Remove"
</button>
</div>
</div>
</div>
})}
</div>
<div>
<h2 class="text-2xl font-bold mb-4">"Boards"</h2>
<a
Expand Down

0 comments on commit ca36b0f

Please sign in to comment.