Initial commit

main
kirbylife 2023-09-24 19:03:40 -06:00
commit 746212f3af
14 changed files with 4255 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
/target

16
Cargo.toml 100644
View File

@ -0,0 +1,16 @@
[package]
name = "worsdle"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4.31", default-features = false, features = ["wasmbind", "clock"] }
gloo = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.20"
rand = { version = "0.8.5", default-features = false, features = ["alloc"] }
wasm-bindgen = "0.2.87"
wasm-logger = "0.2.0"
yew = { version = "0.20.0", features = ["csr"] }

11
index.html 100644
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Yew App</title>
<link data-trunk rel="css" href="static/css/styles.css" />
</head>
<body>
<h1>Hola mundo</h1>
</body>
</html>

View File

@ -0,0 +1,18 @@
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct AttemptRowProps {
pub answer: AttrValue,
pub text: AttrValue,
}
#[function_component]
pub fn AttemptRow(props: &AttemptRowProps) -> Html {
let AttemptRowProps { answer, text } = props;
html! {
{ for answer.chars().zip(text.chars()).map(|(ans, att)| html! { <p class={
if ans == att { "correct" } else if answer.contains(att) { "almost" } else { "missed" }
} >{ att }</p> }) }
}
}

View File

@ -0,0 +1,23 @@
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct CurrentRowProps {
pub text: AttrValue,
}
#[function_component]
pub fn CurrentRow(props: &CurrentRowProps) -> Html {
let attempt = {
let mut ret = [' '; 5];
props
.text
.chars()
.zip(ret.iter_mut())
.for_each(|(val, ptr)| *ptr = val);
ret
};
html! {
{ for attempt.map(|c| html! { <p>{ c }</p> }) }
}
}

View File

@ -0,0 +1,8 @@
use yew::prelude::*;
#[function_component]
pub fn EmptyRow() -> Html {
html! {
{ for (0..5u8).map(|_| html! { <p></p> }) }
}
}

View File

@ -0,0 +1,9 @@
pub use crate::components::attempt_row::AttemptRow;
pub use crate::components::current_row::CurrentRow;
pub use crate::components::empty_row::EmptyRow;
pub use crate::components::result_board::ResultBoard;
mod attempt_row;
mod current_row;
mod empty_row;
mod result_board;

View File

@ -0,0 +1,35 @@
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct ResultBoardProps {
pub answer: AttrValue,
pub attempts: Vec<String>,
}
#[function_component]
pub fn ResultBoard(props: &ResultBoardProps) -> Html {
let ResultBoardProps { answer, attempts } = props;
let answer_chars = answer.chars();
let mut result = String::default();
html! {
<div class="result-board"> {
for attempts.iter().map(|attempt| {
html! {
<> {
for answer.chars().zip(attempt.chars()).map(| (ans, att)| {
if ans == att {
html! { <span> { "🟩" } </span> }
} else if answer.contains(att) {
html! { <span>{ "🟨" }</span> }
} else {
html! { <span>{ "" }</span> }
}
})
} </>
}
})
} </div>
}
}

27
src/consts.rs 100644
View File

@ -0,0 +1,27 @@
pub const MAX_ATTEMPTS: usize = 6;
pub enum Key {
Enter,
// 13
Backspace,
// 8
CharKey(char),
Ignored,
}
impl From<u32> for Key {
fn from(value: u32) -> Self {
match value {
8 => Key::Backspace,
13 => Key::Enter,
65..=90 => Key::CharKey(value as u8 as char),
59 => Key::CharKey(209u8 as char),
_ => Key::Ignored,
}
}
}
pub enum Result {
Win,
Fail,
}

137
src/main.rs 100644
View File

