From 569a7ec7ab35004f751f85b912df110b8b01364a Mon Sep 17 00:00:00 2001 From: kirbylife Date: Tue, 3 Sep 2024 12:27:27 -0600 Subject: [PATCH] initial commit --- .gitignore | 3 + Cargo.toml | 14 + src/attempt.rs | 35 + src/gameslot.rs | 131 +++ src/main.rs | 182 ++++ src/status.rs | 18 + src/words.rs | 29 + templates/index.gmi.tera | 42 + words.txt | 2238 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 2692 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/attempt.rs create mode 100644 src/gameslot.rs create mode 100644 src/main.rs create mode 100644 src/status.rs create mode 100644 src/words.rs create mode 100644 templates/index.gmi.tera create mode 100644 words.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f96d6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*.pem diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..263e556 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "worsdle_gemini" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +glob = "0.3.1" +serde = { version = "1.0.208", features = ["derive"] } +tera = "1.20.0" +tokio = { version = "1.39.2", features = ["full"] } +urlencoding = "2.1.3" +uuid = { version = "1.10.0", features = ["v4", "fast-rng"] } +windmark = { version = "0.3.11", features = ["response-macros", "logger"] } diff --git a/src/attempt.rs b/src/attempt.rs new file mode 100644 index 0000000..5c1101e --- /dev/null +++ b/src/attempt.rs @@ -0,0 +1,35 @@ +use crate::status::Status; + +use serde::Serialize; + +#[derive(Serialize, Clone)] +pub struct Character { + pub char: char, + pub status: Status, +} + +#[derive(Serialize, Clone)] +pub struct Attempt { + pub chars: Vec, +} + +impl Attempt { + pub fn from_string(text: &String, answer: &String) -> Self { + let mut output = vec![]; + for (n, ch) in text.to_uppercase().chars().enumerate() { + let answer_nth = answer + .chars() + .nth(n) + .expect("Answer and text has different length"); + let status = if ch == answer_nth { + Status::Found + } else if answer.contains(ch) { + Status::Almost + } else { + Status::NotFound + }; + output.push(Character { char: ch, status }) + } + Attempt { chars: output } + } +} diff --git a/src/gameslot.rs b/src/gameslot.rs new file mode 100644 index 0000000..9ca8a0d --- /dev/null +++ b/src/gameslot.rs @@ -0,0 +1,131 @@ +use crate::attempt::Attempt; +use crate::status::Status; +use crate::words::get_word_of_the_day; +use std::collections::{HashMap, VecDeque}; +use uuid::Uuid; + +pub type Board = [Option; 6]; + +#[derive(Clone)] +pub enum InsertionStatus { + Ok, + LengthError, + GameOver, +} + +#[derive(Clone, Debug)] +pub enum GameStatus { + Playing, + Win(String), + Fail, +} + +#[derive(Clone)] +pub struct Game { + pub uuid: Uuid, + pub attempt_index: usize, + pub answer: String, + pub board: Board, + pub status: GameStatus, + pub last_insertion_status: InsertionStatus, +} + +impl Game { + pub async fn new() -> Self { + Game { + uuid: Uuid::new_v4(), + attempt_index: 0, + answer: get_word_of_the_day().await, + board: Default::default(), + status: GameStatus::Playing, + last_insertion_status: InsertionStatus::Ok, + } + } + + pub fn add_attempt(&mut self, word: &String) -> InsertionStatus { + let word = word.to_uppercase(); + + let win = word.eq(&self.answer); + + let insertion_status = match (self.attempt_index, word.chars().count(), &self.status) { + (0..=5, 5, &GameStatus::Playing) => { + let new_attempt = Attempt::from_string(&word, &self.answer); + self.board[self.attempt_index] = Some(new_attempt); + self.attempt_index += 1; + + match (win, self.attempt_index) { + (true, _) => { + self.status = GameStatus::Win(self.generate_result_pattern()); + InsertionStatus::GameOver + } + (false, 6) => { + self.status = GameStatus::Fail; + InsertionStatus::GameOver + } + (_, _) => InsertionStatus::Ok, + } + } + (0..=5, _, &GameStatus::Playing) => InsertionStatus::LengthError, + (_, _, &GameStatus::Win(_) | &GameStatus::Fail) => InsertionStatus::GameOver, + _ => todo!(), + }; + self.last_insertion_status = insertion_status.clone(); + insertion_status + } + + fn generate_result_pattern(&self) -> String { + let mut output = String::new(); + for attempt in &self.board { + match attempt { + Some(att) => { + for ch in &att.chars { + output.push(match ch.status { + Status::Found => '🟩', + Status::Almost => '🟨', + Status::NotFound => '⬛', + }); + } + output.push('\n'); + } + None => {} + } + } + output + } +} + +pub struct SlotManager { + max_length: usize, + slots: HashMap, + order: VecDeque, +} + +impl SlotManager { + pub fn new(max_length: usize) -> Self { + SlotManager { + max_length, + slots: HashMap::new(), + order: VecDeque::new(), + } + } + + pub async fn new_game(&mut self) -> &mut Game { + if self.order.len() >= self.max_length { + if let Some(oldest_id) = self.order.pop_front() { + self.slots.remove(&oldest_id); + } + } + + let new_game = Game::new().await; + let new_id = new_game.uuid; + self.slots.insert(new_id, new_game); + self.order.push_back(new_id); + + // This should be safe because the game is inserted previously + self.slots.get_mut(&new_id).unwrap() + } + + pub fn get_game(&mut self, id: Uuid) -> Option<&mut Game> { + self.slots.get_mut(&id) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..93a5990 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,182 @@ +mod attempt; +mod gameslot; +mod status; +mod words; + +use crate::attempt::Attempt; +use crate::gameslot::{Board, Game}; +use crate::words::get_word_of_the_day; +use gameslot::{GameStatus, InsertionStatus, SlotManager}; +use tera::Tera; +use tokio::sync::Mutex; +use tokio::sync::OnceCell; +use uuid::Uuid; + +static TERA: OnceCell = OnceCell::const_new(); + +async fn render>(name: S, context: &tera::Context) -> Result { + let tera = TERA + .get_or_init(|| async { + let mut tera = Tera::default(); + for path in glob::glob("templates/*").unwrap().flatten() { + let raw_path = path.clone(); + let filename = raw_path + .file_name() + .unwrap() // This should be safe (? + .to_str() + .unwrap() // ._.? + .split('.') + .next(); + tera.add_template_file(path, filename).unwrap() + } + tera + }) + .await; + tera.render(name.as_ref(), context).map_err(|_| ()) +} + +static GAMES: OnceCell> = OnceCell::const_new(); + +async fn init_games() -> Result<(), ()> { + let slot_manager = SlotManager::new(100); + GAMES.set(Mutex::new(slot_manager)).map_err(|_| ()) +} + +async fn new_game() -> Uuid { + // This should be safe becasue the initialization is in the main + let slot_manager_mutex = GAMES.get().unwrap(); + let mut slot_manager = slot_manager_mutex.lock().await; + let new_game = slot_manager.new_game().await; + new_game.uuid +} + +async fn get_game(uuid: Uuid) -> Result { + let slot_manager_mutex = GAMES.get().unwrap(); + let mut slot_manager = slot_manager_mutex.lock().await; + + match slot_manager.get_game(uuid) { + Some(game) => Ok(game.clone()), + None => Err(()), + } +} + +async fn add_attempt(uuid: Uuid, attempt: &String) -> Option { + let slot_manager_mutex = GAMES.get().unwrap(); + let mut slot_manager = slot_manager_mutex.lock().await; + match slot_manager.get_game(uuid) { + Some(game) => Some(game.add_attempt(attempt)), + None => None, + } +} + +#[windmark::main] +async fn main() -> Result<(), Box> { + init_games() + .await + .expect("Error trying to initialize the game slots"); + + windmark::router::Router::new() + .set_private_key_file("key.pem") + .set_certificate_file("cert.pem") + .set_languages(["es"]) + .set_port(1970) + .enable_default_logger(true) + .set_fix_path(true) + .mount("/", move |_| async { + let game = new_game().await; + windmark::response::Response::temporary_redirect(format!("/{}", game)) + // let mut context = tera::Context::new(); + + // let board: Board = Default::default(); + // context.insert("board", &board); + + // windmark::response::Response::success(render("index", &context).await.unwrap()) + }) + .mount("/:uuid", move |request| async move { + let mut context = tera::Context::new(); + // This should be safe because you can only get into the route with the right path + let possible_uuid = request.parameters.get("uuid").unwrap(); + + let board: Board; + let error: Option; + let result_pattern: Option; + + match Uuid::parse_str(possible_uuid) { + Ok(uuid) => match get_game(uuid).await { + Ok(game) => { + board = game.board; + match game.status { + GameStatus::Playing => { + error = match game.last_insertion_status { + InsertionStatus::Ok | InsertionStatus::GameOver => None, + InsertionStatus::LengthError => { + Some("La palabra debe de ser de 5 letras".to_string()) + } + }; + result_pattern = None; + } + GameStatus::Fail => { + error = None; + result_pattern = Some( + "Palabra no encontrada :c\nVuelve mañana para otro reto" + .to_string(), + ); + } + GameStatus::Win(pattern) => { + error = None; + result_pattern = Some(format!("Palabra encontrada en {}/6 intentos\n{}Vuelve mañana para otro reto",game.attempt_index, pattern)) + } + } + } + Err(_) => { + board = Default::default(); + error = Some( + "La partida a la que intentas acceder ya no existe o nunca existió" + .to_string(), + ); + result_pattern = None; + } + }, + Err(_) => { + board = Default::default(); + error = Some("Identificador incorrecto".to_string()); + result_pattern = None; + } + } + + context.insert("board", &board); + context.insert("error", &error); + context.insert("result_pattern", &result_pattern); + // context.insert("error", "Partida no encontrada"); + windmark::response::Response::success(render("index", &context).await.unwrap()) + }) + .mount("/:uuid/:attempt", move |request| async move { + let mut context = tera::Context::new(); + + let possible_uuid = request.parameters.get("uuid").unwrap(); + let attempt = urlencoding::decode(request.parameters.get("attempt").unwrap()).unwrap().to_string(); + + let redirect = match Uuid::parse_str(possible_uuid) { + Ok(uuid) => match add_attempt(uuid, &attempt).await { + Some(_) => true, + None => false, + }, + Err(_) => false, + }; + + if redirect { + windmark::response::Response::temporary_redirect(format!("/{}", possible_uuid)) + } else { + let board: Board = Default::default(); + let result_pattern: Option = None; + let error = "La partida a la que intentas jugar no existe"; + + context.insert("board", &board); + context.insert("error", &error); + context.insert("result_pattern", &result_pattern); + windmark::response::Response::success(render("index", &context).await.unwrap()) + } + }) + .run() + .await +} diff --git a/src/status.rs b/src/status.rs new file mode 100644 index 0000000..72bf5dc --- /dev/null +++ b/src/status.rs @@ -0,0 +1,18 @@ +use serde::{Serialize, Serializer}; + +#[derive(Clone)] +pub enum Status { + NotFound, + Found, + Almost, +} + +impl Serialize for Status { + fn serialize(&self, serializer: S) -> Result { + match self { + Status::NotFound => serializer.serialize_char('-'), + Status::Almost => serializer.serialize_char('!'), + Status::Found => serializer.serialize_char(' '), + } + } +} diff --git a/src/words.rs b/src/words.rs new file mode 100644 index 0000000..4251608 --- /dev/null +++ b/src/words.rs @@ -0,0 +1,29 @@ +use chrono::Datelike; +use tokio::sync::OnceCell; + +static WORDS: OnceCell> = OnceCell::const_new(); + +async fn get_words() -> Vec { + let words = WORDS + .get_or_init(|| async { + include_str!("../words.txt") + .trim() + .lines() + .map(|w| w.trim().into()) + .collect() + }) + .await; + words.to_vec() +} + +pub async fn get_word_of_the_day() -> String { + let words = get_words().await; + + 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() +} diff --git a/templates/index.gmi.tera b/templates/index.gmi.tera new file mode 100644 index 0000000..7abcdac --- /dev/null +++ b/templates/index.gmi.tera @@ -0,0 +1,42 @@ +# Worsdle en español + +{% if error -%} +{{ error }} + +{% endif -%} + +``` +╔═══╦═══╦═══╦═══╦═══╗ +{% for attempt in board -%} +{%- if attempt -%} +║{% for char in attempt.chars %}{{ char.status }}{{ char.char }}{{ char.status }}║{% endfor -%} +{%- else -%} +║ ║ ║ ║ ║ ║ +{%- endif -%} +{% if not loop.last %} +╠═══╬═══╬═══╬═══╬═══╣ +{% endif -%} +{% endfor %} +╚═══╩═══╩═══╩═══╩═══╝ +``` + +{% if result_pattern -%} +{{ result_pattern }} + +{% endif -%} + +## Indicaciones: + +* -A- Letra no se encuentra en la palabra. +* !A! Letra se encuentra en la palabra pero en otro lugar. +* A Letra en el lugar correcto. + +## Instrucciones: + +* Para comenzar una partida nueva, ingresa a la URL sin ningún atributo o path. +* Cada que quieras ingresar un nuevo intento, escribe al final de la URL una diagonal seguido de tu palabra. Ejemplo: "/suelo". +* tienes 6 intentos para adivinar la palabra. +* La palabra es igual para todas las personas que visitan la página y cada día hay una palabra nueva. + +## Info: +Este clon de wordle está hecho por kirbylife utilizando el lenguaje de programación Rust, el framework Windmark y el editor Emacs diff --git a/words.txt b/words.txt new file mode 100644 index 0000000..a8a3146 --- /dev/null +++ b/words.txt @@ -0,0 +1,2238 @@ +ABANO +ABEJA +ABETO +ABONA +ABONO +ABRAN +ABRAS +ABREN +ABRIL +ABRIR +ABUSA +ABUSO +ACABA +ACABO +ACATA +ACATO +ACEDA +ACEDO +ACERA +ACERO +ACILO +ACOTA +ACOTE +ACOTO +ACTOR +ACTOS +ACUDA +ACUDE +ACUDO +ADIOS +ADOBA +ADOBE +ADOBO +ADORA +ADORO +ADULA +ADULO +AFEAN +AFEAR +AFILA +AFINA +AFINO +AFORA +AFORO +AGITA +AGITE +AGITO +AGOTA +AGOTO +AGRIA +AGRIO +AGUDA +AGUDO +AGUJA +AHOGA +AHOGO +AHORA +AIRAR +AIREO +AJENA +AJENO +AJERA +AJERO +ALABA +ALABO +ALAGA +ALAGO +ALBOR +ALBUR +ALCEN +ALCES +ALDEA +ALEAR +ALEJA +ALEJE +ALEJO +ALETA +ALFIL +ALIAS +ALOJA +ALOJO +ALPES +ALTAR +ALZAN +ALZAR +AMBAS +AMBOS +AMEBA +AMENO +AMIGA +AMIGO +ANCHA +ANCHO +ANCLA +ANCLO +ANDAN +ANDAR +ANDAS +ANDEN +ANEGO +ANEXA +ANEXO +ANGLO +ANIDA +ANIDO +ANIMA +ANIMO +ANSIA +ANSIO +ANTRO +ANUAL +ANUDA +ANUDO +ANULA +ANULE +ANULO +AÑADA +AÑADE +AÑADO +AÑEJA +AÑEJO +AÑORA +AÑORO +APEGA +APEGO +APNEA +APODO +APOYA +APOYE +APOYO +APTAR +APURA +APURE +APURO +AQUEL +ARAÑA +ARAÑO +ARDER +ARDOR +ARDUO +ARENA +ARETE +ARGOT +ARIES +ARMAN +ARMAR +ARMEN +ARRAS +ARREO +ARROZ +ARTES +ASCUA +ASEAN +ASEAR +ASNOS +ASOMA +ASOMO +ASTRO +ASUME +ASUMO +ATABA +ATACA +ATACO +ATINA +ATINE +ATINO +ATIZA +ATIZO +ATLAS +ATORA +ATORE +ATORO +ATRAE +ATRIL +ATRIO +ATROZ +AUDAZ +AUDIO +AUNAR +AUTOR +AVARA +AVARO +AVIAR +AVISA +AVISE +AVISO +AVIVA +AVIVE +AVIVO +AVOCO +AXILA +AYUDA +AYUDE +AYUDO +AYUNA +AYUNE +AYUNO +AZADA +AZOTA +AZOTE +AZOTO +BABEO +BABOR +BACHE +BAGAR +BAGRE +BAILE +BAJAN +BAJAR +BAJAS +BAJEN +BAJOS +BALAR +BALAS +BALDE +BALSA +BAMBA +BANAL +BANCA +BANCO +BANDA +BANDO +BAÑAN +BAÑAR +BAÑOS +BARBA +BARCA +BARCO +BARDA +BARIO +BARRA +BARRE +BARRO +BASAL +BASAN +BASCA +BASES +BASTA +BASTO +BATEA +BATEN +BATEO +BATES +BATIR +BAZAR +BEATA +BEATO +BEBAN +BEBEN +BEBER +BECAR +BEIGE +BELGA +BELLA +BELLO +BESAR +BICHO +BIELA +BILIS +BINGO +BIRLO +BISEL +BIZCA +BIZCO +BLEDO +BLUSA +BOBOS +BOCAL +BODAS +BOINA +BOLLO +BOLOS +BOLSA +BOLSO +BOMBA +BOMBO +BONOS +BORDA +BORDE +BORDO +BORLA +BORNE +BORRA +BORRE +BORRO +BOSTA +BOTAN +BOXEO +BOZAL +BRAMA +BRASA +BRAVA +BRAVO +BRAZO +BREVE +BRIDA +BRISA +BROCA +BROMA +BROMO +BROTA +BROTE +BROTO +BROZA +BRUJA +BRUJO +BRUMA +BRUTO +BUCAL +BUCEO +BUCHE +BUCLE +BUENA +BUENO +BUFAN +BUFAR +BULBO +BULLA +BULTO +BUQUE +BURDO +BURRA +BURRO +BUSCA +BUSCO +BUSES +BUSTO +BUZOS +CABAL +CABEN +CABER +CABLE +CABRA +CACAO +CACHA +CAFRE +CAJAS +CALAR +CALCA +CALCO +CALDO +CALLA +CALLE +CALLO +CALMA +CALMO +CALOR +CALVA +CALVO +CALZA +CALZO +CAMPO +CANAL +CANES +CANJE +CANOA +CANON +CANSA +CANSO +CANTA +CANTO +CAÑON +CAOBA +CAPAR +CAPAZ +CAQUI +CARAY +CARBA +CARDO +CAREA +CAREO +CARGA +CARGO +CARNE +CAROS +CARRO +CARTA +CASAN +CASAR +CASAS +CASCO +CASPA +CASTA +CASTO +CATAN +CATAR +CATEA +CATEO +CATRE +CAUCE +CAUSA +CAUSO +CAUTA +CAUTO +CAVAN +CAVAR +CAZAR +CEBAR +CEBRA +CEDAN +CEDEN +CEDER +CEDRO +CEGAR +CELAN +CELAR +CELDA +CELTA +CENAN +CENAR +CENIT +CENSA +CENSO +CEPAS +CERCA +CERCO +CERDA +CERDO +CERIO +CEROS +CERRO +CESAN +CESAR +CESEN +CESIO +CESTA +CESTO +CETRO +CHAPA +CHATA +CHATO +CHAVA +CHAVO +CHECA +CHECO +CHICA +CHICO +CHILE +CHINA +CHINO +CHIVA +CHIVO +CHOCA +CHOCO +CHULA +CHULO +CHUPE +CHUTE +CHUTO +CHUZA +CHUZO +CICLO +CIDRO +CIEGO +CIELO +CIFRA +CIFRO +CINCO +CINTA +CINTO +CIRCO +CIRIO +CISNE +CITAN +CITAR +CITAS +CIVIL +CLAMA +CLAMO +CLARA +CLARO +CLASE +CLAVA +CLAVE +CLAVO +CLERO +CLIMA +CLORO +COBRA +COBRE +COBRO +COCER +COCES +COCHE +CODOS +COFIA +COFRE +COJOS +COLAR +COLAS +COLMA +COLMO +COLON +COLOR +COMAL +COMAN +COMBO +COMEN +COMER +CONGO +COPAS +COPEE +COPIA +COPIE +COPIO +COPLA +COPOS +COQUE +CORAL +COREA +COROS +CORRA +CORRE +CORRO +CORTA +CORTE +CORTO +COSAN +COSEN +COSER +COSTA +COSTE +COSTO +COXIS +CRASA +CRASO +CREAN +CREAR +CRECE +CREDO +CREEN +CREER +CREMA +CREMO +CRIAR +CRIBA +CROAR +CROMO +CRUCE +CRUDO +CRUEL +CRUJA +CRUJE +CRUJO +CRUZA +CRUZO +CUAJA +CUAJO +CUBIL +CUBRA +CUBRO +CUCAR +CUECE +CUELA +CUELO +CUERO +CUEVA +CUIDA +CUIDO +CUITA +CULPA +CULPO +CULTA +CULTO +CUNDE +CUOTA +CURAN +CURAR +CUREN +CURES +CURRA +CURRO +CURRY +CURSA +CURSI +CURSO +CURUL +CURVA +CURVO +CUTIS +CUTRE +DABAN +DADOR +DAGAS +DAMOS +DANCE +DANDO +DANES +DANOS +DANZO +DAÑAR +DAÑAS +DAÑOS +DARDO +DARME +DARSE +DATAN +DATAR +DEBER +DECIR +DEDAL +DEJAR +DEMOS +DENLE +DENLO +DENME +DENSA +DENSE +DENSO +DESDE +DESEE +DEUDA +DICEN +DICHA +DICHO +DICTA +DICTO +DIERA +DIESE +DIETA +DIGAN +DIGNA +DIGNO +DILES +DIMOS +DINOS +DIODO +DIOSA +DIQUE +DISCO +DISTE +DOBLA +DOBLE +DOBLO +DOCTA +DOCTO +DOGMA +DOLAR +DOLER +DOLOR +DOMAR +DONAN +DONAR +DONAS +DONDE +DONEN +DOÑAS +DOPAR +DORAN +DORAR +DORNA +DORSO +DOSES +DOSIS +DOTAR +DRAMA +DRENA +DUCHA +DUCHO +DUDAS +DUELA +DUELE +DUELO +DUEÑA +DUEÑO +DUETO +DULCE +DUPLA +DUQUE +DURAN +DURAR +DUREN +DUROS +ECHAN +ECHAR +ECHAS +ECHEN +EDEMA +EDITA +EDUCA +EDUCO +EJIDO +ELEVA +ELEVO +ELIGE +ELLOS +ELOTE +ELUDE +ELUDO +EMITE +EMULO +ENANO +ENEMA +ENERO +ENOJA +ENOJO +ENTRA +ENTRE +ENTRO +EPICA +EPICO +EPOCA +EQUIS +ERBIO +ERIGE +ERIJO +ERIZA +ERIZO +ERRAR +ERROR +ESTAR +ESTOY +ETANO +ETAPA +ETICA +ETICO +ETILO +ETNIA +EVADE +EVITA +EVITE +EVITO +EVOCA +EVOCO +EXIGE +EXIJO +EXIME +EXIMO +EXTRA +FACTO +FAJAN +FAJAR +FALAZ +FALDA +FALLA +FALLO +FALSA +FALSO +FALTA +FALTO +FANGO +FARDA +FARDO +FAROL +FARSA +FATAL +FATUO +FAUNO +FAVOR +FECAL +FECHA +FELIZ +FELPA +FERIA +FEROZ +FETAL +FEUDO +FIBRA +FICHA +FICHO +FIDEO +FIERA +FIERO +FIJAN +FIJAR +FILIA +FILME +FILMO +FINAL +FINAN +FINCA +FINCO +FINES +FINGE +FINJO +FIRMA +FIRME +FIRMO +FISCO +FLACA +FLACO +FLAMA +FLECO +FLEMA +FLETE +FLOJA +FLOJO +FLORA +FLORO +FLOTA +FLOTE +FLOTO +FLUIR +FLUJO +FLUYA +FLUYE +FLUYO +FOBIA +FOCAL +FOCOS +FOLIO +FONDA +FONDO +FORMA +FORMO +FORRA +FORRO +FRASE +FRENA +FRENE +FRENO +FRITA +FRITO +FROTA +FROTE +FROTO +FRUTA +FRUTO +FUEGO +FUERA +FUERO +FUESE +FUGAR +FUGAZ +FULAR +FUNDA +FUNDE +FUNDO +FUNGE +FUNJO +FURIA +FUROR +FUSIL +GACHA +GACHO +GAITA +GAJOS +GALIO +GALLO +GAMBA +GANAN +GANAR +GANAS +GANEN +GANGA +GANSO +GARRA +GARZA +GASES +GASTA +GASTO +GATEA +GATEO +GATOS +GELES +GEMIR +GENIO +GENTE +GESTO +GIRAN +GIRAR +GIREN +GIROS +GLOBO +GNOMO +GOLES +GOLFO +GOLPE +GORDA +GORDO +GORRA +GORRO +GOTEA +GOTEO +GOZAR +GRABA +GRABO +GRADA +GRADO +GRAFO +GRAMO +GRANO +GRAPA +GRASA +GRASO +GRATA +GRATO +GRAVA +GRAVE +GRAVO +GRECO +GRIFO +GRIMA +GRIPA +GRIPE +GRITA +GRITE +GRITO +GRUMO +GRUÑE +GRUÑO +GRUPO +GRUTA +GUANO +GUAPO +GUIAR +GUION +GUISO +GUSTA +GUSTO +HABER +HABLA +HABLO +HACEN +HACER +HACES +HACHA +HACHE +HACIA +HAGAN +HALAN +HALAR +HALOS +HARTA +HARTO +HASTA +HAYAN +HAZLE +HAZLO +HAZME +HAZTE +HEBRA +HECHA +HECHO +HEDER +HEDOR +HELAR +HELIO +HEMOS +HERIR +HERTZ +HIDRA +HIELA +HIELO +HIENA +HIERE +HIERO +HIJOS +HILAN +HILAR +HILOS +HIMNO +HINCA +HINCO +HINDI +HOGAR +HOJEA +HOJEO +HONDA +HONDO +HONGO +HONOR +HONRA +HONRO +HORCA +HORDA +HORMA +HORNO +HOTEL +HUECA +HUECO +HUELE +HUELO +HUESO +HUEVA +HUEVO +HUMOR +HUNDO +HURGO +HURTA +HURTO +ICONO +IDEAL +IDEAR +IGUAL +IMITA +IMITO +IMPAR +INDIA +INDIO +INFLA +INFLO +INGLE +INSTA +INSTO +IONES +ITERA +ITERO +ITRIO +JACTA +JACTO +JADEA +JADEO +JAIBA +JALAR +JALEA +JAQUE +JARRA +JARRO +JAULA +JEQUE +JERBO +JEREZ +JERGA +JIBIA +JODIA +JOVEN +JUEGO +JUGAR +JULIO +JUNIO +JUNTA +JUNTO +JURAN +JURAR +JURAS +JUSTA +JUSTO +JUZGA +JUZGO +KARMA +KENIA +LABIA +LABIO +LABOR +LABRA +LABRO +LACIA +LACIO +LACTA +LACTO +LADEA +LADEO +LADOS +LADRA +LADRO +LAGOS +LAICO +LAMEN +LAMER +LAMIA +LANZA +LANZO +LAPSO +LARGA +LARGO +LARVA +LATEN +LATIR +LAVAN +LAVAR +LAXAR +LAZAN +LAZAR +LAZOS +LECHE +LECHO +LEGAL +LEGUA +LEJOS +LENTA +LENTE +LENTO +LEÑOS +LEONA +LEPRA +LETAL +LETRA +LEYES +LIANA +LIBIA +LIBIO +LIBRA +LIBRE +LIBRO +LICOR +LICUA +LICUO +LIDER +LIDIA +LIDIO +LIGAN +LIGAR +LIGUE +LIJAN +LIJAR +LIMAN +LIMAR +LIMBO +LINCE +LINDA +LINDO +LINEA +LIOSA +LIOSO +LIRIO +LISTA +LISTO +LITIO +LITRO +LIZOS +LLACA +LLAGA +LLAMA +LLAMO +LLANO +LLAVE +LLEGA +LLEGO +LLENA +LLENO +LLEVA +LLEVO +LLORA +LLORE +LLORO +LOBAS +LOBOS +LOCAL +LOCAS +LOCOS +LODOS +LOGIA +LOGRA +LOGRO +LOMOS +LONGA +LONJA +LOROS +LOSAR +LUCEN +LUCHA +LUCHO +LUCIR +LUCRA +LUCRO +LUEGO +LUGAR +LUMEN +LUNAR +LUNES +LUPUS +LUTOS +LUZCA +LUZCO +MACHO +MACRO +MADRE +MAFIA +MAGMA +MAGNO +MAGRA +MAGRO +MALES +MALLA +MALTA +MAMAS +MAMBO +MAMEY +MAMUT +MANCO +MANDA +MANDE +MANDO +MANGA +MANGO +MANOS +MANSA +MANSO +MANTA +MANTO +MARCA +MARCO +MAREA +MAREO +MARES +MARTE +MARZO +MATAN +MATAR +MATEN +MATIZ +MATON +MAYOR +MAZAR +MECER +MECHA +MEDIA +MEDIO +MEDIR +MEJOR +MENEA +MENEO +MENOR +MENSA +MENSO +MENTA +MENTE +MESAS +MESES +METAL +METAN +METEN +METER +METRO +MICRO +MIDAN +MIDEN +MIEDO +MIGRA +MIGRO +MILPA +MIMAR +MINAN +MINAR +MIOMA +MIOPE +MIRAN +MIRAR +MIRAS +MIREN +MIRRA +MIRTO +MISAL +MISIL +MISMO +MITIN +MITOS +MIXTO +MODAL +MOJAN +MOJAR +MOJEN +MOLDE +MOLES +MOMIA +MONJE +MONOS +MONTA +MONTE +MONTO +MORAL +MORAN +MORAR +MORAS +MORBO +MORIA +MORIR +MORSA +MORSE +MOSCA +MOSCO +MOTEL +MOTOR +MOVER +MOVIA +MOZOS +MUCHA +MUCHO +MUDAN +MUDAR +MUDAS +MUDEN +MUDEZ +MUDOS +MUECA +MUELA +MUELE +MUELO +MUERA +MUERE +MUERO +MUEVA +MUEVE +MUEVO +MUGIR +MUGRE +MUJER +MULAS +MULTA +MULTO +MUNDO +MURAL +MURAR +MUSEO +MUSGO +MUSLO +MUTAN +MUTAR +MUTUO +NACEN +NACER +NACES +NACHO +NADAN +NADAR +NADEN +NADIE +NAIPE +NALGA +NARIZ +NASAL +NATAL +NATOS +NAVAL +NAZCA +NAZCO +NECIA +NECIO +NEGAR +NEGRA +NEGRO +NEVAR +NICHO +NIEGA +NIEGO +NIETA +NIETO +NIEVA +NIEVE +NIEVO +NIÑAS +NIÑES +NIÑEZ +NIÑOS +NITRO +NIVEL +NOBEL +NOBLE +NOCHE +NOGAL +NOPAL +NORIA +NORMA +NORTE +NOTAN +NOTAR +NOTAS +NOVEL +NOVIA +NOVIE +NOVIO +NUBLA +NUBLO +NUERA +NUEVA +NUEVE +NUEVO +NULOS +NUNCA +NUTRE +NUTRO +ÑANDU +OASIS +OBESO +OBLEA +OBRAN +OBRAR +OBVIO +OCASO +OCUPA +OCUPO +ODIAR +OESTE +OJEAR +OJERA +OJITO +OJIVA +OLEAR +OLIVA +OLIVO +OLOTE +OMEGA +OMISO +OMITO +ONDEA +ONDEO +OPACA +OPACO +OPERA +OPERO +OPINA +OPINO +OPTAN +OPTAR +ORATE +ORDEN +OREAR +OREJA +ORUGA +OSMIO +OTOÑO +OVEJA +OVINO +OVULA +OVULO +OXIDA +OXIDO +OZONO +PACTA +PACTO +PADRE +PAGAN +PAGAR +PAGAS +PAGOS +PALAS +PALCO +PALEA +PALEO +PALMA +PALMO +PALOS +PALPA +PALPO +PALTA +PANAL +PANDA +PANDO +PANEL +PANES +PANZA +PAÑAL +PAPAS +PAPEL +PARAN +PARAR +PARAS +PARCA +PARDA +PARDO +PAREN +PARES +PARGO +PARIO +PARIR +PARON +PAROS +PARRA +PARTE +PARTO +PASAN +PASAR +PASAS +PASEN +PASEO +PASOS +PASTA +PASTE +PASTO +PATEA +PATEO +PATIO +PATOS +PAUSA +PAUSO +PAUTA +PAUTO +PAVOR +PEAJE +PECAR +PECHO +PEDAL +PEDIR +PEGAN +PEGAR +PEINA +PEINE +PEINO +PELAN +PELAR +PELEA +PELEO +PENAL +PENAN +PENAR +PENCA +PENDA +PENDE +PENDO +PERAS +PERLA +PERNO +PERRA +PERRO +PERSA +PESAN +PESAR +PESCA +PESCO +PESTE +PESTO +PIANO +PICAN +PICAR +PICOR +PICOS +PIDAN +PIDEN +PIEZA +PIFIA +PILAR +PINES +PINOS +PINTA +PINTO +PINZA +PIÑAS +PIÑON +PIOJO +PIRAN +PIRAR +PISAN +PISAR +PISAS +PISCO +PISTA +PITAN +PITAR +PIZCA +PIZZA +PLACA +PLANA +PLANO +PLATA +PLATO +PLAYA +PLAZA +PLAZO +PLEBE +PLENA +PLENO +PLEXO +PLOMO +PLUMA +POBRE +PODAN +PODAR +PODAS +PODER +PODIA +PODIO +PODRE +POEMA +POETA +POLAR +POLEA +POLEN +POLIO +POLLO +POLVO +PONEN +PONER +PONGA +PONGO +PONLA +PONLO +PONTE +PORRA +PORRO +PORTA +PORTE +PORTO +POSAN +POSAR +POSAS +POSEA +POSEN +POSEO +POSTE +POTRA +POTRO +PRADO +PRESA +PRESO +PRIMA +PRIME +PRIMO +PRISA +PRIVA +PRIVO +PROSA +PUBIS +PUDIN +PUDOR +PUDRA +PUDRE +PUDRO +PUGNA +PUGNO +PUJAN +PUJAR +PUJEN +PULAN +PULEN +PULGA +PULIR +PULLA +PULPA +PULPO +PULSA +PULSO +PUNAN +PUNIR +PUNTA +PUNTO +PUÑOS +PURGA +PURGO +PUROS +QUEDA +QUEDO +QUEJA +QUEJO +QUEMA +QUEMO +QUESO +QUIEN +QUINA +QUISE +QUISO +QUITA +QUITO +RABIA +RABOS +RADAR +RADIO +RAFIA +RAJAR +RALLA +RALLO +RAMAS +RAMOS +RAMPA +RANGO +RAPAN +RAPAR +RAPAZ +RAPTA +RAPTO +RARAS +RAROS +RASCA +RASCO +RASGA +RASGO +RASOS +RASPA +RASPO +RATAS +RATEA +RATEO +RATON +RATOS +RAUDO +RAYAN +RAYAR +RAYOS +RAZON +RECIA +RECIO +RECTA +RECTO +REDES +REDIL +REDOR +REGAR +REGIO +REGIR +REGLA +REINA +REINO +RELAX +RELOJ +RENAL +RENCA +RENCO +RENTA +RENTO +RESTA +RESTO +RETAR +RETAS +RETOS +RETRO +REUMA +REZAR +RICAS +RICOS +RIEGA +RIEGO +RIESE +RIFAR +RIFLE +RIGEN +RIGOR +RIJAN +RIMAR +RINDE +RINDO +RIÑAS +RIÑON +RISCO +RITMO +RITOS +RIVAL +RIZAN +RIZAR +RIZOS +ROBAR +ROBLE +ROBOT +RODAR +RODEO +RODIO +ROGAR +ROJAS +ROJOS +ROLLO +ROMBO +RONCA +RONCO +RONDA +ROPAS +ROSAL +ROSAR +ROSCA +ROSEA +ROSEO +ROTAN +ROTAR +ROTOR +ROZAR +RUBIA +RUBIO +RUBLO +RUBOR +RUBRO +RUECA +RUEDA +RUEDO +RUEGA +RUEGO +RUGBY +RUGIR +RUIDO +RUINA +RUMBA +RUMBO +RUMOR +RUPIA +RURAL +RUSIA +SABEN +SABER +SABIA +SABIO +SABLE +SABOR +SACAN +SACAR +SACAS +SACIA +SACIO +SACOS +SACRO +SAGAZ +SALAN +SALAR +SALDA +SALDO +SALEN +SALGA +SALGO +SALIA +SALIO +SALIR +SALMO +SALSA +SALTA +SALTE +SALTO +SALVA +SALVO +SANAN +SANAR +SANAS +SANOS +SANTA +SANTO +SARNA +SARRO +SARTA +SAUCE +SAUNA +SAVIA +SECAN +SECAR +SECAS +SECOS +SECTA +SEDAL +SEDAN +SEDAR +SEDEN +SEGAR +SELLA +SELLO +SELVA +SENDA +SENIL +SEÑAL +SEÑAS +SEÑOR +SEPAN +SEPIA +SERAS +SERES +SERIA +SERIE +SERIO +SESEO +SESGO +SEXTA +SEXTO +SIDRA +SIEGA +SIEGO +SIETE +SIGAN +SIGLO +SIGMA +SIGNO +SIGUE +SILBA +SILBO +SILLA +SIMIO +SIRIA +SIRIO +SIRVA +SIRVE +SIRVO +SISMO +SITIO +SOBAN +SOBAR +SOBRA +SOBRE +SOBRO +SOCIA +SOCIO +SODIO +SOGAS +SOLAR +SOLAS +SOLES +SOLOS +SOMOS +SONAR +SONDA +SOÑAR +SOPES +SOPLA +SOPLE +SOPLO +SORBE +SORBO +SORDA +SORDO +SORGO +SUAVE +SUBIR +SUCIA +SUCIO +SUDAN +SUDAR +SUDEN +SUDOR +SUECO +SUELA +SUELE +SUELO +SUENA +SUENO +SUEÑO +SUERO +SUFRE +SUIZA +SUIZO +SUMAN +SUMAR +SUMEN +SUMIR +SUPLA +SUPLE +SUPLO +SURCA +SURCO +SURGE +SURJA +SURJO +SURTA +SURTE +SURTO +SUSTO +SUTIL +TABLA +TACHA +TACHE +TACHO +TACOS +TACTO +TAJAN +TAJAR +TALAR +TALCO +TALIO +TALLA +TALLO +TAMAL +TAMBO +TAMIZ +TANDA +TANGO +TANTO +TAPAN +TAPAR +TAPAS +TAPEN +TAPIA +TAPIR +TAPIZ +TARDA +TARDE +TARDO +TAREA +TARJA +TARRO +TARTA +TASAN +TASAR +TAURO +TECHO +TECLA +TEDIO +TEJAN +TEJEN +TEJER +TELAR +TELAS +TELES +TEMAS +TEMER +TEMOR +TEMPO +TENAZ +TENER +TENGA +TENGO +TENIA +TENIS +TENLA +TENLO +TENME +TENOR +TENSA +TENSO +TENUE +TEÑIR +TERCA +TERCO +TERMO +TERNA +TERSO +TESIS +TESLA +TESTA +TEXTO +TIARA +TIBIA +TIBIO +TIENE +TIESA +TIESO +TIFUS +TIGRE +TILDA +TILDE +TILDO +TILMA +TIMAN +TIMAR +TIMEN +TIMOS +TINTA +TINTE +TINTO +TIPOS +TIRAN +TIRAR +TIRAS +TIREN +TIROS +TIZNA +TIZNE +TIZNO +TOCAN +TOCAR +TOCAS +TOLDO +TOLVA +TOMAN +TOMAR +TOMEN +TOMOS +TONAL +TONGO +TONOS +TONTA +TONTO +TOPAR +TOPAS +TOPOS +TOQUE +TORDA +TORDO +TOREA +TOREO +TORIO +TORNA +TORNO +TORPE +TORRE +TORSO +TORTA +TOSCO +TOSEN +TOSER +TOTAL +TRABA +TRABE +TRABO +TRAEN +TRAER +TRAGA +TRAGO +TRAJE +TRAJO +TRAMA +TRAMO +TRAPO +TRATA +TRATE +TRATO +TRAZA +TRAZO +TRECE +TREPA +TREPE +TREPO +TRIBU +TRIGO +TRINA +TRINO +TRIPA +TRONO +TROPA +TROPO +TROTE +TROVA +TROZO +TRUCO +TRUFA +TULIO +TUMBA +TUMBO +TUMOR +TUNDA +TUNDO +TUPIR +TURBA +TURBO +TURCA +TURCO +TURNA +TURNO +TUSAN +TUSAR +TUTOR +TUYAS +TUYOS +UBICA +UBICO +ULTRA +UNGIR +UNIDA +UNIDO +UNTAN +UNTAR +URGIR +USALA +USALO +USUAL +UVERO +VACUO +VADEA +VADEO +VAGAR +VAINA +VALEN +VALER +VALGA +VALGO +VALLA +VALLE +VALOR +VAMOS +VAPOR +VARIA +VARIO +VARIZ +VASOS +VASTA +VASTO +VATIO +VAYAN +VELAN +VELAR +VELLO +VELOS +VELOZ +VENCE +VENDA +VENDE +VENDO +VENGA +VENGO +VENIA +VENIR +VENTA +VENUS +VENZA +VENZO +VERAZ +VERBO +VERDE +VERJA +VERLA +VERLO +VERME +VERSA +VERSO +VERTE +VETAR +VIAJA +VIAJE +VIAJO +VIBRA +VIBRE +VIBRO +VICIA +VICIO +VIDEO +VIEJA +VIEJO +VIENE +VIGOR +VILLA +VINOS +VIRAL +VIRAN +VIRAR +VIRGO +VIRIL +VIRUS +VISOR +VISTA +VISTO +VITAL +VIUDA +VIUDO +VIVAN +VIVAR +VIVAS +VIVAZ +VIVEN +VIVIR +VIVOS +VOCAL +VOCEA +VOCEO +VODKA +VOLAR +VOLEA +VOLEO +VORAZ +VOTAN +VOTAR +VOTEN +VOTOS +VUELA +VUELO +WEBER +YACEN +YACER +YACES +YARDA +YEGUA +YELMO +YEMEN +YENDO +YERBA +YERNO +YOGUR +YUGOS +ZAFAR +ZANCA +ZANCO +ZARCO +ZARPA +ZORRA +ZORRO +ZUECO +ZUMBA +ZUMOS +ZURDA +ZURDO