Skip to content

Commit

Permalink
placement is numbered in thumbnails
Browse files Browse the repository at this point in the history
  • Loading branch information
randallard committed Nov 30, 2024
1 parent 3674cab commit ffb4262
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 41 deletions.
68 changes: 61 additions & 7 deletions src/components/board.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,68 @@
use std::time::Duration;

use leptos::*;
use leptos::prelude::*;
use serde::{Serialize, Deserialize};

use crate::components::utils::generate_thumbnail;
use crate::components::utils::{generate_thumbnail, save_board};


#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub enum CellContent {
Empty,
Player,
Trap,
}

#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Board {
pub grid: Vec<Vec<CellContent>>,
pub size: usize,
pub moves: Vec<(usize,usize)>
}

impl Board {
pub fn new(size: usize) -> Self {
Board {
grid: vec![vec![CellContent::Empty; size]; size],
size,
moves: Vec::new(),
}
}
}

#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct SavedBoard {
pub board: Board,
pub thumbnail: String,
}

fn reset_board(
board: &RwSignal<Board>,
current_turn: &RwSignal<usize>,
finished: &RwSignal<bool>
) {
board.set(Board::new(2));
current_turn.set(0);
finished.set(false);
}

fn has_valid_moves(board: &Board) -> bool {
if let Some((player_row, player_col)) = find_player(board) {
if player_row == 0 {
return true;
}
for i in 0..board.size {
for j in 0..board.size {
if matches!(board.grid[i][j], CellContent::Empty)
&& is_adjacent(player_row, player_col, i, j)
&& i <= player_row {
return true;
}
}
}
}
false
}

#[component]
Expand All @@ -37,20 +76,25 @@ pub fn BoardCreator(
let handle_cell_click = move |row: usize, col: usize| {
let mut current_board = board.get();
if current_turn.get() == 0 && row == current_board.size - 1 {
// First turn - only allow placing player in bottom row
current_board.grid[row][col] = CellContent::Player;
current_board.moves.push((row,col));
board.set(current_board);
current_turn.set(1);
} else if !finished.get() {
// Subsequent turns - allow placing trap or moving player
let player_pos = find_player(&current_board);
if let Some((player_row, player_col)) = player_pos {
if player_row == 0 || is_adjacent(player_row, player_col, row, col) {
if row == usize::MAX { // Special case for final move
finished.set(true);
let current_board = board.get();
let _ = save_board(current_board);
set_timeout(move || {
reset_board(&board, &current_turn, &finished);
}, Duration::from_millis(333));
} else {
current_board.grid[player_row][player_col] = CellContent::Empty;
current_board.grid[row][col] = CellContent::Player;
current_board.moves.push((row,col));
board.set(current_board);
current_turn.update(|t| *t += 1);
}
Expand All @@ -66,8 +110,18 @@ pub fn BoardCreator(
<div class="flex flex-col gap-4">
{move || {
let player_pos = find_player(&board.get());
if let Some((row, _)) = player_pos {
if row == 0 {
if let Some((row, _col)) = player_pos {
let has_valid_moves = has_valid_moves(&board.get());
if !has_valid_moves {
view! {
<button
class="w-full h-8 bg-red-600 hover:bg-red-700 rounded mb-2"
on:click=move |_| reset_board(&board, &current_turn, &finished)
>
"You're trapped! Reset and try again"
</button>
}.into_any()
} else if row == 0 {
view! {
<button
class="w-full h-8 bg-green-600 hover:bg-green-700 rounded mb-2"
Expand Down
37 changes: 36 additions & 1 deletion src/components/saved_boards.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
use leptos::*;
use leptos::prelude::*;

use crate::components::utils::{delete_board,generate_thumbnail,load_saved_boards};
use crate::components::board::SavedBoard;

// In saved_boards.rs, modify the component:
#[component]
pub fn SavedBoards() -> impl IntoView {
let boards = RwSignal::new(load_saved_boards().unwrap_or_default());

let delete = move |index: usize| {
let _ = delete_board(index);
boards.set(load_saved_boards().unwrap_or_default());
};

view! {
<div class="grid grid-cols-4 gap-4 mt-4">
// Thumbnails will go here
<For
each=move || boards.get()
key=|board| generate_thumbnail(&board.board)
children=move |board: SavedBoard| {
view! {
<div class="relative">
<img
src=board.thumbnail.clone()
alt="Saved board"
class="w-24 h-24 rounded border border-slate-700"
/>
<button
class="absolute -top-2 -right-2 bg-red-600 hover:bg-red-700 rounded-full w-6 h-6 flex items-center justify-center"
on:click=move |_| {
if let Some(index) = boards.get().iter().position(|b| b.thumbnail == board.thumbnail) {
delete(index)
}
}
>
"×"
</button>
</div>
}
}
/>
</div>
}
}
108 changes: 75 additions & 33 deletions src/components/utils.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,79 @@
use super::board::{Board, CellContent};
use web_sys::window;

use std::fmt::Write;
use super::board::{Board, CellContent, SavedBoard};

pub fn generate_thumbnail(board: &Board) -> String {
let svg = format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
let mut svg = String::from(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" fill="rgb(30, 41, 59)"/>
<g transform="translate(5,5)">{}</g>
</svg>"#,
board.grid.iter().enumerate().map(|(i, row)| {
row.iter().enumerate().map(|(j, cell)| {
let x = j as f32 * 45.0;
let y = i as f32 * 45.0;
match cell {
CellContent::Empty => format!(
r#"<rect x="{}" y="{}" width="40" height="40" fill="rgb(51, 65, 85)"/>"#,
x, y
),
CellContent::Player => format!(
r#"<rect x="{}" y="{}" width="40" height="40" fill="rgb(51, 65, 85)"/>
<circle cx="{:.0}" cy="{:.0}" r="15" fill="rgb(37, 99, 235)"/>"#,
x, y, x + 20.0, y + 20.0
),
CellContent::Trap => format!(
r#"<rect x="{}" y="{}" width="40" height="40" fill="rgb(51, 65, 85)"/>
<path d="M{} {} l30 30 m0 -30 l-30 30" stroke="rgb(220, 38, 38)" stroke-width="4"/>"#,
x, y, x + 5.0, y + 5.0
),
}
}).collect::<String>()
}).collect::<String>()
);

format!(
r#"data:image/svg+xml,{}"#,
urlencoding::encode(&svg)
)
<g transform="translate(5,5)">"#);

// Draw grid and traps
for (i, row) in board.grid.iter().enumerate() {
for (j, cell) in row.iter().enumerate() {
let x = j as f32 * 45.0;
let y = i as f32 * 45.0;
let _ = match cell {
CellContent::Empty | CellContent::Player => write!(
svg,
r#"<rect x="{}" y="{}" width="40" height="40" fill="rgb(51, 65, 85)"/>"#,
x, y
),
CellContent::Trap => write!(
svg,
r#"<rect x="{}" y="{}" width="40" height="40" fill="rgb(51, 65, 85)"/>
<path d="M{} {} l30 30 m0 -30 l-30 30" stroke="rgb(220, 38, 38)" stroke-width="4"/>"#,
x, y, x + 5.0, y + 5.0
),
};
}
}

// Draw numbered circles for moves
for (idx, &(i, j)) in board.moves.iter().enumerate() {
let x = j as f32 * 45.0;
let y = i as f32 * 45.0;
let _ = write!(
svg,
r#"<circle cx="{:.0}" cy="{:.0}" r="15" fill="rgb(37, 99, 235)"/>
<text x="{:.0}" y="{:.0}" font-size="16" fill="white" text-anchor="middle" dy=".3em">{}</text>"#,
x + 20.0, y + 20.0, x + 20.0, y + 20.0, idx + 1
);
}

svg.push_str("</g></svg>");
format!(r#"data:image/svg+xml,{}"#, urlencoding::encode(&svg))
}

pub fn save_board(board: Board) -> Result<(), serde_json::Error> {
let storage = window().unwrap().local_storage().unwrap().unwrap();
let thumbnail = generate_thumbnail(&board);
let saved_board = SavedBoard { board, thumbnail };

// Load existing boards first
let mut saved_boards = load_saved_boards().unwrap_or_default();
web_sys::console::log_1(&format!("Current boards: {}", saved_boards.len()).into());

saved_boards.push(saved_board);
web_sys::console::log_1(&format!("After adding: {}", saved_boards.len()).into());

let json = serde_json::to_string(&saved_boards)?;
storage.set_item("saved_boards", &json).unwrap();
Ok(())
}

pub fn load_saved_boards() -> Option<Vec<SavedBoard>> {
let storage = window().unwrap().local_storage().unwrap().unwrap();
let data = storage.get_item("saved_boards").ok()??;
serde_json::from_str(&data).ok()
}

// In utils.rs, add this function:
pub fn delete_board(index: usize) -> Result<(), serde_json::Error> {
let storage = window().unwrap().local_storage().unwrap().unwrap();
let mut saved_boards = load_saved_boards().unwrap_or_default();
saved_boards.remove(index);
let json = serde_json::to_string(&saved_boards)?;
storage.set_item("saved_boards", &json).unwrap();
Ok(())
}

0 comments on commit ffb4262

Please sign in to comment.