@ -0,0 +1,137 @@
use yew::prelude::*;
use crate::components::AttemptRow;
use crate::components::CurrentRow;
use crate::components::EmptyRow;
use crate::components::ResultBoard;
use crate::consts::Key;
use crate::consts::Result;
use crate::consts::MAX_ATTEMPTS;
use crate::services::words::{get_all_words, get_word_of_the_day};
use chrono::Datelike;
use gloo::console;
mod components;
mod consts;
mod services;
#[function_component]
fn App() -> Html {
let answer = use_state(|| get_word_of_the_day());
let all_the_words = use_memo(|_| get_all_words(), 0);
let attempts: UseStateHandle<Vec<String>> = use_state(|| vec![String::default(); MAX_ATTEMPTS]);
let attempt_index = use_state(|| 0usize);
let current_attempt = use_state(|| String::new());
let result = use_state(|| None::<Result>);
let onkeypress = {
console::log!(chrono::Utc::now().year());
let current = current_attempt.clone();
let answer = answer.clone();
let answer_str = answer.clone().to_string();
let current_str = current.clone().to_string();
let attempts = attempts.clone();
let result = result.clone();
let index = attempt_index.clone();
move |event: KeyboardEvent| {
console::log!(&event);
if result.is_some() {
return {};
}
let key_code = event.key_code();
match Key::from(key_code) {
Key::CharKey(c) => {
if current.chars().count() >= 5 {
return ();
}
let mut new_attempt = current_str.clone();
new_attempt.push(c);
console::log!(&new_attempt);
current.set(new_attempt);
}
Key::Backspace => {
if current.is_empty() {
return ();
}
let mut new_attempt = current_str.clone();
// This unwrap is safe because the potential failura it's already covered
// by the upper if
new_attempt.pop().unwrap();
console::log!(&new_attempt);
current.set(new_attempt);
}
Key::Enter => {
if current.chars().count() != 5 {
return;
}
if !all_the_words.contains(&current) {
return;
}
let mut new_attempts = attempts.clone().to_vec();
new_attempts[*index] = current_str.clone();
attempts.set(new_attempts);
current.set(String::default());
index.set(*index + 1);
if &current_str == &answer_str {
result.set(Some(Result::Win));
} else if *index == MAX_ATTEMPTS {
result.set(Some(Result::Fail));
} else {
}
}
_ => {}
}
}
};
html! {
<>
<h1>{ "Worsdle" }</h1>
<div class="board" onkeyup={onkeypress} tabindex="0">
{
for attempts.clone().iter().enumerate().map(|(i, attempt)| {
let current = current_attempt.clone().to_string();
let attempt = attempt.clone();
if i < *attempt_index {
let answer = answer.clone().to_string();
html! { <AttemptRow text={attempt} answer={answer} /> }
} else if i == *attempt_index {
html! { <CurrentRow text={ current } /> }
} else {
html! { <EmptyRow /> }
}
})
}
</div>
<div class={ format!("result {}", if result.is_some() { "" } else { "hidden" }) } >
<span> {
match *result {
Some(Result::Win) => "Ganaste!!",
Some(Result::Fail) => "Perdiste :c",
_ => "No deberías ver esto"
}
} </span>
< ResultBoard answer={ answer.to_string().clone() } attempts={(*attempts).clone()} />
<p> { "Vuelve mañana para otro desafío :)" } </p>
</div>
</>
}
}
fn main() {
wasm_logger::init(wasm_logger::Config::default());
yew::Renderer::<App>::new().render();
}

View File

@ -0,0 +1 @@
pub mod words;

View File

@ -0,0 +1,23 @@
use chrono::Datelike;
use lazy_static::lazy_static;
lazy_static! {
pub static ref WORDS: Vec<String> = include_str!("../../words.txt")
.lines()
.map(|w| w.to_string())
.collect();
}
pub fn get_all_words() -> &'static WORDS {
&WORDS
}
pub fn get_word_of_the_day() -> String {
let date = chrono::Utc::now();
let year = date.year() as usize;
let month = date.month() as usize;
let day = date.day() as usize;
let index = year + month + day;
WORDS[index % WORDS.len()].clone()
}

View File

@ -0,0 +1,84 @@
body > * {
font-family: sans-serif;
}
body {
min-height: 99dvh;
margin-left: 150px;
margin-right: 150px;
display: flex;
align-items: center;
flex-direction: column;
}
.board {
width: 25%;
height: 25%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
justify-items: center;
justify-self: auto;
gap: 10px;
padding-bottom: 10px;
}
.board > p {
display: flex;
width: 70px;
height: 70px;
border: 3px solid #444444;
border-radius: 10px;
justify-content: center;
align-items: center;
margin: 0;
font-size: 40px;
}
.correct {
border-color: #BDD9BF !important;
background-color: #BDD9BF90;
color: black;
}
.almost {
border-color: #FFC857 !important;
background-color: #FFC85790;
color: black;
}
.missed {
border-color: #444444 !important;
background-color: #44444490;
color: white;
}
.result {
display: flex;
flex-direction: column;
align-items: center;
}
.result > span {
font-size: 20px;
}
.hidden {
visibility: hidden;
}
.result-board {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
width: fit-content;
gap: 2px;
}
.result-board > span {
margin: 0;
font-size: 20px;
height: 20px;
width: 20px;
}

3862
words.txt 100644

File diff suppressed because it is too large Load Diff