Initial commit
commit
746212f3af
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -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"] }
|
|
@ -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>
|
|
@ -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> }) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> }) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn EmptyRow() -> Html {
|
||||||
|
html! {
|
||||||
|
{ for (0..5u8).map(|_| html! { <p></p> }) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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(¤t) {
|
||||||
|
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 ¤t_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();
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod words;
|
|
@ -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()
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue