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