From cbd303056547f18650058c4f9cbb4a00839ae338 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Mon, 25 Jul 2022 22:58:02 +0200 Subject: [PATCH 01/14] Create initial wordle methods --- database/crud/wordle.py | 23 ++++++++ database/mongo_types.py | 6 ++ database/schemas/mongo.py | 58 +++++++++++++++++++- tests/test_database/test_crud/test_wordle.py | 42 ++++++++++++++ 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 database/crud/wordle.py create mode 100644 database/mongo_types.py create mode 100644 tests/test_database/test_crud/test_wordle.py diff --git a/database/crud/wordle.py b/database/crud/wordle.py new file mode 100644 index 0000000..5a2211c --- /dev/null +++ b/database/crud/wordle.py @@ -0,0 +1,23 @@ +from typing import Optional + +from database.mongo_types import MongoCollection +from database.schemas.mongo import WordleGame + +__all__ = ["get_active_wordle_game", "make_wordle_guess", "start_new_wordle_game"] + + +async def get_active_wordle_game(collection: MongoCollection, user_id: int) -> Optional[WordleGame]: + """Find a player's active game""" + return await collection.find_one({"user_id": user_id}) + + +async def start_new_wordle_game(collection: MongoCollection, user_id: int, word: str) -> WordleGame: + """Start a new game""" + game = WordleGame(user_id=user_id, word=word) + await collection.insert_one(game.dict(by_alias=True)) + return game + + +async def make_wordle_guess(collection: MongoCollection, user_id: int, guess: str): + """Make a guess in your current game""" + await collection.update_one({"user_id": user_id}, {"$push": {"guesses": guess}}) diff --git a/database/mongo_types.py b/database/mongo_types.py new file mode 100644 index 0000000..11f5b7a --- /dev/null +++ b/database/mongo_types.py @@ -0,0 +1,6 @@ +import motor.motor_asyncio + +# Type aliases for the Motor types, which are way too long +MongoClient = motor.motor_asyncio.AsyncIOMotorClient +MongoDatabase = motor.motor_asyncio.AsyncIOMotorDatabase +MongoCollection = motor.motor_asyncio.AsyncIOMotorCollection diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index 95d2a2a..6560b95 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -1,7 +1,12 @@ -from bson import ObjectId -from pydantic import BaseModel, Field +import datetime +from abc import ABC, abstractmethod +from typing import Optional -__all__ = ["MongoBase"] +from bson import ObjectId +from overrides import overrides +from pydantic import BaseModel, Field, conlist + +__all__ = ["MongoBase", "WordleGame"] class PyObjectId(str): @@ -36,3 +41,50 @@ class MongoBase(BaseModel): arbitrary_types_allowed = True json_encoders = {ObjectId: str, PyObjectId: str} use_enum_values = True + + +class MongoCollection(MongoBase, ABC): + """Base model for the 'main class' in a collection + + This field stores the name of the collection to avoid making typos against it + """ + + @staticmethod + @abstractmethod + def collection() -> str: + raise NotImplementedError + + +class WordleStats(BaseModel): + """Model that holds stats about a player's Wordle performance""" + + guess_distribution: conlist(int, min_items=6, max_items=6) = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) + last_guess: Optional[datetime.date] = None + win_rate: float = 0 + current_streak: int = 0 + max_streak: int = 0 + + +class GameStats(MongoCollection): + """Collection that holds stats about how well a user has performed in games""" + + user_id: int + wordle: Optional[WordleStats] = None + + @staticmethod + @overrides + def collection() -> str: + return "game_stats" + + +class WordleGame(MongoCollection): + """Collection that holds people's active Wordle games""" + + user_id: int + word: str + guesses: conlist(str, min_items=0, max_items=6) = Field(default_factory=list) + + @staticmethod + @overrides + def collection() -> str: + return "wordle" diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py new file mode 100644 index 0000000..6b85d7e --- /dev/null +++ b/tests/test_database/test_crud/test_wordle.py @@ -0,0 +1,42 @@ +import pytest + +from database.crud import wordle as crud +from database.mongo_types import MongoCollection, MongoDatabase +from database.schemas.mongo import WordleGame + + +@pytest.fixture +async def wordle_collection(mongodb: MongoDatabase) -> MongoCollection: + """Fixture to get a reference to the wordle collection""" + yield mongodb[WordleGame.collection()] + + +@pytest.fixture +async def wordle_game(wordle_collection: MongoCollection, test_user_id: int) -> WordleGame: + """Fixture to create a new game""" + game = WordleGame(user_id=test_user_id, word="test") + await wordle_collection.insert_one(game.dict(by_alias=True)) + yield game + + +async def test_start_new_game(wordle_collection: MongoCollection, test_user_id: int): + """Test starting a new game""" + result = await wordle_collection.find_one({"user_id": test_user_id}) + assert result is None + + await crud.start_new_wordle_game(wordle_collection, test_user_id, "test") + + result = await wordle_collection.find_one({"user_id": test_user_id}) + assert result is not None + + +async def test_get_active_wordle_game_none(wordle_collection: MongoCollection, test_user_id: int): + """Test getting an active game when there is none""" + result = await crud.get_active_wordle_game(wordle_collection, test_user_id) + assert result is None + + +async def test_get_active_wordle_game(wordle_collection: MongoCollection, wordle_game: WordleGame): + """Test getting an active game when there is none""" + result = await crud.get_active_wordle_game(wordle_collection, wordle_game.user_id) + assert result == wordle_game.dict(by_alias=True) From ea4181eac04e2321f92d73e9a0974b2afebc3a49 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Tue, 26 Jul 2022 21:48:50 +0200 Subject: [PATCH 02/14] Fix broken time formatting, remove word field --- database/crud/wordle.py | 30 +- database/enums.py | 9 +- database/schemas/mongo.py | 19 +- database/utils/datetime.py | 11 +- didier/cogs/games.py | 27 + didier/cogs/tasks.py | 31 +- didier/didier.py | 14 + files/dictionaries/words-english-wordle.txt | 21952 +++++++++++++++++ tests/test_database/test_crud/test_wordle.py | 4 +- 9 files changed, 22085 insertions(+), 12 deletions(-) create mode 100644 didier/cogs/games.py create mode 100644 files/dictionaries/words-english-wordle.txt diff --git a/database/crud/wordle.py b/database/crud/wordle.py index 5a2211c..4f4e650 100644 --- a/database/crud/wordle.py +++ b/database/crud/wordle.py @@ -1,7 +1,9 @@ from typing import Optional +from database.enums import TempStorageKey from database.mongo_types import MongoCollection from database.schemas.mongo import WordleGame +from database.utils.datetime import today_only_date __all__ = ["get_active_wordle_game", "make_wordle_guess", "start_new_wordle_game"] @@ -11,9 +13,9 @@ async def get_active_wordle_game(collection: MongoCollection, user_id: int) -> O return await collection.find_one({"user_id": user_id}) -async def start_new_wordle_game(collection: MongoCollection, user_id: int, word: str) -> WordleGame: +async def start_new_wordle_game(collection: MongoCollection, user_id: int) -> WordleGame: """Start a new game""" - game = WordleGame(user_id=user_id, word=word) + game = WordleGame(user_id=user_id) await collection.insert_one(game.dict(by_alias=True)) return game @@ -21,3 +23,27 @@ async def start_new_wordle_game(collection: MongoCollection, user_id: int, word: async def make_wordle_guess(collection: MongoCollection, user_id: int, guess: str): """Make a guess in your current game""" await collection.update_one({"user_id": user_id}, {"$push": {"guesses": guess}}) + + +async def get_daily_word(collection: MongoCollection) -> Optional[str]: + """Get the word of today""" + result = await collection.find_one({"key": TempStorageKey.WORDLE_WORD, "day": today_only_date()}) + if result is None: + return None + + return result["word"] + + +async def set_daily_word(collection: MongoCollection, word: str): + """Set the word of today + + This does NOT overwrite the existing word if there is one, so that it can safely run + on startup every time + """ + current_word = await get_daily_word(collection) + if current_word is not None: + return + + await collection.update_one( + {"key": TempStorageKey.WORDLE_WORD}, {"day": today_only_date(), "word": word}, upsert=True + ) diff --git a/database/enums.py b/database/enums.py index 3f75130..f64998e 100644 --- a/database/enums.py +++ b/database/enums.py @@ -1,6 +1,6 @@ import enum -__all__ = ["TaskType"] +__all__ = ["TaskType", "TempStorageKey"] # There is a bug in typeshed that causes an incorrect PyCharm warning @@ -11,3 +11,10 @@ class TaskType(enum.IntEnum): BIRTHDAYS = enum.auto() UFORA_ANNOUNCEMENTS = enum.auto() + + +@enum.unique +class TempStorageKey(str, enum.Enum): + """Enum for keys to distinguish the TemporaryStorage rows""" + + WORDLE_WORD = "wordle_word" diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index 6560b95..90b9deb 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -6,7 +6,9 @@ from bson import ObjectId from overrides import overrides from pydantic import BaseModel, Field, conlist -__all__ = ["MongoBase", "WordleGame"] +__all__ = ["MongoBase", "TemporaryStorage", "WordleGame"] + +from database.utils.datetime import today_only_date class PyObjectId(str): @@ -55,6 +57,17 @@ class MongoCollection(MongoBase, ABC): raise NotImplementedError +class TemporaryStorage(MongoCollection): + """Collection for lots of random things that don't belong in a full-blown collection""" + + key: str + + @staticmethod + @overrides + def collection() -> str: + return "temporary" + + class WordleStats(BaseModel): """Model that holds stats about a player's Wordle performance""" @@ -80,9 +93,9 @@ class GameStats(MongoCollection): class WordleGame(MongoCollection): """Collection that holds people's active Wordle games""" - user_id: int - word: str + day: datetime.date = Field(default_factory=lambda: today_only_date()) guesses: conlist(str, min_items=0, max_items=6) = Field(default_factory=list) + user_id: int @staticmethod @overrides diff --git a/database/utils/datetime.py b/database/utils/datetime.py index 8450e84..c1796d6 100644 --- a/database/utils/datetime.py +++ b/database/utils/datetime.py @@ -1,5 +1,14 @@ +import datetime import zoneinfo -__all__ = ["LOCAL_TIMEZONE"] +__all__ = ["LOCAL_TIMEZONE", "today_only_date"] LOCAL_TIMEZONE = zoneinfo.ZoneInfo("Europe/Brussels") + + +def today_only_date() -> datetime.datetime: + """Mongo can't handle datetime.date, so we need datetime + + We do, however, only care about the date, so remove all the rest + """ + return datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) diff --git a/didier/cogs/games.py b/didier/cogs/games.py new file mode 100644 index 0000000..765448f --- /dev/null +++ b/didier/cogs/games.py @@ -0,0 +1,27 @@ +from typing import Optional + +from discord import app_commands +from discord.ext import commands + +from didier import Didier + + +class Games(commands.Cog): + """Cog for various games""" + + client: Didier + + def __init__(self, client: Didier): + self.client = client + + @app_commands.command(name="wordle", description="Play Wordle!") + async def wordle(self, ctx: commands.Context, guess: Optional[str] = None): + """View your active Wordle game + + If an argument is provided, make a guess instead + """ + + +async def setup(client: Didier): + """Load the cog""" + await client.add_cog(Games(client)) diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index 6a1e5c6..d37990e 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -8,6 +8,8 @@ import settings from database import enums from database.crud.birthdays import get_birthdays_on_day from database.crud.ufora_announcements import remove_old_announcements +from database.crud.wordle import set_daily_word +from database.schemas.mongo import TemporaryStorage from didier import Didier from didier.data.embeds.ufora.announcements import fetch_ufora_announcements from didier.decorators.tasks import timed_task @@ -46,7 +48,14 @@ class Tasks(commands.Cog): self.pull_ufora_announcements.start() self.remove_old_ufora_announcements.start() - self._tasks = {"birthdays": self.check_birthdays, "ufora": self.pull_ufora_announcements} + # Start other tasks + self.reset_wordle_word.start() + + self._tasks = { + "birthdays": self.check_birthdays, + "ufora": self.pull_ufora_announcements, + "wordle": self.reset_wordle_word, + } @commands.group(name="Tasks", aliases=["Task"], case_insensitive=True, invoke_without_command=True) @commands.check(is_owner) @@ -113,6 +122,17 @@ class Tasks(commands.Cog): async with self.client.postgres_session as session: await remove_old_announcements(session) + @tasks.loop(time=DAILY_RESET_TIME) + async def reset_wordle_word(self): + """Reset the daily Wordle word""" + db = self.client.mongo_db + collection = db[TemporaryStorage.collection()] + await set_daily_word(collection, random.choice(self.client.wordle_words)) + + @reset_wordle_word.before_loop + async def _before_reset_wordle_word(self): + await self.client.wait_until_ready() + @check_birthdays.error @pull_ufora_announcements.error @remove_old_ufora_announcements.error @@ -123,5 +143,10 @@ class Tasks(commands.Cog): async def setup(client: Didier): - """Load the cog""" - await client.add_cog(Tasks(client)) + """Load the cog + + Initially reset the Wordle word + """ + cog = Tasks(client) + await client.add_cog(cog) + await cog.reset_wordle_word() diff --git a/didier/didier.py b/didier/didier.py index ce81307..ea0ac41 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -27,6 +27,7 @@ class Didier(commands.Bot): error_channel: discord.abc.Messageable initial_extensions: tuple[str, ...] = () http_session: ClientSession + wordle_words: tuple[str] = tuple() def __init__(self): activity = discord.Activity(type=discord.ActivityType.playing, name=settings.DISCORD_STATUS_MESSAGE) @@ -60,6 +61,9 @@ class Didier(commands.Bot): This hook is called once the bot is initialised """ + # Load the Wordle dictionary + self._load_wordle_words() + # Load extensions await self._load_initial_extensions() await self._load_directory_extensions("didier/cogs") @@ -101,6 +105,16 @@ class Didier(commands.Bot): elif os.path.isdir(new_path := f"{path}/{file}"): await self._load_directory_extensions(new_path) + def _load_wordle_words(self): + """Load the dictionary of Wordle words""" + words = [] + + with open("files/dictionaries/words-english-wordle.txt", "r") as fp: + for line in fp: + words.append(line.strip()) + + self.wordle_words = tuple(words) + async def resolve_message(self, reference: discord.MessageReference) -> discord.Message: """Fetch a message from a reference""" # Message is in the cache, return it diff --git a/files/dictionaries/words-english-wordle.txt b/files/dictionaries/words-english-wordle.txt new file mode 100644 index 0000000..42de6b5 --- /dev/null +++ b/files/dictionaries/words-english-wordle.txt @@ -0,0 +1,21952 @@ +aahed +aalii +aalst +aalto +aamsi +aapss +aarau +aaren +aargh +aaron +aavso +ababa +abaca +abaci +aback +abaco +abada +abaff +abaft +abaka +abama +abamp +abana +aband +abase +abash +abask +abate +abats +abaue +abave +abaze +abbai +abbas +abbey +abbes +abbie +abbye +abbot +abdal +abdat +abdel +abdom +abdon +abdul +abeam +abear +abebi +abede +abele +abell +abend +abepp +aberr +abert +abets +abhor +abide +abidi +abied +abyed +abies +abyes +abihu +abyla +abilo +abime +abysm +abyss +abkar +abler +ables +ablet +ablow +abmho +abner +abnet +abode +abody +abohm +aboil +aboma +aboon +abord +aborn +abort +abote +abott +about +above +abray +abram +abran +abret +abrim +abrin +abris +abrus +absbh +absee +absey +absis +absit +abstr +abuna +abune +abura +abury +abuse +abush +abuta +abuts +abuzz +abwab +acale +acana +acapu +acara +acari +acast +acate +acaws +accad +accel +accoy +accra +accts +accum +accur +accus +aceae +acean +acedy +acerb +acers +aceta +achab +achad +achan +achar +achaz +ached +achen +acher +aches +achoo +achor +acidy +acids +acier +acies +acyls +acima +acing +acini +acity +ackee +ackey +acker +aclys +acmes +acmic +acmon +acned +acnes +acock +acoin +acold +acoma +acone +acool +acorn +acost +acoup +acrab +acred +acres +acrid +acryl +acroa +acron +acrux +acted +actin +actis +acton +actor +actos +actpu +actup +actus +acuan +acute +adage +adagy +adaha +adair +adays +adala +adali +adall +adama +adamo +adams +adana +adapa +adapt +adara +adati +adaty +adawe +adawn +adccp +adcon +addam +addax +addcp +addda +added +adder +addia +addie +addio +addis +addle +addnl +adead +adeem +adeep +adela +adele +adell +adena +adeps +adept +adest +adfix +adfrf +adger +adham +adiel +adieu +adige +adyge +adila +adina +adine +adion +adios +adyta +adits +adjag +adlai +adlay +adlar +adlee +adlei +adley +adler +adlet +admah +adman +admen +admin +admit +admix +admov +admrx +adnah +adnan +adnex +adobe +adobo +adolf +adona +adopt +adora +adore +adorl +adorn +adowa +adown +adoxa +adoxy +adoze +adpao +adpcm +adrad +adrea +adret +adria +adrip +adron +adrop +adrue +adsum +adult +adunc +adure +adusk +adust +aduwa +adzer +adzes +aeaea +aecia +aedes +aedon +aeger +aegia +aegir +aegis +aegle +aella +aello +aemia +aenea +aeons +aequi +aeria +aeric +aerie +aerol +aeron +aesir +aesop +aetat +aetna +aevia +aevum +aface +afads +afara +afars +afcac +afear +affer +affix +affra +afgod +afifi +afyon +afips +afire +aflat +afley +aflex +aflow +afnor +afoam +afoot +afore +afoul +afray +afret +afric +afrit +afros +after +afton +aftra +agace +agada +agade +again +agama +agami +agamy +agana +agape +agars +agasp +agast +agata +agate +agaty +agave +agaze +agena +agend +agene +agent +agers +agete +agger +aggie +aggri +aggry +aggro +aggur +aghan +aghas +agiel +agile +aging +agios +agism +agist +aglee +agley +agler +aglet +aglow +agmas +agnat +agnel +agnes +agnew +agnus +agoge +agoho +agone +agony +agons +agora +agrah +agral +agram +agree +agria +agric +agrin +agrom +agron +agsam +aguey +agues +aguie +agung +agura +agush +agust +agway +ahead +aheap +ahems +ahern +ahind +ahint +ahira +ahmad +ahmar +ahmed +ahmet +ahoys +ahola +ahold +aholt +ahong +ahouh +ahron +ahsan +ahull +ahunt +ahura +ahush +ahvaz +ahwal +ahwaz +ayahs +ayala +aidan +aidde +aided +aiden +ayden +aider +aides +aidin +aydin +aidit +aidos +aieee +ayelp +ayens +aiery +ayers +aiger +aigre +ayina +ayins +aiken +ailed +ailee +ailey +aylet +ailie +ailin +ailyn +ailis +aillt +ayllu +ailsa +aimak +aimed +aimee +aimer +aymer +aimil +aynat +ainee +ainoi +aynor +ainus +aioli +ayond +ayont +ayous +airan +aired +airel +airer +aires +ayres +airla +airns +airth +airts +aisha +aisle +aisne +aitch +aitis +ayuyu +aiver +aiwan +aizle +ajaja +ajani +ajari +ajava +ajhar +ajiva +ajmer +ajuga +akaba +akala +akali +akasa +akbar +akebi +akees +akeki +akela +akene +akers +akyab +akiak +akiba +akili +aking +akins +akira +akita +akkad +akkra +aklog +aknee +aknow +akpek +akron +aksel +aksum +akule +akund +akure +alack +alada +alage +alain +alair +alake +alaki +alala +alamo +alana +aland +alane +alang +alani +alano +alans +alant +alapa +alard +alary +alarm +alate +alawi +albay +alban +albas +albee +alben +alber +albia +albie +albin +albyn +album +albur +albus +alcae +alces +alcid +alcis +alcoa +alcon +alcor +alcot +alcus +alday +aldan +aldas +aldea +alded +alden +alder +aldie +aldim +aldin +aldis +aldol +aldon +aldos +aldus +aleak +aleck +alecs +aleda +aledo +aleen +alefs +aleft +alejo +alena +alene +alenu +aleph +alert +aleta +aleus +aleut +alexa +alexi +alfas +alfeo +alfet +alfie +alfin +alfur +algae +algal +algar +algas +alger +algia +algic +algid +algie +algin +algol +algor +algum +alhet +alias +alibi +alica +alice +alyce +alick +alida +alyda +alids +alief +alien +aliet +alife +alifs +align +aliya +alika +alike +alima +alina +aline +alisa +alysa +alyse +alish +aliso +alisp +alyss +alist +alita +alite +ality +alius +alive +aliza +alkes +alkyd +alkyl +alkin +alkol +allah +allay +allan +alley +allen +aller +allez +allie +allyl +allin +allyn +allis +allys +allix +alloa +allod +alloy +alloo +allot +allow +almad +almah +alman +almas +almeh +almes +almon +almud +almug +alnus +alodi +alody +aloed +aloes +aloft +alogi +alogy +aloha +aloid +aloin +alois +aloys +aloke +aloma +alone +along +aloof +alope +alosa +alose +alost +aloud +alout +alowe +alpax +alpen +alper +alpha +alpid +alric +alroi +alroy +alrzc +alsea +alsey +alsen +alsip +alson +alsop +altaf +altai +altay +altar +alten +alter +altes +altha +altho +altin +altis +alton +altos +altro +altun +altus +aluco +aluin +alula +alums +alurd +alure +aluta +alvah +alvan +alvar +alver +alves +alvia +alvie +alvin +alvis +alvus +alway +alwin +alwyn +amaas +amacs +amadi +amado +amaga +amahs +amaya +amain +amala +amalg +amana +amand +amang +amani +amann +amant +amapa +amara +amarc +amari +amary +amasa +amase +amass +amata +amate +amati +amaty +amato +amaut +amaze +ambay +amban +ambar +ambas +amber +ambia +ambie +ambit +amble +amboy +ambon +ambos +ambry +ambur +ameba +ameds +ameed +ameen +ameer +amelu +amena +amend +amene +amens +ament +amery +amero +amess +amhar +amias +amyas +amice +amici +amick +amida +amide +amido +amids +amiel +amies +amiga +amigo +amylo +amyls +amine +amini +amino +amins +amire +amirs +amish +amiss +amita +amite +amity +amlet +amlin +amman +ammer +ammon +ammos +amnia +amnic +amoco +amoke +amoks +amole +among +amora +amorc +amory +amort +amour +amove +amowt +ampas +amper +ampex +amphi +ampyx +ample +amply +ampul +amram +amrit +amroc +amsat +amsel +amuck +amula +amund +amuse +amuze +amvet +amvis +amzel +anabo +anack +anama +anana +anand +anasa +anaxo +ancel +ancha +ancle +ancon +ancor +ancre +anded +andee +andel +ander +andes +andia +andie +andor +andra +andre +andri +andry +anear +anele +anend +anent +aneta +aneth +aneto +anett +angas +angel +anger +angia +angie +angil +angka +angle +anglo +angor +angry +angst +angus +anhyd +anyah +aniak +aniba +anica +anice +anigh +anile +anils +anima +anime +animi +animo +anion +anise +anita +anius +aniwa +anjan +anjou +ankee +anker +ankhs +ankle +ankou +ankus +anlas +anlet +anlia +anmia +annal +annam +annas +annat +annet +annex +annia +annie +annis +anniv +annoy +annot +annul +annum +annus +annwn +anoas +anode +anoia +anoil +anoka +anole +anoli +anomy +anora +anorn +anour +anous +anova +ansae +ansar +ansel +anser +anson +antae +antal +antar +antas +anted +antep +antes +anthe +antia +antic +antin +antiq +antis +anton +antra +antre +antsy +antum +anura +anury +anvik +anvil +anzac +anzio +anzus +aoede +aoide +aoife +aorta +aosta +aotea +aotes +aotus +aouad +apace +apaid +apair +apama +apart +apass +apast +apeak +apeek +apepi +apery +apers +apert +aperu +apfel +apgar +aphid +aphis +aphra +apian +apics +apiin +apili +apina +aping +apiol +apios +apish +apism +apium +apnea +apocr +apoda +apods +apoop +aport +apout +appay +appal +appar +appel +appet +appia +apple +apply +appmt +appro +apptd +appui +apres +april +apron +apses +apsid +apsis +aptal +apter +aptly +aptos +apure +aqaba +aquae +aquas +araba +arabi +araby +arabs +araca +arace +arach +arado +arage +arago +arain +arake +araks +aralu +aramu +arand +arany +arank +arara +araru +arase +arati +araua +arawa +arawn +araxa +arber +arbil +arbon +arbor +arcae +arcas +arced +arces +archd +arche +archy +archt +arcos +arcus +ardea +ardeb +ardel +arden +arder +ardie +ardin +ardis +ardys +ardme +ardor +ardra +ardri +aread +areae +areal +arean +arear +areas +areca +areek +areel +arefy +areic +arela +arena +arend +arene +areng +arent +arere +arest +areta +arete +areus +argal +argan +argas +argel +arges +argia +argid +argil +argin +argle +argol +argon +argos +argot +argue +argus +arhar +arhat +arhna +arian +aryan +arias +arica +arick +ariel +aries +ariew +ariki +arils +aryls +arimo +arioi +arion +ariot +arise +arish +arist +arita +arite +arith +arium +arius +arjay +arjan +arjun +arkab +arkie +arlan +arlee +arley +arlen +arles +arlie +arlin +arlyn +arlis +arlon +arman +armco +armed +armen +armer +armet +armil +armin +armyn +armit +armor +arndt +arneb +arnee +arney +arnel +arnie +arnim +arnon +arnot +arnst +arnut +aroar +arock +aroda +aroid +arola +aroma +arona +aroon +aroph +arose +arpen +arpin +arrah +array +arran +arras +arrau +arrey +arret +arrgt +arrha +arria +arrie +arrio +arris +arron +arrow +arroz +arses +arsyl +arsis +arsle +arson +artal +artar +artas +artcc +artel +arter +artha +artic +artie +artly +artou +artsy +artur +artus +aruac +aruba +aruke +arulo +arums +aruns +arupa +aruru +arusa +arvad +arval +arvel +arvid +arvie +arvin +arvol +arvos +arzan +arzun +asabi +asael +asahi +asale +asana +asaph +asare +asarh +asben +ascan +ascap +ascii +ascon +ascot +ascry +ascus +asdic +asdsp +asean +asgmt +ashab +ashby +ashed +ashen +asher +ashes +ashet +ashia +ashil +ashir +ashla +ashli +ashly +ashok +ashot +ashti +ashur +asian +aside +asyla +asyle +async +asine +asius +asyut +askar +asked +asker +askew +askip +askja +askoi +askos +askov +aslam +aslef +aslop +asoak +asoka +aspac +aspca +aspen +asper +aspia +aspic +aspis +asroc +assad +assai +assay +assam +assen +asser +asses +asset +assyr +assis +assoc +assot +assur +astay +astel +aster +astir +astms +aston +astor +astra +astre +astri +astto +astur +asura +asuri +asway +aswan +aswim +atacc +atake +atame +ataps +atavi +ataxy +atdrs +ateba +atees +ately +atelo +athal +athar +athey +athel +athie +athol +athos +atila +atile +atilt +atimy +ating +ation +atypy +ative +atlas +atlee +atman +atmas +atmid +atmos +atnah +atoka +atoke +atole +atoll +atomy +atoms +atone +atony +atopy +atory +atour +atpco +atrax +atren +atria +atrip +attah +attal +attar +atter +attic +attid +attis +attle +attry +atule +atune +atwin +auber +aubin +aubyn +aubry +aucan +aucht +audad +auden +audie +audio +audit +audix +audly +audra +audre +audri +audry +audun +aueto +augen +auger +auget +aught +augie +augur +aulae +aulas +aulea +aulic +aulis +auloi +aulos +aumil +aunty +aunts +aurae +aural +aurar +auras +aurea +aurei +aurel +aures +auria +auric +aurie +auryl +aurin +aurir +auris +aurum +autem +autor +autos +autre +autry +autum +autun +auvil +auxil +auxin +avahi +avail +avale +avant +avars +avast +avell +avena +aveny +avens +avera +averi +avery +avern +avers +avert +avgas +avian +avice +avick +aview +avila +avile +avine +avion +aviso +aviva +avlis +avner +avoca +avoid +avoir +avoke +avoue +avour +avowe +avows +avram +avril +avrit +avrom +avron +awabi +awacs +awaft +aways +await +awake +awald +awalt +awane +award +aware +awarn +awash +awave +awber +aweek +aweel +awest +aweto +awful +awhet +awhir +awide +awing +awink +awiwi +awkly +awned +awner +awoke +awols +awork +axels +axers +axial +axile +axils +axine +axing +axiom +axion +axite +axled +axles +axman +axmen +axoid +axone +axons +axson +axtel +axton +azana +azans +azide +azido +azyme +azine +aziza +azlon +azoch +azofy +azoic +azole +azons +azote +azoth +azoxy +azral +aztec +azure +azury +azusa +baaed +baals +babai +babar +babas +babby +babel +baber +babes +babis +babka +bable +babol +baboo +babua +babul +babur +babus +bacao +bacau +bacca +baccy +bache +bacin +bacis +backy +backs +bacon +badan +baddy +baden +badge +badin +badju +badly +badon +baeda +baerl +baese +baffy +baffs +bafta +bagdi +bagel +bagge +baggy +baggs +bagie +bagio +bagle +bagne +bagre +bagsc +bahai +bahay +baham +bahan +bahar +bahia +bahoe +bahoo +bahts +bahur +bahut +baiae +bayal +bayam +bayar +bayda +bayed +baiel +bayer +baign +baile +bayle +baily +bayly +bailo +bails +bains +baioc +bayok +bayou +baird +bairn +baiss +baith +baits +baiza +baize +bajaj +bajan +bajau +bajer +bajra +bajri +bakal +baked +baken +baker +bakes +bakie +bakke +bakki +bakli +bakra +bakst +balac +balai +balak +balan +balao +balas +balat +balau +balbo +balch +baldy +baldr +balds +baled +balei +baler +bales +balkh +balky +balko +balks +balla +balli +bally +ballo +balls +balmy +balms +balon +baloo +balor +balow +balpa +balsa +balta +balti +balun +balut +balza +bamaf +bamah +bambi +bamby +banak +banal +banat +banba +banca +banco +bancs +banda +bande +bandh +bandi +bandy +bando +bands +baned +banes +banff +banga +bange +bangy +bangs +bania +banya +banig +banjo +banka +banky +banks +banna +banns +banon +banty +bantu +banus +bapco +bapct +barac +barad +baray +barak +baram +baras +barat +barba +barbe +barbi +barby +barbs +barbu +barca +barce +barch +barco +barde +bardy +bardo +bards +barea +bared +barer +bares +baret +barff +barfy +barfs +barge +bargh +baria +baric +barid +barie +barye +barih +baris +barit +barky +barks +barly +barmy +barms +barna +barny +barns +baroi +baron +barra +barre +barri +barry +barse +barta +barth +barty +barto +basad +basal +basan +basat +basco +based +basel +baser +bases +basho +basia +basic +basie +basye +basil +basyl +basin +basir +basis +baske +basks +basle +basom +bason +basos +basov +basra +bassa +bassi +bassy +basso +basta +baste +basti +basto +basts +batad +batak +batan +batch +batea +bated +batel +baten +bater +bates +batha +bathe +baths +batia +batik +batis +batna +baton +bator +batse +batta +batty +batts +battu +batum +batwa +baubo +bauch +bauds +bauer +bauge +baugh +bauld +baulk +baume +bauno +baure +bauru +bauta +bavin +bavon +bawdy +bawds +bawke +bawly +bawls +bawra +bawty +baxie +bazar +bazil +bazin +bazoo +bbxrt +bcdic +bcere +bchar +beach +beady +beads +beaky +beaks +beala +beale +beall +beals +beamy +beams +beane +beany +beano +beans +beant +beard +beare +bearm +bears +beast +beata +beath +beati +beats +beaus +beaut +beaux +bebay +bebar +bebat +bebed +bebel +bebog +bebop +becap +becca +becco +beche +becht +becka +becki +becky +becks +becry +becut +bedad +beday +bedel +beden +bedew +bedye +bedim +bedin +bedip +bedog +bedot +bedub +bedur +beebe +beech +beeck +beedi +beefy +beefs +beele +beent +beeps +beera +beery +beers +beest +beeth +beety +beets +beeve +befan +befit +befog +befop +befur +begad +begay +began +begar +begat +begem +beget +begga +beggs +begin +begob +begod +begot +begum +begun +begut +behah +behan +behap +behar +behav +behen +behew +behka +behre +beica +beice +beyer +beige +beigy +beild +beyle +being +beira +beisa +beitz +bejan +bejel +bejig +bejou +bekaa +bekah +beker +bekha +bekki +bekko +belah +belay +belak +belam +belap +belar +belat +belch +belda +belee +belem +belen +belga +belia +belie +belis +bella +belle +belli +belly +bello +bells +below +belts +beltu +belue +belus +belva +belve +bemad +beman +bemar +bemas +bemat +bemba +bemis +bemix +bemol +bemud +benab +bench +benco +benda +bendy +bends +benes +benet +benge +bengt +benia +benic +benil +benim +benin +benis +benji +benjy +benld +benne +benni +benny +bennu +bensh +benty +bents +benue +benzo +beode +beora +beore +bepat +bepaw +bepen +bepun +beqaa +beray +berar +berat +bercy +berck +berea +berey +beret +berga +bergh +bergy +bergs +beria +beryl +berio +berit +beryx +berke +berky +berks +berme +berms +berna +berne +berni +berny +bernj +berns +bernt +berob +beroe +berra +berri +berry +berta +berte +berth +berti +berty +berun +besan +besee +beset +besew +besht +besin +besit +besom +besot +bespy +besra +besse +bessi +bessy +bests +betag +betas +betel +betes +bethe +beths +betis +beton +betrs +betsi +betsy +betso +betta +bette +betti +betty +bevan +bevel +bever +bevil +bevin +bevis +bevon +bevor +bevue +bevus +bevvy +bewet +bewig +bewit +bewry +bexar +bezae +bezan +bezel +bezil +bezzi +bezzo +bhaga +bhalu +bhang +bhara +bhatt +bhava +bhave +bhili +bhima +bhola +bhoot +bhuts +biabo +biagi +biali +bialy +byard +byars +bibby +bibbs +bybee +bibio +bible +bicep +bices +bichy +bicol +bidar +biddy +bided +bider +bides +bidet +bidle +bidri +bidry +biela +bield +biens +biers +byers +bifer +biffy +biffs +bifid +bigae +bigam +bigas +biggy +biggs +bigha +bight +bigly +bigod +bigot +bihai +biham +bihar +biisk +biysk +bijou +biked +biker +bikes +bikie +bikol +bilac +bylas +bylaw +bilbe +bilbi +bilby +bilbo +bilch +bilek +byler +biles +bilge +bilgy +bilic +bilin +bilio +bilks +billa +bille +billi +billy +bills +bilos +bilow +bilsh +bimah +bimas +bimbo +binah +binal +bindi +binds +bines +binet +binge +bingy +bingo +bynin +binit +binky +binna +binni +binny +bints +bynum +biola +biome +biont +biose +biota +byous +biped +bipod +bypro +byram +byran +birch +birck +birde +birdy +birds +birdt +byres +birky +birks +birle +byrle +birls +byrls +birma +birne +byrne +birny +byrom +biron +byron +byrrh +birri +byrri +birrs +birse +birsy +birth +bisdn +bysen +bises +biset +bisie +bisks +bisme +bison +byssi +bisso +bisti +bitch +bited +biter +bites +bytes +bitis +bytom +biton +bitsy +bitte +bitty +bitto +bitts +biune +bivvy +byway +bixby +bixin +byzas +bizel +bizen +bizes +bizet +bjart +bjork +bjorn +blabs +black +blade +blady +blaeu +blaew +blaff +blagg +blahs +blayk +blain +blair +blake +blame +blams +blanc +bland +blane +blank +blare +blart +blase +blash +blast +blate +blats +blatt +blatz +blawn +blaws +blaze +blazy +bldge +bleak +blear +bleat +blebs +bleck +bleed +bleep +blend +blenk +blens +blent +blere +blert +bless +blest +blets +blibe +blick +blida +blier +bligh +blimy +blimp +blind +blini +bliny +blink +blinn +blynn +blype +blips +blirt +bliss +blist +blite +blyth +blitt +blitz +blizz +bloat +blobs +bloch +block +blocs +bloem +blois +bloke +blond +blood +bloom +bloop +blore +blote +blots +blout +blowy +blown +blows +blued +bluey +bluer +blues +bluet +bluff +bluhm +bluma +blume +blunk +blunt +blurb +blurs +blurt +blush +bmare +bmete +bmews +bmgte +board +boars +boart +boast +boats +bobac +bobbe +bobbi +bobby +bobet +bobol +bocal +bocca +bocce +bocci +boche +bocks +bocoy +boded +boden +boder +bodes +bodge +bodhi +bodle +boece +boeke +boers +boffa +boffo +boffs +bogan +bogey +boget +boggy +boggs +bogie +bogys +bogle +bogor +bogot +bogue +bogum +bogus +bohea +bohme +bohol +bohon +bohor +bohun +boyar +boyau +boice +boyce +boyds +boyer +boyes +boiko +boyla +boyle +boily +boils +boyne +boing +boyos +boise +boyse +boist +boite +boito +bojer +bokom +bokos +bolag +bolan +bolar +bolas +boldo +boldu +boled +boley +bolen +boles +bolis +bolly +bolls +bolme +bolos +bolte +bolti +bolty +bolts +bolus +bombe +bombo +bombs +bomke +bomos +bonar +bonav +bonbo +bonce +bondy +bonds +boned +boney +boner +bones +bongo +bongs +bonis +bonks +bonne +bonni +bonny +bonns +bonos +bonpa +bonum +bonus +bonze +booby +boobs +boodh +boody +booed +boogy +booky +books +boole +booly +booma +boomy +booms +boone +boong +boony +boonk +boons +boors +boort +boose +boosy +boost +boote +booth +booty +boots +booze +boozy +borah +borak +boral +boran +boras +borax +borda +bordy +bored +boree +borek +borel +borer +bores +borgh +boric +borid +boryl +boris +borne +bornu +boron +borty +borts +bortz +borup +bosch +bosey +boser +bosix +bosky +bosks +bosom +boson +bossa +bossy +bosun +botan +botas +botch +botel +botes +botha +bothe +bothy +botry +botte +botti +botts +bottu +bouak +bouar +bouch +boucl +bouet +bouge +bough +boule +boult +bound +bourd +bourg +bourn +bourr +bouse +bousy +bouto +bouts +bovey +bovet +bovid +bovld +bowed +bowel +bowen +bower +bowes +bowet +bowge +bowie +bowla +bowle +bowly +bowls +bowne +bowra +bowse +boxed +boxen +boxer +boxes +boxty +bozal +bozen +bozoo +bozos +bozze +bpdpa +bpete +bphil +braca +brace +brach +brack +bract +brade +brady +brads +braes +braga +brage +bragg +bragi +brags +brahe +brahm +braid +braye +brail +brain +brays +brake +braky +brale +brame +brana +brand +brank +brans +brant +brash +brass +brast +brats +braun +brava +brave +bravi +bravo +brawl +brawn +braws +braxy +braza +braze +bread +break +bream +brear +breba +breck +breda +brede +bredi +breed +breek +breen +brees +breme +brena +brenk +brenn +brens +brent +brerd +brere +bress +brest +breth +brett +bretz +breva +breve +brevi +brews +brian +bryan +briar +bribe +brice +bryce +brick +bride +brief +brien +brier +bries +brigg +brigs +brike +brill +brimo +brims +brina +bryna +brine +bring +briny +brink +brinn +brynn +brins +brion +bryon +brios +brisa +brise +brisk +briss +brist +brita +brite +brith +brits +britt +bryum +briza +brize +brizo +brizz +broad +broca +broch +brock +brody +broek +brogh +broid +broil +broke +broll +broma +brome +bromo +bronc +bronk +bront +bronx +brood +brook +brool +broom +broon +broos +brose +brosy +brost +brote +broth +brott +broun +brout +browd +brown +brows +brubu +bruce +bruch +bruet +brugh +bruhn +bruin +bruyn +bruis +bruit +bruja +brujo +bruke +brule +brume +brune +bruni +brunk +brunn +bruno +bruns +brunt +brusa +brush +brusk +bruta +brute +bruzz +bsadv +bsaee +bsage +bsagr +bsbus +bsche +bscom +bsdes +bsele +bseng +bsgph +bshec +bshed +bshyg +bsmet +bsmin +bsmtp +bsphn +bsrec +bsret +bsrfs +bstie +btise +buaer +buaze +bubal +bubas +bubba +bubby +buber +bubos +bucca +bucco +buchu +bucky +bucko +bucks +bucku +budde +buddh +buddy +budge +budgy +buell +buena +bueno +buffa +buffe +buffi +buffy +buffo +buffs +bugan +bugas +buggy +bught +bugle +bugre +buhls +buhrs +buick +buyer +build +built +buine +buyse +buist +bukat +bukum +bulak +bulan +bulby +bulbs +bulge +bulgy +bulky +bulks +bulla +bully +bulls +bulow +bulse +bumbo +bumfs +bumph +bumpy +bumps +bunce +bunch +bunco +bunda +bunde +bundh +bundy +bunds +bundt +bundu +bunga +bungy +bungo +bungs +bunia +bunya +bunin +bunky +bunko +bunks +bunni +bunny +bunns +bunow +bunty +bunts +bunus +buoys +buote +buran +burao +buraq +buras +burbs +burch +burck +burds +burel +buren +buret +burez +burga +burge +burgh +burgs +burin +burys +burka +burke +burly +burls +burma +burna +burne +burny +burns +burnt +buroo +burps +burra +burry +burro +burrs +bursa +burse +burst +burta +burty +burtt +burut +busby +busch +bused +busey +buses +bushi +bushy +busky +busks +busra +bussy +bussu +busti +busty +busto +busts +butat +butch +butea +buteo +butes +butic +butyl +butin +butyn +butyr +butle +butsu +butta +butte +butty +butts +butut +buxom +buxus +buzzy +bwana +caaba +caama +cabaa +cabal +caban +cabas +cabby +cabda +caber +cabet +cabin +cabio +cable +cabob +cabot +cabre +cacak +cacam +cacan +cacao +cacas +cacei +cache +cacia +cacie +cacka +cacks +cacti +cacur +cacus +cadal +caddy +caddo +cadee +cadel +cader +cades +cadet +cadew +cadge +cadgy +cadie +cadis +cadiz +cados +cadre +cadua +cadus +caeca +caeli +cafes +caffa +cafiz +cafoy +caged +cagey +cager +cages +caggy +cagit +cagle +cagot +cagui +cahan +cahiz +cahot +cahow +cahra +cahuy +cayce +caids +cayey +cayes +cayla +caine +cains +cayos +caird +cairn +cairo +caite +caius +cajan +cajon +cajou +cajun +caked +cakey +caker +cakes +cakra +calah +calan +calas +calci +caleb +calef +calen +calfs +calia +calic +calid +calie +calif +calin +calio +calix +calyx +calks +calla +calle +calli +cally +callo +calls +calmy +calms +calor +calpe +calrs +calva +calve +calvo +camac +camag +camay +camak +caman +camas +camby +cambs +camey +camel +cameo +cames +camis +camla +cammi +cammy +campa +campe +campi +campy +campo +camps +camra +camuy +camus +canad +canal +canap +canby +canch +candi +candy +cando +canea +caned +caney +canel +caner +canes +cangy +canid +canis +canli +canna +canny +canoe +canon +canos +canso +canst +canty +canto +cants +cantu +canun +canzo +caoba +capac +capax +caped +capek +capel +caper +capes +capet +caphs +capys +capiz +capoc +capon +capos +capot +cappy +capps +capra +capri +capsa +capua +caput +caque +carap +caras +carat +caraz +carby +carbo +carbs +carce +cardo +cards +cared +carey +carel +caren +carer +cares +caret +carew +carex +carga +cargo +caria +carya +carib +carid +carie +caril +caryl +carin +caryn +carks +carla +carle +carli +carly +carlo +carls +carma +carme +carmi +carne +carny +carns +caroa +carob +carol +carom +caron +carot +carpe +carpi +carpo +carps +carri +carry +carrs +carse +carte +carty +carts +carua +carum +carus +carve +carvy +casal +casar +casas +casco +cased +casey +casel +caser +cases +casha +casia +casie +casky +casks +cason +casse +cassi +cassy +casta +caste +casts +casus +catan +catch +catel +cater +cates +catha +cathe +cathi +cathy +catie +catis +caton +catso +catti +catty +catto +catur +cauca +cauch +cauda +cauld +cauli +caulk +cauls +cauma +caupo +causa +cause +cavae +caval +cavan +cavea +caved +cavey +cavel +caver +caves +cavia +cavie +cavil +cavin +cavit +cavum +cavus +cawed +cawky +cawny +caxon +cbema +ccafs +ccccm +ccitt +ccoya +cctac +ccuta +cdiac +cdoba +cdrom +ceara +cease +cebid +cebil +cebur +cebus +cecal +cecca +cechy +cecil +cecum +cedar +ceded +ceder +cedes +cedis +cedre +cedry +ceert +cefis +ceiba +ceibo +ceile +ceils +ceint +celeb +celia +celie +celik +celin +celio +celka +cella +celle +celli +cello +cells +celom +celss +celts +cemal +cenac +cence +cenci +cenis +cense +centi +cento +cents +ceorl +cepes +cequi +ceral +ceram +ceras +cerat +cerci +cered +cerer +ceres +ceria +ceric +ceryl +cerin +cerys +ceryx +ceros +cerro +certy +cesar +cesya +cesta +ceste +cesti +cetes +cetic +cetid +cetyl +cetin +cetus +ceuta +cgiar +chace +chack +chaco +chadd +chads +chafe +chaff +chaft +chaga +chaya +chaim +chain +chair +chais +chays +chait +chaja +chaka +chald +chalk +chama +chamm +champ +chams +chana +chanc +chane +chang +chany +chank +chant +chaon +chaos +chapa +chape +chaps +chapt +chara +chard +chare +chari +chary +chark +charm +charo +charr +chars +chart +chase +chasm +chass +chati +chats +chaui +chauk +chaum +chaus +chave +chawk +chawl +chawn +chaws +chazy +cheam +cheap +cheat +check +cheek +cheep +cheer +cheet +chefs +chego +cheir +cheju +cheka +cheke +cheki +chela +chelp +chema +cheme +chena +cheng +chera +chere +cheri +chery +chert +chese +chess +chest +cheth +cheux +cheve +chevy +chewa +chewy +chews +chyak +chiam +chian +chiao +chias +chiba +chica +chich +chick +chico +chics +chide +chief +chiel +chien +child +chile +chyle +chili +chill +chilo +chilt +chimb +chime +chyme +chimp +chimu +china +chine +ching +chink +chino +chins +chint +chios +chiot +chiou +chyou +chips +chirk +chirl +chirm +chiro +chirp +chirr +chirt +chiru +chita +chits +chive +chivy +chivw +chizz +chloe +chlor +choak +choca +chock +choco +choel +choes +choga +choya +choil +choir +choke +choky +choko +chola +chold +choli +cholo +chomp +chong +chonk +chook +choom +choop +chopa +chops +chora +chord +chore +chort +chorz +chose +chosn +chots +chott +choup +chous +chout +choux +chowk +chows +chria +chris +chron +chuah +chubb +chubs +chuch +chuck +chude +chuet +chufa +chuff +chugs +chuje +chula +chump +chums +chung +chunk +chura +churl +churm +churn +churr +chuse +chute +chwas +cyane +ciano +cyano +cyans +cyath +cybil +cibis +cibol +cicad +cycad +cycas +cicer +cycle +ciclo +cyclo +cidal +cider +cyder +cidin +cydon +cidra +cigar +cygni +cigua +cyler +cilia +cilix +cylix +cilka +cilla +cymae +cimah +cymar +cymas +cymba +cymes +cimex +cymol +cimon +cymry +cynar +cinch +cinct +cinda +cynde +cindi +cindy +cyndi +cyndy +cinel +cines +cynic +cinna +cynth +cions +cippi +cypre +cypro +cipus +circa +circe +circs +cires +cyrie +cyril +cirri +cyrus +cisco +cisne +cissy +cista +cists +cysts +cital +cited +citee +citer +cites +cytol +cyton +citra +citua +civet +civia +civic +civie +civil +civvy +cizar +clabo +clach +clack +clade +clads +claes +clags +claye +claik +claim +clair +clays +clake +clamb +clame +clamp +clams +clang +clank +clans +clape +clapp +claps +clapt +clara +clare +clari +clary +clark +claro +clart +clase +clash +clasp +class +clast +claud +claus +claut +clava +clave +clavi +clavy +clawk +claws +clead +cleam +clean +clear +cleat +cleck +cleek +clefs +cleft +clein +clela +cleon +clepe +clept +clerc +clere +clerk +cleta +clete +cleti +cleuk +cleva +cleve +clews +clich +click +clide +clyde +clyer +cliff +clift +clima +climb +clime +cline +cling +clink +clint +clype +clips +clipt +clite +clyte +clive +clyve +clywd +cloak +cloam +clock +clods +cloes +cloff +clogs +clois +cloys +cloit +cloke +cloky +clomb +clomp +clone +clong +clonk +clons +cloof +cloop +cloot +clops +close +closh +clote +cloth +clots +cloud +clour +clout +clova +clove +clown +cloze +clubb +clubs +cluck +clued +clues +cluff +clump +clune +clung +cluny +clunk +clute +clwyd +cmise +cmsgt +cnida +coach +coact +coady +coaid +coala +coaly +coals +coamo +coapt +coarb +coart +coast +coati +coats +coaxy +coban +cobby +cobbs +coben +cobia +coble +cobol +cobra +cobus +cocao +cocas +cocci +cocco +cocin +cocke +cocky +cocks +cocle +cocoa +cocom +cocos +cocot +cocus +codal +codas +codcf +coddy +codec +coded +codee +codel +coden +coder +codes +codex +codie +codol +codon +coeds +coees +coeff +coele +coeno +coeus +coffs +cogan +cogen +cogie +cogit +cogon +cogue +cohan +cohby +cohen +cohin +cohla +cohob +cohog +cohol +cohos +cohow +cohue +coyan +coyed +coyer +coifs +coign +coila +coyle +coyly +coils +coing +coiny +coins +coyol +coyos +coypu +coire +coirs +coked +cokey +coker +cokes +cokie +colan +colas +colat +colby +colds +coled +coley +colen +coles +colet +colic +colin +colis +colla +colly +colob +colog +colon +color +colts +colum +colza +comae +comal +coman +comas +combe +comby +combo +combs +comdg +comdr +comdt +comer +comes +comet +comfy +comic +comid +comix +comma +comme +commy +commo +comox +compd +compi +compo +comps +compt +comte +comus +conah +conal +conan +conch +concn +conda +conde +condo +coned +coney +coner +cones +confr +conga +conge +congo +conia +conic +conin +conky +conks +conli +conni +conny +conns +connu +conoy +conon +conor +consy +const +contd +conte +contg +conti +conto +contr +conus +cooba +cooch +cooed +cooee +cooey +cooer +coofs +cooja +cooke +cooky +cooks +cooly +cools +coomb +coomy +coony +coons +coops +coopt +coorg +coors +coosa +coost +cooth +cooty +coots +copal +copan +coped +copei +copen +coper +copes +copht +copia +copis +coppa +coppy +copps +copra +copse +copsy +copus +coque +corah +coray +coral +coram +coran +corbe +corby +cordi +cordy +cords +corea +cored +coree +corey +corel +corer +cores +corfu +corge +corgi +coria +corie +coryl +corin +corke +corky +corks +corly +corms +corny +corno +corns +cornu +coroa +corol +corot +corpl +corpn +corps +corri +corry +corse +corsy +corso +corta +corti +corty +corum +corve +corvi +corvo +cosby +cosec +cosed +cosey +cosen +coses +coset +cosie +cosin +cosma +cosme +cosmo +cosse +costa +costs +cotan +cotch +coted +cotes +cothe +cothy +cotyl +cotys +cotta +cotte +cotty +couac +couch +coude +cough +could +couma +count +coupe +coups +courb +cours +court +cousy +couth +couve +coved +covey +covel +coven +cover +coves +covet +covid +covin +cowal +cowan +cowed +cowey +cowen +cower +cowes +cowie +cowle +cowls +cowry +coxae +coxal +coxed +coxey +coxes +cozad +cozed +cozey +cozen +cozes +cozie +cozmo +cozza +craal +crabb +crabs +cracy +crack +craft +crags +craie +craye +craig +craik +crain +crake +cralg +cramp +crams +crane +crang +crany +crank +crape +crapy +crapo +craps +crare +crary +crash +crass +crate +crave +cravo +crawl +crawm +craws +craze +crazy +crcao +crche +cread +creak +cream +crean +creat +crecy +creda +credo +creed +creek +creel +creem +creen +creep +crees +creil +creme +crena +creon +crepe +crepy +crept +cresa +cresc +cress +crest +creta +crete +crewe +crews +crfmp +cryal +cryan +cribo +cribs +crick +cried +criey +crier +cries +crile +crime +crimp +crine +crink +cripe +crips +crypt +crisp +criss +crist +cryst +crith +critz +crius +croak +croat +croce +croci +crock +crocs +croft +croyl +crois +croix +crome +crone +crony +cronk +crood +crook +crool +croom +croon +crops +crore +crosa +crose +cross +crost +croup +crout +crowd +crowe +crowl +crown +crows +croze +crres +crsab +cruce +cruck +crude +crudy +cruds +cruel +cruet +crull +crumb +crump +crunk +crunt +cruor +crura +cruse +crush +crust +cruth +crwth +csacc +csacs +csect +csiro +csnet +cspan +ctene +cterm +ctimo +cuban +cubas +cubby +cubeb +cubed +cuber +cubes +cubic +cubit +cubla +cubti +cucuy +cuddy +cueca +cuero +cueva +cuffy +cuffs +cufic +cuyab +cuyas +cuifs +cuing +cuish +cujam +cukes +cukor +culch +culet +culex +culla +cully +culls +culmy +culms +culot +culpa +culti +cults +culus +cumae +cumay +cumal +cuman +cumar +cumby +cumbu +cumic +cumyl +cumin +cumly +cumol +cunan +cunas +cundy +cunea +cunei +cuney +cuneo +cunye +cunit +cunni +cunny +cunts +cunza +cupay +cupel +cupid +cuppa +cuppy +curat +curby +curbs +curch +curdy +curds +cured +curer +cures +curet +curfs +curia +curie +curin +curio +curly +curls +curns +curry +currs +cursa +curse +curst +curua +curve +curvy +cusco +cusec +cushy +cusie +cusks +cusps +cusso +cutch +cutey +cuter +cutes +cutie +cutin +cutis +cutty +cutup +cuvee +cuzco +cwlth +czars +czech +dabba +dabby +dabbs +dabih +dabuh +dacca +daccs +dacey +daces +dacha +dache +dachi +dachy +dachs +dacia +dacie +dacko +dacus +dadap +dadas +daddy +dados +daeva +daffi +daffy +daffs +dafla +dafna +dagan +dagda +dagga +daggy +dagna +dagny +dagon +dagos +dahle +dahls +dahms +dahna +dayak +dayal +dayan +daijo +daile +dayle +daily +dayna +daint +daira +dairi +dairy +dairt +daisi +daisy +daiva +dakar +daker +dakir +dalai +dalan +dalar +dalat +dalbo +dalea +daley +dalen +daler +dales +dalia +dalis +dalle +dalli +dally +dalny +dalpe +damal +daman +damar +damas +damek +dames +damia +damie +damle +damme +damns +damon +dampy +damps +danae +danai +danas +danby +dance +dancy +danda +dandy +daney +danes +dangs +dania +danya +danic +danie +danio +danit +danke +danli +danna +danni +danny +dansy +dansk +danta +dante +darac +daraf +darat +darby +darbs +darce +darci +darcy +darda +dared +daren +darer +dares +dargo +daria +darya +daric +darii +daryl +darin +daryn +dario +darky +darks +darla +darns +daron +daroo +darpa +darra +darry +darst +darts +dasha +dashi +dashy +dasht +dasya +dasie +dasnt +dassy +datch +dated +dater +dates +datha +datil +datos +datsw +datto +datuk +datum +daube +dauby +daubs +dauke +dault +daune +daunt +dauri +dauts +davao +davey +daven +daver +david +davie +davin +davis +davys +davit +davon +davos +dawdy +dawed +dawen +dawes +dawks +dawna +dawny +dawns +dawts +dawut +dazed +dazey +dazes +dbrad +dcpsk +ddcmp +ddene +ddpex +deach +deady +deads +deair +deale +deals +dealt +deana +deane +deans +deare +deary +dearn +dearr +dears +deash +death +deave +debag +debar +debat +debbi +debby +debee +debel +deben +debes +debye +debir +debit +debna +debor +debra +debts +debug +debus +debut +decad +decaf +decay +decal +decan +decap +decca +decem +decil +decyl +decke +decks +declo +decoy +decor +decos +decry +decus +dedal +dedan +deddy +deden +dedie +dedit +dedra +deedy +deeds +deeyn +deems +deena +deeny +deeps +deere +deery +deers +deess +deeth +deets +defat +defer +defet +defis +defix +defoe +defog +degas +degum +dehue +deice +deify +deign +deils +deina +deink +deino +deynt +deion +deism +deist +deity +deked +dekes +dekko +dekle +dekow +delay +delaw +delco +deled +deles +delfs +delft +delhi +delia +delim +delis +delit +della +delle +delly +dells +delma +delni +deloo +delos +delph +delqa +delta +delua +deluc +delve +demal +demes +demit +demmy +demob +demon +demos +demot +dempr +demur +demus +denae +denay +denar +denat +denby +denbo +denda +deneb +denes +denie +denim +denio +denis +denys +denna +denni +denny +denom +dense +denty +dents +deota +depas +depca +depel +depew +depit +depoh +depoy +depot +deppy +depth +depue +depuy +deqna +derah +deray +derat +derby +dercy +derek +derep +deric +deryl +deriv +derma +derms +derna +derog +deron +deroo +derri +derry +derte +derth +derve +desai +desde +desex +desha +desyl +desks +desma +despr +dessa +desta +deste +desto +detar +detat +detax +deter +detin +detox +detta +dette +detur +deuce +deuna +deuno +deval +devan +devas +devel +dever +devex +devil +devin +devol +devon +devot +devow +dewal +dewan +dewar +dewax +dewed +dewey +dewer +dewie +dexec +dexes +dexie +dhabb +dhabi +dhaka +dhaks +dhals +dhava +dheri +dhyal +dhikr +dhlos +dhobi +dhoby +dhole +dhoni +dhoon +dhoti +dhoty +dhoul +dhows +dhruv +dhuti +diact +dyads +diaka +dials +diamb +diana +dyana +diane +dyane +diann +dyann +diary +dyaus +diazo +dibai +dibri +dibru +diced +dicey +dicer +dices +dyche +dichy +dicht +dicky +dicks +dicot +dicta +dicty +didal +diddy +didie +didym +didle +didna +didnt +didos +didst +didus +diego +diehl +diels +dielu +diena +diene +dieri +dyers +dyess +diety +diets +dietz +difda +dyfed +dygal +digby +diggs +dight +digit +digne +digor +digue +dying +dijon +diked +dyked +dikey +dykey +diker +dyker +dikes +dykes +dilan +dylan +dildo +diley +dilis +dilks +dille +dilli +dilly +dills +dilog +dilos +dymas +dimer +dimes +dimin +dimit +dimly +dimmy +dimna +dimps +dinah +dynah +dynam +dinan +dinar +dined +dynel +diner +dines +dynes +dinge +dingy +dingo +dings +dinic +dinin +dinka +dinky +dinks +dinny +dinos +dinse +dints +dinus +diode +diols +dione +dioon +diose +diota +dioti +dioxy +diple +dippy +dipsy +dipso +dipus +dirac +dirae +dirca +dirck +direr +direx +dirge +dirgy +dirks +dirls +dirty +dirts +disci +disco +discs +dishy +disko +disks +disli +disme +disna +dyson +disty +distn +distr +dital +ditas +ditch +diter +dites +ditsy +ditty +ditto +ditzy +dyula +diurn +divan +divas +dived +divel +diver +dives +divet +divia +divid +divot +divus +divvy +diwan +dixie +dixil +dixit +dixon +dizen +dizzy +djaja +djave +djinn +djins +djuka +dlitt +dlupg +dncri +dnepr +dnitz +doand +doane +doaty +doats +dobby +dobbs +dobie +dobla +doble +dobos +dobra +dobro +docia +docks +doddy +dodds +dodge +dodgy +dodie +dodos +doers +doesn +doest +doeth +doffs +dogal +dogey +doges +doggy +doggo +dogie +dogly +dogma +dogra +dogue +doyen +doigt +doyle +doily +doyly +doylt +doina +doing +doyon +doisy +doyst +doits +dojos +dolan +dolby +dolce +dolci +doled +doley +doles +dolia +dolin +dolli +dolly +dolls +dolma +dolon +dolor +dolos +dolph +dolts +dolus +domal +domba +domed +domel +domer +domes +domic +domph +dompt +domus +donal +donar +donas +donat +donau +donax +doncy +dondi +donec +donee +doney +doner +donet +donga +dongs +donia +donie +donis +donna +donne +donni +donny +donor +donsy +donum +donus +donut +dooja +doole +dooli +dooly +dooms +doone +doorn +doors +doozy +dopas +doped +dopey +doper +dopes +dorab +dorad +doray +doran +dorca +dorcy +doree +dorey +doria +doric +dorie +dorin +doris +dorje +dorky +dorks +dormy +dorms +dorps +dorri +dorry +dorrs +dorsa +dorse +dorsi +dorsy +dorty +dorts +dorus +dosed +doser +doses +dosia +dosis +dossy +dotal +doted +doter +dotes +dotti +dotty +douai +douay +douar +doubs +doubt +douce +douds +dough +dougy +dougl +douma +doums +doura +douro +douse +douty +dovap +dovey +doven +dover +doves +dovev +dowdy +dowed +dowel +dower +dowie +dowly +downe +downy +downs +dowry +dowse +dowve +doxia +doxie +dozed +dozen +dozer +dozes +dpans +dphil +dpnph +draba +drabs +draco +draff +draft +drago +drags +drail +drain +drais +drays +drake +drama +drame +dramm +drams +drang +drank +drant +drape +drate +drats +drava +drave +drawk +drawl +drawn +draws +dread +dream +drear +dreck +dreda +dredi +dreed +dreep +drees +dregs +dreks +dreng +drent +dress +drest +drews +dryad +drias +dryas +dribs +drice +drida +dried +drier +dryer +dries +drift +drily +dryly +drill +drina +drink +drinn +drips +dript +drisk +dryth +drive +drogh +droil +droyl +droit +droll +drome +drona +drone +drony +droob +drool +droop +drops +dropt +dross +droud +drouk +drove +drovy +drown +drubs +druce +druci +drucy +drugi +drugs +druid +drums +drung +drunk +drunt +drupa +drupe +drury +druse +drusi +drusy +druxy +druze +dsbam +dsect +dtset +duads +duala +duali +duals +duane +duant +duats +dubai +dubba +dubby +dubbo +dubhe +dubio +dubna +dubre +ducal +ducan +ducat +duces +duchy +ducky +ducks +ducor +ducts +duddy +duded +dudes +duels +duena +duero +duets +dufay +duffy +duffs +dufur +dugal +dugan +dugas +duhat +duyne +duits +dujan +dukas +dukey +dukes +dukhn +dukie +dulac +dulat +dulce +dulci +dulcy +duler +dulia +dulla +dully +dulls +dulse +dumah +dumas +dumba +dumby +dumbs +dumka +dumky +dummy +dumpy +dumps +dunaj +dunal +dunam +dunce +dunch +dunes +dungy +dungs +dunks +dunlo +dunne +dunny +dunno +dunst +dunts +duole +duomi +duomo +duong +duped +duper +dupes +dupin +dupla +duple +duply +duppa +duppy +dupre +dupuy +dural +duran +duras +durax +dured +duree +durer +dures +duret +durex +durga +durgy +duryl +durio +durno +durns +duroc +duroy +duros +durra +durry +durrs +durst +durum +durzi +dusen +dusio +dusky +dusks +duson +dusty +dusts +dusun +dusza +dutch +dutra +duval +duvet +duxes +dvigu +dvina +dvmrp +dwain +dwale +dwalm +dwane +dwang +dwaps +dwarf +dweck +dwell +dwelt +dwyer +dwyka +dwine +dzoba +eably +eacso +eadas +eadie +eagan +eagar +eager +eagle +eagre +eakly +eamon +eanes +eared +earla +earle +early +earls +earns +earom +earsh +earth +eased +easel +easer +eases +easts +eaten +eater +eaton +eaved +eaver +eaves +ebbed +ebbet +ebbie +ebeye +ebert +eblis +ebner +eboli +ebony +ebons +ebsen +ecafe +ecart +ecass +echar +echea +eched +eches +echis +echos +ecize +eckel +eclat +eclss +ecoid +ecole +ecrus +ectad +ectal +ector +edana +eddas +edder +eddic +eddie +eddra +edema +edgar +edged +edger +edges +edict +edify +ediya +edyie +edile +edina +edita +edith +edyth +edits +ediva +edley +edlin +edlyn +edlun +edman +edmea +edmee +edmon +ednas +ednie +edoni +edora +edrea +edrei +edric +edris +edroi +edroy +edsel +edson +edtcc +educe +educt +edveh +edwin +eeler +eemis +eerie +eeten +effet +effie +effye +efram +efrap +efrem +efren +efron +egadi +egads +egall +egede +egers +egest +eggar +egged +egger +egham +egide +egypt +eglin +eglon +egnar +egrep +egret +egrid +egwan +egwin +ehden +ehlke +ehman +ehudd +eyass +eibar +eider +eydie +eidos +eyers +eyess +eifel +eiger +eight +eyght +eigne +eying +eikon +eilat +eilis +eimak +eimer +einar +eyota +eyoty +eyrar +eyras +eyren +eyrer +eyres +eyrie +eyrir +eisen +eiser +eiten +ejasa +eject +ejido +ejusd +ekaha +eking +ekron +ekwok +elaic +elayl +elain +elais +elamp +elana +eland +elane +elans +elaps +elara +elata +elate +elath +elbie +elboa +elbow +elche +elcho +elden +elder +eldin +eldon +elean +elect +eleen +elegy +eleia +eleme +elemi +elena +elene +eleni +eleph +elery +eleut +eleva +eleve +elevs +elexa +elfic +elfie +elfin +elgan +elgar +elger +elgin +elgon +elian +elias +elida +elide +eliga +elihu +elymi +elint +eliot +elyot +elisa +elise +elyse +elish +elita +elite +eliza +elkin +ellan +ellas +ellen +ellga +ellie +ellin +ellyn +ellis +ellon +elman +elmer +elnar +eloah +eloge +elogy +eloin +elong +elope +elops +elora +elric +elrod +elroy +elsah +elsan +elsey +elses +elset +elsie +elsin +elson +elton +elude +elura +elurd +elute +elvah +elvan +elver +elves +elvet +elvia +elvie +elvin +elvyn +elvis +elwee +elwin +elwyn +emacs +email +emane +embay +embar +embed +ember +embla +embog +embow +embox +embry +embue +embus +emcee +emden +emeer +emend +emera +emery +emesa +emeus +emyde +emyds +emigr +emile +emyle +emili +emily +emina +emirs +emits +emlen +emlin +emlyn +emmey +emmen +emmer +emmet +emmew +emmie +emmye +emmit +emong +emony +emory +emote +emove +empeo +empty +emule +emuls +enact +enage +enami +enapt +enarm +enate +encia +encyc +encke +encup +ended +ender +endew +endia +endor +endow +endue +eneas +eneid +enema +enemy +enent +enfia +enfin +engel +engem +engen +engin +engle +engud +enhat +eniac +enjoy +enlay +enlil +enloe +enmew +ennew +ennia +ennis +ennoy +ennui +enoch +enode +enoil +enola +enols +enone +enorm +enorn +enows +enpia +enray +enrib +enrol +enrut +ensky +ensor +ensue +entad +ental +entea +enter +entia +entom +entre +entry +entte +enugu +enure +enver +envoi +envoy +enweb +enzed +enzym +eoith +eolia +eolic +eosin +epact +epees +epeus +ephah +ephas +ephes +ephod +ephoi +ephor +epics +epiky +epiph +episc +epist +eplot +epner +epoch +epode +epona +epopt +epoxy +eppes +eppie +epris +eprom +epscs +epsom +epulo +equal +equel +eques +equid +equip +equiv +equus +erade +erase +erath +erato +erava +erbaa +erbes +erbia +erbil +erdah +erdda +erdei +erech +erect +erena +erept +ergal +ergon +ergot +erian +erica +erich +erick +erida +eridu +eries +eriha +erika +erina +erine +erinn +eryon +erisa +erizo +erkan +erlin +ermey +ermin +ermit +ernes +ernie +ernst +ernul +erode +erose +erred +errol +erron +error +ersar +erses +ertha +eruca +eruct +erugo +erump +erund +erupt +ervil +ervin +ervum +erwin +esbay +esbon +escar +escot +escry +esdud +esere +esher +eshin +eskar +esker +eskil +eslie +espec +espoo +esque +esrog +essay +essam +essed +essee +essen +esses +essex +essie +essig +estab +estas +estey +estel +esten +ester +estes +estis +estoc +estop +estre +estus +etacc +etana +etang +etape +ethal +ethan +ethel +ether +ethic +ethid +ethyl +ethos +etiam +etyma +etlan +etnas +etrem +etrog +etssp +ettie +ettle +etude +etuis +etuve +etwas +etwee +etzel +eucha +eucre +eucti +euell +eugen +eulau +eulee +euler +eulis +eunet +eupad +euros +eurus +eusol +eutaw +euton +evade +evang +evans +evant +evars +evart +evase +evatt +eveck +evene +evens +event +every +evers +evert +evese +evict +evils +evita +evite +evius +evoke +evora +evros +evvie +ewald +ewall +eward +ewart +ewder +ewell +ewens +ewery +ewers +ewest +ewhow +ewing +ewold +exact +exalt +exams +exaun +excel +excud +excur +exdie +exeat +execs +exect +exede +exert +exhbn +exies +exile +exine +exing +exion +exira +exist +exite +exits +exlex +exode +exody +exons +exopt +expdt +expel +expos +exptl +expwy +exsec +exter +extol +exton +extra +exude +exult +exuma +exurb +exust +exxon +ezana +ezara +faaas +faade +faber +fabes +fabio +fable +fabre +fabri +faced +facer +faces +facet +facia +facie +facit +facks +facom +facty +facto +facts +faddy +faded +faden +fader +fades +fadge +fadil +fadme +fados +faena +faery +faffy +fagan +fagen +fager +faggi +faggy +fagin +fagot +fagus +faham +fahey +faial +fayal +fayed +fails +fayme +faina +fains +faint +faire +fayre +fairy +fairm +fairs +faith +fayth +faits +fayum +faked +fakey +faker +fakes +fakir +falco +falda +falla +fally +falls +falun +falus +famed +fames +fanal +fanam +fancy +fanes +fanga +fangy +fango +fangs +fania +fanya +fanit +fanni +fanny +fanon +fanos +fante +fanti +fanum +fanwe +faqir +farad +farah +farce +farci +farcy +farde +fardh +fardo +fards +fared +farer +fares +fargo +farhi +faria +fario +farle +farly +farls +farmy +farms +farny +faros +farra +farro +farse +farsi +farth +farts +faruq +fasst +fasta +fasti +fasto +fasts +fatah +fatal +fated +fates +fatil +fatly +fatma +fator +fatso +fatty +fatwa +fauch +faugh +fauld +fault +faulx +fauna +fauns +faurd +faure +fause +faust +faute +fauve +favel +favin +favor +favus +fawna +fawne +fawny +fawns +faxan +faxed +faxen +faxes +faxon +faxun +fazed +fazes +fchar +fcomp +fconv +fdubs +feala +fears +fease +feast +featy +feats +feaze +fecal +feces +fecit +fecks +fedak +fedia +fedin +fedor +feedy +feeds +feely +feels +feere +feest +feeze +feyer +feigl +feign +feyly +feint +feist +felch +felda +feldt +felic +felid +felis +felix +fella +felly +fells +felon +felty +felts +felup +femes +femic +femme +femur +fence +fendy +fends +fenks +fenny +feods +feoff +feola +ferae +feral +ferde +ferdy +feres +feria +ferie +ferio +ferly +ferme +fermi +ferna +ferne +ferny +ferns +ferox +ferri +ferry +ferro +ferth +fesse +festa +feste +festy +fetal +fetas +fetch +feted +fetes +fetid +fetis +fetor +fetus +fetwa +feuar +feuds +feued +feune +feute +fever +fevre +fewer +fezes +fezzy +ffrdc +fgrep +fgrid +fhlba +fhlmc +fhrer +fiann +fiant +fiard +fiars +fiats +fiatt +fiber +fibra +fibre +fibry +fibro +fices +fyces +fiche +fichu +ficin +ficus +fidac +fidel +fiden +fides +fidge +fidia +fidos +fiefs +field +fiend +fient +fieri +fiery +fifed +fifer +fifes +fyffe +fifie +fifth +fifty +figge +figgy +fight +fiked +fikey +fykes +fikie +filao +filar +filch +fylde +filea +filed +filer +files +filet +filia +filii +filip +filix +filla +fille +filly +fillo +fills +filmy +films +filos +filth +filum +final +finca +finch +findy +finds +fined +finer +fines +finew +fingo +fingu +finis +finky +finks +finly +finny +finns +finos +fiona +fionn +fiora +fiord +fiore +fique +firca +fired +firer +fires +firma +firmr +firms +firns +firry +first +firth +fisch +fiscs +fishy +fiske +fisty +fists +fitch +fitly +fytte +fitty +fitts +fiume +fiver +fives +fixed +fixer +fixes +fixin +fixit +fixup +fizzy +fjare +fjeld +fjord +flabs +flacc +flack +flaff +flagg +flags +flail +flain +flair +flays +flake +flaky +flamb +flame +flamy +flams +flane +flang +flank +flann +flans +flaps +flare +flary +flash +flask +flats +flavo +flawy +flawn +flaws +flaxy +flche +fldxt +fleay +fleak +fleam +flear +fleas +fleck +flect +fleda +fleer +flees +fleet +flegm +fleys +fleme +flesh +fleta +fleur +flews +flexo +flyby +flick +flics +flied +flier +flyer +flies +flimp +fling +flinn +flynn +flint +flipe +flype +flips +flirt +flisk +flita +flite +flyte +flits +fload +float +flock +flocs +floey +floes +flogs +floyd +flois +floit +floyt +flong +flood +flook +floor +flops +flora +flore +flori +flory +floro +flosh +flosi +floss +flota +flote +flots +flour +flout +flowe +flowk +flown +flows +flrie +flubs +flued +fluey +fluer +flues +fluff +fluid +fluyt +fluke +fluky +flume +flump +flung +flunk +fluor +flurn +flurr +flurt +flush +flusk +flute +fluty +fname +fnese +foaly +foals +foamy +foams +focal +focus +fodda +foder +fodge +foehn +foeti +fogas +fogey +fogel +foggy +fogie +fogle +fogon +fogou +fogus +fohat +fohns +foyer +foyil +foils +foims +foins +foirl +foism +foist +fokos +foldy +folds +foley +folia +folic +folie +folio +folky +folks +folly +fomes +fomor +fonda +fonds +fondu +fonly +fonts +foody +foods +fools +foote +footy +foots +foppy +foray +foram +forby +forbs +force +forcy +forcs +fordy +fordo +fords +forel +fores +foret +forex +forge +forgo +forky +forks +forli +forma +forme +formy +forms +forra +forst +forta +forte +forth +forty +forts +forum +fosie +fossa +fosse +fotch +fotui +fouke +foulk +fouls +found +fount +fouqu +fourb +fours +foute +fouth +fouty +fovea +fowey +fowle +fowls +foxed +foxer +foxes +foxie +foxly +fplot +fpsps +frack +fract +frags +fraya +fraid +fraik +frail +frayn +frays +frame +franc +frank +frans +franz +frape +frapp +fraps +frary +frase +frass +frate +frats +fraud +fraus +frawn +fraze +frden +freak +fream +frear +frech +freck +freda +fredi +freed +freen +freer +frees +freet +frege +freia +freya +freir +freyr +freit +fremd +fremt +frena +freon +frere +fresh +fress +frets +frett +freud +friar +fricc +frick +frida +fried +frier +fryer +fries +frigg +frigs +frija +frike +frill +friml +frise +frisk +friss +frist +frith +frits +fritt +fritz +frize +frizz +frock +frodi +froes +frogs +froid +froma +frome +fromm +frona +frond +frons +front +froom +frore +frory +frosh +frosk +frost +froth +frowy +frowl +frown +frows +froze +frugs +fruin +fruit +fruma +frump +frush +frust +fslic +fuage +fubar +fubby +fubsy +fuchi +fuchs +fucks +fucus +fuder +fudge +fudgy +fuels +fuffy +fugal +fuget +fuggy +fugie +fugio +fugit +fugle +fugue +fugus +fujio +fujis +fulah +fulas +fulda +fulke +fulks +fully +fulls +fulth +fults +fultz +fulup +fulvi +fulwa +fumed +fumer +fumes +fumet +fumid +funch +funda +fundi +fundy +funds +funge +fungi +fungo +funic +funis +funje +funky +funks +funli +funny +fural +furan +furca +furey +furie +furil +furyl +furls +furor +furry +furth +furud +furze +furzy +fusan +fusco +fused +fusee +fusel +fuses +fusht +fusil +fussy +fusty +fusus +futon +futwa +fuzed +fuzee +fuzes +fuzil +fuzzy +gabar +gabbi +gabby +gabbs +gabey +gabel +gabes +gabie +gable +gabon +gabor +gabun +gaddi +gader +gades +gadge +gadid +gadis +gadso +gadus +gaels +gaeta +gaffe +gaffs +gafsa +gaged +gagee +gager +gages +gagne +gagor +gahan +gayal +gayel +gayer +gaige +gayla +gaile +gayle +gaily +gayly +gaine +gains +gaist +gaits +gaitt +gaius +gaivn +gaize +gajda +galah +galan +galas +galax +galba +galbe +galea +galee +galei +galey +galen +galer +gales +galet +galga +galik +galla +galle +galli +gally +galls +galop +galut +galva +galvo +gamay +gamal +gamas +gamba +gambe +gambi +gambs +gamed +gamey +gamer +games +gamic +gamin +gamma +gammy +gamps +gamut +ganam +gance +ganch +ganda +ganef +ganev +ganga +gange +gangs +ganja +ganny +ganof +gansa +gansy +ganta +gantt +ganza +gaols +gaons +gaped +gaper +gapes +gapin +gappy +garad +garau +garbe +garbo +garbs +garce +garda +garde +gardy +gareh +garey +garek +garik +garin +garle +garmr +garni +garon +garoo +garry +garse +garth +garua +garum +garvy +gasan +gases +gashy +gaspe +gaspy +gasps +gassy +gasts +gatch +gated +gater +gates +gatha +gator +gatow +gatun +gauby +gaucy +gaudy +gauds +gauge +gauls +gault +gaumy +gaums +gaunt +gaura +gaure +gauri +gaurs +gause +gauss +gauze +gauzy +gavan +gavel +gaven +gavia +gavin +gavle +gavot +gavra +gawby +gawen +gawky +gawks +gawps +gawra +gawsy +gazed +gazee +gazel +gazer +gazes +gazet +gazon +gazoz +gazzo +gbari +gconv +geary +gears +gease +geast +geber +gebur +gecko +gecks +gecos +gedds +geeky +geeks +geese +geest +gefen +gehey +geyan +geier +geigy +geira +geisa +geiss +geist +gekko +gelds +gelee +gelya +gelid +gelly +gelts +gemel +gemma +gemmy +gemot +gemse +gemul +genae +genal +genep +genes +genet +genia +genic +genie +genii +genin +genio +genip +genys +genit +genna +genni +genny +genoa +genom +genos +genre +genro +genty +gents +genua +genus +geode +geoff +geoid +geole +georg +geoty +gerah +gerar +gerbe +gerbo +gerda +gerdi +gerdy +gerek +gerge +gerik +gerim +gerip +germy +germs +gerri +gerry +gerta +gerti +gerty +gesan +gesso +geste +gests +getae +getah +getas +getfd +getic +getid +getty +getup +geums +gezer +ghain +ghana +ghast +ghats +ghaut +ghazi +ghbor +gheen +ghees +ghent +ghess +ghyll +ghole +ghoom +ghost +ghoul +giamo +giana +gyani +giant +gyasi +gyatt +gibbi +gibby +gibbs +gibed +gybed +gibel +giber +gyber +gibes +gybes +gibil +gibli +gibun +gibus +giddy +giess +giffy +gifts +gigas +gyges +gigge +gighe +gygis +gigle +gigli +gigot +gigue +giher +gijon +gilba +gilby +gilda +gilds +giles +gilet +gilia +gilim +gylys +gilli +gilly +gills +gilpy +gilse +gilty +gilts +gilud +gilus +gimel +gymel +gimme +gimpy +gimps +ginep +gynic +ginks +ginni +ginny +gintz +ginza +ginzo +giono +gipon +gippy +gippo +gyppo +gipps +gipsy +gypsy +gyral +girba +girds +gyred +gyres +gyric +girja +girly +girls +girny +girns +giron +gyron +giros +gyros +girru +girse +girsh +girth +girts +gyrus +gisel +gisla +gismo +gists +gitel +gitim +gytle +giuba +giuki +giule +giust +gyved +givey +given +giver +gives +gyves +givin +gizeh +gizmo +gjuki +glaab +glace +glack +glade +gladi +glady +glads +glaga +glaik +glair +glaky +glali +gland +glans +glare +glary +glass +glaum +glaur +glaux +glave +glaze +glazy +glead +gleam +glean +gleba +glebe +gleby +gleda +glede +gledy +gleds +gleed +gleek +gleen +glees +gleet +gleir +gleys +gleit +glene +glenn +glens +glent +glial +glias +glick +glide +gliff +glike +glime +glimp +glims +glink +glynn +glint +glyph +glisk +gliss +glist +glitz +gloam +gloat +globe +globy +globs +gloea +glogg +glome +glomi +glomr +gloms +glood +gloom +glops +glore +glori +glory +gloss +glost +glout +glove +glows +gloze +gluck +glued +gluey +gluer +glues +glugs +gluma +glume +glump +gluon +gluts +gnarl +gnarr +gnars +gnash +gnast +gnats +gnawn +gnaws +gnide +gnoff +gnome +goads +goala +goals +goaty +goats +goave +goban +gobat +gobbe +gobbi +gobby +gober +gobet +gobia +gobio +gobos +godey +godel +godet +godin +godly +goers +goety +goetz +gofer +gogga +gogol +gogos +gogra +goias +goico +goyen +goyim +goyin +goyle +going +gokey +golda +goldi +goldy +golds +golee +golem +goles +golet +golfs +golgi +golly +goloe +golpe +golts +goltz +golub +golva +gomar +gombo +gomel +gomer +gomez +gonad +gonal +gondi +gonef +goney +goner +gongs +gonia +gonid +gonif +gonys +gonna +gonne +gonof +gonta +gonzo +gooch +goode +goody +goods +gooey +goofy +goofs +gooky +gooks +goole +gools +gooma +goony +goons +goopy +goops +goose +goosy +gopak +goral +goran +gorce +gordy +gordo +gored +goree +gorey +goren +gorer +gores +gorga +gorge +goric +gorin +gorki +gorky +gorps +gorra +gorry +gorse +gorsy +gorst +gorum +gosip +gosse +gossy +gotch +goter +gotha +goths +gotos +gotra +gotta +gouda +goudy +gouge +gough +gould +goumi +goura +gourd +goury +gouty +gouts +gowan +gowdy +gowds +gowen +gower +gowks +gowns +gowon +goxes +graaf +graal +grabs +grace +gracy +grade +grady +grads +graff +graft +grahn +graig +grail +grain +graip +grays +grama +grame +gramy +gramp +grams +grana +grand +grane +grani +grank +grano +grans +grant +grape +graph +grapy +grasp +grass +grata +grate +grath +grati +gratt +gratz +grave +gravy +grawn +graze +great +grebe +grebo +grece +greco +greed +greek +green +greer +grees +greet +grefe +greff +grega +grege +gregg +grego +greig +grein +greys +greit +grene +greta +grete +grewt +grice +gride +gryde +grids +grief +grieg +grier +griff +grift +grigs +grike +grill +grime +grimy +grimm +grimp +grind +gring +grins +grint +griot +gripe +grype +griph +gryph +gripy +grips +gript +grise +grist +grith +grits +groan +groat +grobe +grofe +groff +grogs +groin +groma +grond +gront +groof +groom +groop +groos +groot +groow +grope +grory +grose +gross +grosz +grote +grots +grouf +group +grous +grout +grove +grovy +growl +grown +grows +grubb +grube +grubs +gruel +grues +gruff +gruft +gruis +gruys +grume +grump +grunt +grush +gruss +gsbca +gschu +gteau +guaba +guaco +guafo +guage +guaka +guama +guana +guano +guans +guara +guard +guary +guars +guasa +guato +guava +guaza +gubat +gubbo +gucki +gucks +gudea +gudes +gudge +gudok +guelf +guess +guest +gueux +guffy +guffs +gugal +guiac +guiba +guide +guido +guids +guyed +guyer +guige +guijo +guild +guile +guily +guilt +guinn +guion +guyon +guyot +guiro +guise +gujar +gulae +gulag +gular +gulas +gulch +gules +gulfy +gulfs +gulix +gully +gulls +gulph +gulpy +gulps +gumby +gumbo +gumly +gumma +gummy +gunar +gunas +gunda +gundi +gundy +gunge +gunja +gunky +gunks +gunne +gunny +guppy +gupta +guran +gurdy +gurge +guria +guric +gurle +gurly +gurry +gursh +gurts +gurus +gusba +guser +gushy +gusla +gusle +gussi +gussy +gusta +gusti +gusty +gusto +gusts +gutow +gutsy +gutta +gutte +gutti +gutty +guzel +guzul +gwari +gwawl +gweed +gweyn +gwely +gwelo +gwenn +gwent +gwine +gwinn +gwynn +haafs +haars +haase +habab +habbe +haber +habet +habib +habit +hable +habub +habus +hacek +hache +hacht +hacky +hacks +hadal +hadar +hadas +haddo +haded +haden +hades +hadik +hadit +hadji +hadjs +hadnt +hadst +haeju +haems +haerr +haets +hafis +hafiz +hafts +hagai +hagan +hagar +hagen +hager +haggi +haggy +hagia +hagno +hague +hahas +haick +haida +haydn +hayed +hayey +hayer +hayes +haifa +haika +haikh +haiks +haiku +haile +haily +hails +haymo +haine +hayne +haire +hairy +hairs +haise +hayse +haysi +haiti +hayti +hajes +hajib +hajis +hajji +hajjs +hakai +hakam +hakan +hakea +hakes +hakim +hakka +hakon +halaf +halal +halas +halbe +halch +halda +haldi +haldu +haled +haley +haler +hales +halfa +halfy +halid +halie +halke +halla +halle +halli +hally +hallo +halls +halma +halms +haloa +halos +halse +halsy +halte +halts +halva +halve +halvy +halwe +hamal +haman +hamel +hamer +hames +hamid +hamil +hamli +hammy +hamon +hamsa +hamus +hamza +hanae +hanan +hanap +hanau +hance +hanch +handy +hands +haney +hange +hangs +hanya +hanif +hanky +hanks +hankt +hanna +hanni +hanny +hanno +hanoi +hansa +hanse +hants +hanus +haole +haoma +haori +hapax +haply +happy +hapte +haram +harar +haras +harbi +harco +hardi +hardy +hards +hared +harem +hares +harim +harka +harks +harle +harli +harls +harms +harns +harod +harpa +harpy +harpp +harps +harre +harri +harry +harsh +harst +harte +harty +harts +harve +harze +hasan +hasek +hasen +hashy +hasht +hasid +hasin +hasky +hasnt +hasps +hasse +hassi +hasta +haste +hasty +hatch +hated +hatel +hater +hates +hathi +hatia +hatta +hatte +hatti +hatty +hauck +hauge +haugh +hauld +haulm +hauls +hault +haunt +hausa +hause +haust +haute +havel +haven +haver +haves +havoc +havre +hawed +hawer +hawky +hawks +hawok +hawse +hazan +hazed +hazel +hazem +hazen +hazer +hazes +hazle +hazor +hbert +hcsds +hctds +hdqrs +heady +heads +heald +healy +heall +heals +heapy +heaps +heard +hearn +hears +heart +heath +heats +heave +heavy +heazy +hebbe +hebel +heben +heber +hebes +hecco +hecht +hecks +hecla +hecte +hedda +heddi +heddy +heder +hedge +hedgy +hedie +hedin +hedva +hedve +heedy +heeds +heels +heeze +heezy +hefty +hefts +hegel +heger +hehre +heian +heiau +heida +heyde +heidi +heidy +heidt +heyer +heyes +heigh +heygh +heigl +heijo +heike +heild +heily +heils +heine +heins +heinz +heirs +heise +heyse +heist +heize +hejaz +hekla +helas +helco +helda +helen +helga +helge +helyn +helio +helix +helle +helli +helly +hello +hells +helms +heloe +helot +helps +helsa +helse +helve +hemad +hemal +heman +hemen +hemes +hemet +hemia +hemic +hemin +hemol +hemon +hempy +hemps +henad +hence +hench +hendy +henen +henge +henie +henig +henka +henke +henna +henni +henny +henri +henry +henty +hents +henze +hepar +hepza +hepzi +herat +herba +herby +herbs +herds +herem +heres +herls +herma +hermi +hermy +hermo +herms +herne +herns +herod +heron +heros +herra +herry +herse +hersh +herta +herts +hertz +herut +herve +herzl +hesky +hesse +hesta +hests +heths +hetti +hetty +heuau +heuch +heugh +hevea +heved +hewed +hewel +hewer +hewes +hewet +hewgh +hewie +hexad +hexed +hexer +hexes +hexyl +hexis +hyads +hyams +hiant +hiate +hiatt +hyatt +hibbs +hibla +hybla +hicht +hichu +hicky +hicks +hided +hidel +hyden +hider +hides +hydes +hidie +hydra +hydri +hydro +hield +hiems +hyena +hienz +hiera +hiett +higgs +highs +hight +higra +hihat +hying +hiips +hijaz +hijra +hiked +hiker +hikes +hylan +hilar +hylas +hilch +hilda +hilde +hildy +hyleg +hilel +hylic +hilla +hilly +hillo +hills +hilsa +hilts +hilum +hilus +hyman +hymen +hymie +hymir +himne +hymns +hinau +hinch +hinda +hynda +hynde +hindi +hinds +hindu +hiney +hynek +hines +hynes +hinge +hinny +hints +hinze +hyoid +hyozo +hyped +hiper +hyper +hypes +hypha +hypho +hipmi +hypos +hippa +hippi +hippy +hippo +hirai +hiram +hyrax +hired +hiren +hirer +hires +hyrie +hirse +hyrse +hirsh +hirst +hyrst +hyrum +hyrup +hirza +hisbe +hisis +hyson +hispa +hissy +hists +hitch +hithe +hived +hiver +hives +hizar +hlhsr +hliod +hoagy +hoang +hoard +hoare +hoary +hoars +hoast +hoban +hobby +hobbs +hobey +hobic +hobie +hobis +hoboe +hobos +hocco +hocky +hocks +hocus +hodad +hoddy +hoder +hodge +hodur +hoers +hoeve +hofei +hofer +hoffa +hofuf +hogan +hogen +hoggy +hoggs +hogle +hogni +hogue +hoyas +hoick +hoyle +hoise +hoist +hokah +hokan +hoked +hokey +hoker +hokes +hokku +hokum +holds +holed +holey +holer +holes +holgu +holia +holks +holla +holle +holli +holly +hollo +holms +holna +holst +holts +holtz +holub +homam +homed +homey +homer +homes +homme +homos +honan +honda +hondo +honed +honey +honer +hones +hongs +honig +honky +honks +honna +honor +honus +honzo +hooch +hoody +hoods +hooey +hoofy +hoofs +hooge +hoogh +hooye +hooka +hooke +hooky +hooks +hooly +hoopa +hoops +hoose +hoosh +hooty +hoots +hoove +hopak +hoped +hopeh +hopei +hoper +hopes +hopin +hopis +hoppe +hoppy +hoppo +horae +horah +horal +horan +horas +horde +horeb +horim +horla +horme +horne +horny +horns +horol +horry +horsa +horse +horsy +horst +horta +horus +hosea +hosed +hosel +hosen +hoses +hoshi +hosta +hosts +hotch +hotei +hotel +hotly +hotol +hotta +hotze +houck +hough +hoult +houma +hound +houri +hours +house +housy +houss +houve +hovey +hovel +hoven +hover +howdy +howea +howey +howel +howes +howff +howfs +howie +howks +howls +howso +hoxha +hoxie +hrolf +hsian +hsien +hssds +hsuan +huaca +huaco +huang +huari +huave +hubba +hubby +hubey +huber +hubie +hubli +hucar +hucho +hucks +huddy +hudis +huffy +huffs +hufuf +hugel +huger +hugin +hugli +hugon +huila +huile +hulas +hulch +hulda +hulen +hulky +hulks +hullo +hulls +hulme +human +humbo +humet +humic +humid +humin +humit +humor +humph +humpy +humps +humus +hunan +hunch +hundi +hunky +hunks +hunts +hupeh +hurds +hurff +hurly +hurls +hurok +huron +hurri +hurry +hurst +hurty +hurts +husch +husha +husho +husht +husky +husks +hussy +husum +hutch +hutia +hutre +hutto +huzza +huzzy +hwang +yabbi +yabby +yaboo +yacal +yacca +iache +yacht +yacks +yacov +yadim +yaffs +yager +yagis +yagua +yahan +yahoo +yahve +yahwe +yaird +yajna +yakan +yaker +yakin +yakka +yakut +yalla +yalta +iambe +iambi +iambs +yamel +yamen +yameo +yamis +yampa +yamph +yamun +iamus +yanan +yance +yancy +yangs +yanky +yanks +ianus +iapyx +yaply +yapok +yapon +yappy +iappp +yapur +yaqui +yaray +yarak +yards +yarer +iaria +yarke +yarly +yarns +yaron +yarry +yarth +yasht +iasis +yasna +yassy +yasui +yasuo +iasus +yates +iatry +iatse +yauco +yauds +yauld +yaups +yavar +yawed +yawey +yawls +yawny +yawns +yawps +yazoo +ibada +ibagu +ibbie +iberi +ibert +ibiza +iblis +ibota +ibsen +ibson +icaco +icard +icasm +icccm +iceni +icers +icftu +ichor +ichth +ician +icica +icier +icily +icing +icity +icken +icker +ickes +ickle +yclad +iclid +icons +iconv +ictic +ictus +idaea +idaho +idaic +idant +idcue +iddat +idden +iddhi +iddio +ideal +idean +ideas +idell +idems +ident +idest +ideta +idgah +idyll +idyls +idiom +idion +idiot +idism +idist +idite +idium +idled +idler +idles +idmon +idola +idols +idona +idose +idoux +idria +idryl +idris +iduna +idzik +yeans +yeara +yeard +yearn +years +yeast +yeats +yecch +yechy +yechs +yeech +yeggs +yeisk +yelek +yelks +yells +yelps +yemen +yenan +yenta +yente +ieper +yeply +yerba +yerga +yerks +yermo +ierna +ierne +yerth +yerva +yerxa +yeses +yesso +yesty +yetac +yetah +yetis +ietta +yetta +yetti +yetty +yetts +yeuky +yeuks +yeung +yeven +yezdi +yezzy +yfere +ifill +ifint +iflwu +iform +ifree +ifrit +ifrps +ygapo +igara +igbos +igdyr +igfet +iggie +ighly +igigi +iglau +igloo +iglus +ignaw +ignaz +ignis +iguac +ihlat +ihlen +ihram +iyang +iiasa +yield +iyyar +yikes +yills +yince +iinde +yinst +yipes +yirds +yirrs +yirth +iispb +iives +ijmaa +ijore +ikara +ikary +ikeda +ikeja +ikona +ikons +ilama +ilana +ileac +ileal +ylems +ilene +ileon +ileum +ileus +ilgwu +iliac +iliad +ilial +ilian +iliau +iliff +ilima +iline +ilion +ilisa +ilysa +ilise +ilyse +ility +ilium +iller +illia +illth +illus +ilmen +iloko +ilona +ilone +image +imago +imams +imare +imaum +imban +imbat +imbed +imber +imbue +imcnt +imena +imide +imido +imids +imine +imino +imitt +imlay +imler +immew +immis +immit +immix +immov +immun +imola +impar +imped +impel +impen +imper +impis +imply +impot +imput +imray +imroz +imshi +imsvs +imune +imvia +inact +inads +inaja +inane +inapt +inari +inark +inarm +inbye +inbow +incan +incas +incle +incog +incor +incra +incur +incus +incut +indan +indef +indew +index +india +indic +indie +indii +indyl +indin +indio +indiv +indol +indow +indra +indre +indri +induc +indue +indus +ineye +inept +ineri +inerm +inert +infer +infin +infit +infix +infos +infra +ingan +ingar +ingem +inger +ingle +inglu +ingot +ingra +inial +inigo +inina +inine +inion +injun +inked +inken +inker +inket +inkie +inkle +inkom +inkos +inkra +inlay +inlaw +inlet +inman +inmew +inned +inner +innes +innet +innis +inola +inoma +inone +inonu +inorb +inorg +inoue +input +inria +inrol +inrub +inrun +insea +insee +insep +inset +insko +insol +instr +insue +intap +intel +inter +intil +intis +intnl +intra +intro +intsv +intuc +intue +inuit +inula +inure +inurn +inust +invar +invoy +inwit +yobbo +yocco +yocks +iodal +yodel +yoder +yodhs +iodic +iodid +iodin +yodle +iodol +yogas +yogee +yoghs +yogic +yogin +yogis +ioyal +yoick +yojan +yoked +yokel +yoker +yokes +yokum +yolyn +yolky +yolks +yomer +yomim +yomin +yomud +yonah +ionia +ionic +yonic +yonis +yonit +yonne +yoong +yores +iorgo +yorgo +iorio +yorke +yorks +iortn +iosep +yoshi +iotas +youff +young +youre +yourn +yours +yourt +youse +youth +youve +youze +ioved +yoven +iover +ioves +iowan +iowas +yowed +yowes +yowie +yowls +ioxus +ipava +iphis +iplan +ypres +ypsce +ipsus +iqbal +yquem +iraan +irade +iraki +irani +iraqi +irate +irazu +irbid +irbil +irbis +yreka +irena +irene +ireos +irfan +irgun +irian +irido +irids +irina +iring +irisa +irish +irita +irked +iroha +iroko +irone +irony +irons +irous +irpex +irred +irreg +irvin +irwin +isaac +isaak +ysaye +isamu +isawa +isbas +isbel +isere +iseum +isfug +ishan +ishii +ishum +isiac +isiah +ising +isize +islay +islam +isled +islek +isles +islet +islip +islot +ismay +ismal +isman +ismet +isnad +isode +isola +isoln +isolt +isort +issei +yssel +issie +issue +issus +ister +isthm +istic +istle +itala +itali +italy +italo +itchy +itcze +itemy +items +iters +ither +ithun +ition +itnez +itous +itsec +ytter +itusa +yuans +yucat +yucca +yucch +yuchi +yucky +yucks +yugas +yuhas +yukio +yukon +yulan +yulee +yules +yulma +iulus +yuman +yumas +yummy +yumuk +yunca +yupon +yurak +yurev +yuria +yurik +yurok +yursa +yurta +yurts +yuruk +yusem +yusuk +yutan +yuzik +ivana +ivens +ivers +ivett +ivied +ivies +ivins +iviza +ivory +ivray +iwbni +ixias +ixion +ixora +ixtle +izaak +izard +izars +izawa +izing +izyum +izmir +izmit +iznik +izote +iztle +izumi +izzak +izzat +jaala +jabal +jaban +jabez +jabia +jabin +jabir +jabon +jabot +jabul +jacal +jacey +jacht +jacie +jacki +jacky +jacko +jacks +jacob +jadda +jaddo +jaded +jades +jaela +jaffa +jaffe +jagat +jager +jaggy +jaggs +jagir +jagla +jagra +jagua +jahel +jahve +jahwe +jayem +jails +jaime +jayme +jaina +jaine +jayne +jakey +jakes +jakie +jakin +jakob +jakop +jakos +jakun +jalap +jalee +jalet +jalop +jalor +jalur +jamal +jaman +jambe +jambi +jambo +jambs +jamey +jamel +james +jamie +jamil +jamin +jammy +jammu +jamul +jandy +janey +janek +janel +janes +janet +jania +janie +janye +janik +janis +janys +janka +janna +janok +janos +janot +janty +jantu +janua +janus +japan +japed +japer +japes +japha +japyx +japur +jarad +jarde +jareb +jared +jarek +jaret +jarib +jarid +jarls +jarmo +jarra +jarry +jarvy +jasey +jasen +jasik +jason +jaspe +jassy +jasun +jatha +jatki +jatni +jatos +jauch +jauks +jaunt +jaups +javan +javas +javed +javel +javer +jawab +jawan +jawed +jazey +jazzy +jbeil +jeana +jeane +jeany +jeans +jebat +jebel +jebus +jecho +jecoa +jecon +jedda +jeddy +jeddo +jeeps +jeery +jeers +jefes +jeffy +jegar +jehad +jehan +jehol +jehup +jehus +jeida +jelab +jelib +jelks +jelle +jelly +jello +jells +jembe +jemez +jemie +jemma +jemmy +jenda +jenei +jenin +jenks +jenna +jenne +jenni +jenny +jepum +jerad +jerba +jeres +jerez +jerib +jerid +jeris +jerky +jerks +jerol +jerre +jerri +jerry +jessa +jesse +jessi +jessy +jests +jesup +jesus +jetes +jeton +jetty +jevon +jewed +jewel +jewis +jewry +jfmip +jheel +jhool +jibba +jibby +jibbs +jibed +jiber +jibes +jiboa +jidda +jiffy +jiffs +jiggy +jihad +jilli +jilly +jills +jilts +jimbo +jimmy +jimpy +jinan +jingo +jingu +jinja +jinks +jinni +jinny +jinns +jyoti +jiqui +jirga +jisms +jitro +jived +jiver +jives +jixie +jizya +jnana +joana +joane +joann +joash +jobey +jobie +jobye +jocko +jocks +jocum +jodee +jodel +jodie +jodyn +joeys +joela +joell +joerg +joete +johan +johen +johna +johny +johns +johor +johst +joyan +joice +joyce +joyed +joins +joint +joist +jokai +joked +jokey +joker +jokes +jokul +jolda +jolee +joles +jolie +jolyn +jolla +jolly +jolon +jolty +jolts +jomon +jonah +jonas +jonel +jones +jonie +jonis +jonme +jonna +jonny +joola +jooss +joost +joppa +joram +joree +jorey +jorge +jorie +jorin +joris +jorry +jorum +josee +josef +josey +josep +joser +joses +joshi +josie +josip +josue +jotas +jotty +jotun +joual +jough +jougs +jouks +joule +joung +journ +jours +joust +jouve +jovia +jowar +jowed +jowel +jower +jowly +jowls +jowpy +jozef +jtids +jtunn +juana +juang +juans +jubal +jubas +jubbe +jubes +jubus +judah +judas +judea +judex +judge +judie +judye +judon +judos +judus +jueta +jufti +jufts +jugal +juger +jugum +juyas +juice +juicy +juise +jujuy +jujus +juked +jukes +julee +juley +julep +jules +julia +julid +julie +julio +julis +julus +jumba +jumby +jumbo +jumma +jumna +jumpy +jumps +junco +jundy +junet +junia +junie +junji +junky +junko +junks +junna +junno +junot +junta +junto +jupes +jupon +jural +jurat +jurdi +jurel +jurez +juris +juror +jurua +jussi +justa +justo +justs +jutes +jutic +jutka +jutta +jutty +juvia +juxon +juxta +kaaba +kaama +kabab +kabar +kabel +kabir +kabob +kabul +kacey +kacha +kacie +kadai +kadar +kaden +kadis +kadmi +kados +kaela +kaete +kafir +kafiz +kafka +kafre +kafta +kagos +kagus +kahar +kahau +kahle +kaiak +kayak +kayan +kayes +kaifs +kaila +kayla +kaile +kayle +kails +kaimo +kaine +kayne +kains +kayos +kaiwi +kajar +kakan +kakar +kakas +kakis +kakke +kalam +kalan +kalat +kaleb +kales +kalie +kalif +kalil +kalin +kalis +kalki +kalle +kalli +kally +kalon +kalpa +kalvn +kamay +kamal +kamao +kamas +kamat +kamba +kamel +kames +kamet +kamik +kamin +kamis +kamsa +kanab +kanae +kanal +kanap +kanas +kanat +kande +kandy +kaneh +kanes +kanga +kania +kanya +kanji +kannu +kansa +kansu +kanzu +kaons +kapaa +kapai +kapas +kaphs +kapok +kapor +kappa +kappe +kapur +kaput +karas +karat +karbi +karch +karee +karel +karen +karez +karia +karie +karil +karyl +karim +karin +karyn +karla +karli +karly +karma +karna +karns +karol +karon +karoo +karos +karou +karri +karry +karst +karts +kasai +kasey +kaser +kasha +kashi +kaska +kassa +kassi +kassu +katar +katat +katee +katey +katha +kathe +kathi +kathy +katya +katie +katik +katti +katuf +katun +kauai +kauch +kaule +kauri +kaury +kavas +kaver +kavla +kawai +kazak +kazan +kazim +kazoo +kazue +kbars +keaau +keach +keane +keare +keary +kearn +keats +keavy +keawe +kebab +kebar +kebby +keble +kebob +kechi +kecky +kecks +kedah +kedar +kedge +kedgy +keech +keefe +keefs +keeks +keele +keely +keels +keena +keene +keens +keeps +keese +keest +keets +keeve +kefir +kefti +kegan +kehoe +keyed +keyek +keyer +keyes +keijo +keiko +keily +keirs +keist +keita +keyte +keith +keywd +kelby +kelci +kelcy +kelda +keleh +kelek +kelep +kelia +kella +kelli +kelly +kelpy +kelps +kelsi +kelsy +kelso +kelty +kelts +kemah +kemal +kemme +kempe +kempy +kemps +kempt +kenaf +kenai +kenay +kenaz +kench +kendy +kendo +kenya +kenji +kenly +kenna +kenny +kenno +kenon +kenos +kenta +kente +kenti +kenzi +keogh +keota +keout +kepis +kerak +kerat +kerby +kerbs +kerch +kerek +kerel +keres +kerfs +kerge +kerin +keryx +kerki +kermy +kerne +kerns +keros +kerri +kerry +kerst +kerve +kesar +kesia +kesse +ketal +ketch +keten +ketyl +ketoi +ketol +kette +ketti +ketty +keung +kevan +kevel +keven +kever +kevil +kevin +kevyn +kevon +kexes +kezer +khadi +khafs +khaya +khayy +khair +khaja +khaki +khalk +khalq +khama +khami +khano +khans +khaph +khasa +khasi +khass +khats +kheda +kheth +khets +khiam +khieu +khila +khios +khiva +khmer +khnum +khoin +khoja +khoka +khond +khosa +khoum +khuai +khufu +khula +khuzi +khvat +kiaat +kiack +kyack +kiaki +kyaks +kiang +kyang +kyars +kyats +kibbe +kibei +kibes +kibla +kicky +kicks +kicva +kidde +kiddy +kiddo +kiefs +kiehl +kiehn +kieye +kiele +kiers +kieta +kihei +kiyas +kikai +kikar +kiker +kikes +kikki +kikoi +kilah +kylah +kilan +kilar +kilby +kileh +kiley +kylen +kylie +kilij +kilim +kylin +kylix +killy +kills +kilns +kyloe +kilom +kilos +kilty +kilts +kimbe +kimbo +kimmi +kimmy +kimmo +kimon +kimpo +kymry +kinah +kynan +kinas +kinau +kinch +kinde +kinds +kindu +kines +kings +kingu +kinic +kinin +kinky +kinks +kinna +kinny +kinoo +kinos +kinot +kinta +kioea +kioga +kyoga +kioko +kiona +kiosk +kioto +kyoto +kiowa +kippy +kiran +kirby +kirch +kyrie +kirin +kirit +kirks +kirns +kiron +kirov +kirst +kirve +kisan +kishi +kishy +kisor +kisra +kissy +kists +kiswa +kitab +kitan +kitar +kited +kiter +kites +kytes +kithe +kythe +kiths +kitti +kitty +kitwe +kyung +kivas +kiver +kiwai +kiwis +kizil +kyzyl +kkyra +klans +klapp +klara +klatt +klaus +klber +klebs +klehm +kleig +klein +klemm +klenk +kleon +kleve +klick +klieg +klimt +klina +kline +kling +klino +klips +kljuc +klong +kloof +klops +klosh +klotz +kluck +kluge +klump +klunk +klute +klutz +kmmel +kmole +knack +knape +knapp +knaps +knark +knarl +knars +knaur +knave +knead +kneed +kneel +knees +knell +knelt +knezi +kniaz +knyaz +knick +knies +knife +knipe +knish +knits +knive +knobs +knock +knoit +knoke +knoll +knops +knorr +knosp +knots +knott +knout +knowe +known +knows +knurl +knurs +knute +knuth +koala +koali +koans +koban +kobus +kochi +kodak +kodok +kodro +koels +koeri +kofta +kogai +kogia +kohen +kohls +kohua +koyan +koila +koine +kokam +kokan +kokas +kokia +kokil +kokio +kokka +kokos +kokra +kokum +kolar +kolas +kolbe +kolea +kolis +koloa +kolos +kolva +kombu +komsa +konak +konde +kondo +koner +konev +kongo +kongu +konia +konya +konig +konyn +konks +kooka +kooky +kooks +koord +koorg +kopaz +kopec +kopek +kophs +kopis +kopje +koppa +korah +korai +koral +koran +korat +korea +korec +korey +koren +korff +korie +korin +korma +korns +koroa +koror +korry +korun +korwa +kosak +kosey +kosel +koser +kosha +koshu +kosin +koslo +kosos +kosse +kosti +kotal +kotar +kotos +kotow +kotta +kotto +kouts +kouza +koval +kovar +kovil +kovno +kowal +kraal +kraft +kragh +krait +krall +krama +krang +kranj +krans +kraul +kraus +kraut +krebs +kreda +kreep +kreil +krein +kreis +kreit +krell +krems +kreng +krenn +krepi +kress +krieg +kries +krill +krina +krips +kriss +krivu +krock +krogh +kroll +krome +krona +krone +kroon +krosa +krubi +krupp +kruse +krute +krutz +kuban +kubba +kubis +kucik +kudos +kudus +kudva +kudzu +kuehn +kufic +kugel +kukri +kuksu +kukui +kulah +kulak +kulan +kulda +kulla +kulun +kuman +kumar +kumbi +kumyk +kumis +kumys +kumni +kunai +kunbi +kungs +kunia +kunin +kurys +kurku +kurma +kurmi +kursh +kursk +kurta +kurth +kurtz +kurus +kusam +kusan +kusch +kusha +kusin +kuska +kusso +kusti +kusum +kutch +kutta +kvass +kvint +kwame +kwang +kwapa +kwara +kwasi +kwela +laang +laban +labaw +labba +labby +label +labia +labis +labor +labra +lacca +laced +lacee +lacey +lacer +laces +lacet +lache +lacie +lacis +lacks +lacon +lacto +ladar +laddy +laded +laden +lader +lades +ladew +ladik +ladin +ladle +ladon +ladue +laeti +laevo +lafox +lafta +lagan +lagas +lagen +lager +lagly +lagna +lagos +lagro +lahar +lahey +lahmu +lahti +laich +laics +layed +layer +laigh +layia +layla +laina +laine +layne +laing +laird +lairy +lairs +laise +laith +laity +layup +laius +lajas +lajos +laked +lakey +laker +lakes +lakhs +lakie +lakin +lakke +lakme +laksa +lalia +lalla +lally +lalls +laluz +lamar +lamas +lamba +lamby +lambs +lamda +lamed +lamee +lamel +lamer +lames +lamia +lamin +lammy +lamna +lampe +lampf +lamps +lamus +lamut +lanae +lanai +lanam +lanao +lanas +lanaz +lance +lanch +lancs +landa +lande +landy +lando +lands +laney +lanes +lange +langi +lango +lanie +lanka +lanky +lanna +lanni +lanny +lansa +lanse +lanta +lanti +lantz +lanum +lanza +laoag +laona +lapaz +lapel +lapin +lapis +lapon +lappa +lapps +lapse +lapsi +larch +lardy +lards +laree +lares +large +largy +largo +laria +larid +larin +laris +larix +larky +larks +laroy +laron +larry +larsa +larto +larue +larum +larus +larva +larve +lasal +lased +laser +lases +laski +lasky +lasko +lassa +lasse +lasso +lassu +lasty +lasts +latah +latax +latch +latea +lated +laten +later +latex +lathe +lathi +lathy +laths +latia +latif +latin +latis +latke +laton +latry +latro +latta +latty +latus +lauan +lauda +laude +lauds +lauer +laugh +lauia +laund +laura +laure +lauri +laury +lauro +lautu +laval +lavas +laved +laven +laver +laves +lavic +lavon +lawai +lawed +lawen +lawes +lawks +lawny +lawns +lawry +lawzy +laxer +laxly +lazar +lazed +lazes +lazio +lazys +lazor +lazos +lbeck +lccis +lccln +lcloc +lcsen +ldmts +leach +leady +leads +leafy +leafs +leahy +leake +leaky +leaks +leany +leann +leans +leant +leaps +leapt +leary +learn +lears +lease +leash +least +leath +leave +leavy +lebam +leban +lebar +lebec +leben +lebes +lebna +lecce +leche +lecia +lecky +ledah +ledda +leddy +leden +ledge +ledgy +ledol +ledum +leech +leeco +leede +leeds +leeke +leeky +leeks +leela +leena +leery +leers +leesa +leese +leeth +leets +lefor +lefty +lefts +legal +leger +leges +legge +leggy +legis +legit +legoa +legra +legua +lehay +lehar +lehet +lehrs +lehua +leics +leyes +leigh +leila +leyla +leiss +leyte +leith +lekha +lelah +leler +lelia +lello +lemay +lemal +leman +lemar +lemel +lemhi +lemma +lemmy +lemna +lemon +lemur +lenad +lenca +lench +lenci +lencl +lends +lendu +lenee +lenes +lenin +lenis +lenka +lenna +lenni +lenny +lenno +lenos +lenox +lense +lenth +lento +lenzi +leola +leoma +leona +leone +leong +leoni +leora +leota +leoti +lepal +lepas +leper +lepid +leppy +lepra +lepre +lepry +lepsy +lepta +lepus +lerna +lerne +leroi +leroy +leros +lerot +lerwa +lesak +lesed +lesgh +lesya +lesiy +lesko +lesli +lesly +lessn +leste +letch +letha +lethe +lethy +letta +lette +letti +letty +letts +letup +leuce +leuch +leuco +leuds +leuma +leund +leung +leupp +levan +levee +levey +level +leven +lever +levet +levin +levir +levis +levit +levon +lewak +lewan +lewes +lewie +lewin +lewis +lewls +lewse +lewth +lewty +lexes +lexia +lexic +lexie +lexis +lezes +lezzy +lfacs +lhary +lhasa +lhota +lyall +liana +liane +liang +liard +lyard +liars +lyart +lyase +libau +libbi +libby +libel +liber +libia +libya +libna +libra +libre +libri +licca +lycea +lycee +licet +licha +lichi +licht +licia +lycia +lycid +licit +licko +licks +lycon +lycus +lidah +lidar +lidda +lydda +liddy +lidia +lydia +lidie +lydie +lydon +lidos +liege +lyell +liens +lyery +liers +liesa +liesh +liest +lieue +lieus +lieut +lieve +lifar +lifey +lifen +lifer +lifia +lifts +ligan +ligas +liger +ligge +light +ligne +ligon +lygus +lihue +lying +liked +liken +lyken +liker +likes +lykes +likin +likud +lilac +lilah +lilas +liles +lyles +lilia +lilla +lille +lilli +lilly +lillo +lilty +lilts +liman +lyman +limas +limax +limba +limbi +limby +limbo +limbs +limbu +limed +limey +limen +limer +limes +limit +limli +limma +limmu +limns +limon +limos +limpa +lymph +limpy +limps +limsy +linac +linch +lynch +lynco +lincs +linda +lynda +lynde +lindi +lindy +lyndy +lindo +linea +lynea +lined +liney +linen +lynen +liner +lines +linet +linga +linge +lingy +lyngi +lingo +lings +linha +linie +linin +linis +linyu +linja +linje +linky +links +lynna +linne +lynne +linns +linon +linos +linsk +linty +lints +linum +linus +lynus +linzy +lions +lyons +lipan +lipic +lipid +lipin +lippe +lippi +lippy +lipps +lipse +lyrae +liras +lyres +lyric +lyrid +liris +lyris +lirot +lyrus +lisan +lisco +lysed +lyses +lisha +lishe +lysin +lysis +lisle +lysol +lisps +lissa +lyssa +lissi +lissy +listy +lists +liszt +litae +litai +litas +litch +liter +lites +litha +lithe +lythe +lithi +lithy +litho +lytic +lytle +litra +litre +litta +lytta +littb +littd +littm +litui +litus +liuka +lived +liven +liver +lives +livia +livid +livor +livre +livvi +livvy +liwan +lizzy +ljoka +llama +llano +lloyd +lludd +loach +loads +loafs +loami +loamy +loams +loans +loasa +loath +loats +loave +lobal +lobar +lobby +lobed +lobel +lobes +lobos +lobus +local +locap +loche +lochi +lochy +lochs +locke +locky +locks +locos +locum +locus +loden +lodes +lodge +lodha +lodie +lodur +loeil +loess +loewe +loewi +loewy +lofti +lofty +lofts +logan +loger +loges +loggy +logia +logic +logie +login +logis +logoi +logos +logue +lohan +lohar +lohse +loyal +loyce +loyde +loins +loire +loise +loiza +lokao +loket +lolly +lolls +loman +lomax +lomta +lonee +loney +loner +longa +longe +longo +longs +lonie +lonna +lonne +lonni +lonny +lonzo +looby +looch +looed +looey +loofa +loofs +looie +looky +looks +looms +loony +loons +loope +loopy +loops +loord +loory +loose +loots +loped +loper +lopes +lopez +loppy +loral +loram +loran +lorca +lordy +lords +lored +loree +lorel +loren +lores +loria +loric +lorie +lorin +loris +lorna +lorne +loros +lorou +lorri +lorry +lorum +lorus +losey +losel +loser +loses +lossa +losse +lossy +lotah +lotan +lotas +lotha +lotic +lotis +lotor +lotos +lotta +lotte +lotti +lotty +lotto +lotus +lotze +louch +louey +lough +louhi +louie +louin +louis +louys +louls +loulu +loupe +loups +lourd +loury +lours +louse +lousy +louth +louty +louts +lovat +loved +lovee +lovey +lovel +lover +loves +lovie +lowan +lowed +lower +lowes +lowis +lowly +lowry +lowse +lowth +loxed +loxes +loxia +loxic +lozar +lpcdf +lrecl +lrida +ltzen +luana +luane +luann +luaus +lubba +lubbi +lubec +luben +lubes +lubet +lubin +lubke +lubow +lubra +lucan +lucas +lucca +lucey +luces +lucet +lucho +lucia +lucic +lucid +lucie +lucio +lucky +lucks +lucre +luddy +ludes +ludic +ludie +ludly +luffa +luffs +lugar +luged +luger +luges +luhey +luian +luigi +luing +luisa +luise +luite +luiza +lukan +lukas +lukey +luket +lukin +lulab +lulav +lulea +lulie +lulli +lully +lulls +lulus +lumen +lumme +lummy +lumpy +lumps +lumut +lunar +lunas +lunch +lunda +lundy +lundt +lunel +lunes +lunet +lunge +lungi +lungy +lungs +lunik +lunka +lunks +lunna +lunts +lupee +lupid +lupin +lupis +lupus +luray +lural +lurch +lured +lurer +lures +lurex +lurid +lurie +lurky +lurks +lurry +lusby +luser +lushy +lusia +lusky +lussi +lusty +lusts +lusus +lutao +lutea +luted +luteo +luter +lutes +luton +lutra +lutts +luxes +luxor +luxus +luzon +lweis +lwoff +maana +maars +mabel +maben +mabes +mabie +mable +macan +macao +macap +macau +macaw +macbs +macco +maced +macey +maceo +macer +maces +macha +mache +machi +machy +macho +machs +macks +macle +macon +macri +macro +macur +madag +madai +madam +maddi +maddy +madea +madel +madge +madia +madid +madly +madoc +madox +madra +madre +maely +maeon +maera +maeve +maewo +mafey +maffa +mafia +mafic +mafoo +magan +magas +magda +magec +maged +magee +magel +magen +mages +maggi +maggy +maggs +maghi +magic +magma +magna +magog +magot +magus +mahal +mahan +mahar +mahat +mahau +mahdi +maher +mahla +mahoe +mahon +mahra +mahri +mahto +mahua +mahwa +mayag +maiah +mayan +mayas +maybe +maice +mayce +maida +mayda +maidy +maids +maidu +mayed +mayey +maier +mayer +mayes +maiga +maiid +maile +maill +mails +mayme +maims +maine +mayne +mains +maint +maynt +mainz +mayon +mayor +mayos +maire +mairs +maise +maist +mayst +maite +maius +maize +majas +majka +major +majos +makah +makar +maker +makes +makos +makua +makuk +malay +malam +malan +malar +malax +malca +malda +malee +malek +maleo +males +malet +malgr +malia +malic +malie +malik +malin +malka +malls +malmy +malmo +malms +maloy +malta +malti +malty +malto +malts +maltz +malum +malus +malva +malwa +mamas +mamba +mambo +mambu +mamey +mamie +mamma +mammy +mamor +mamou +mamry +manak +manal +manas +manat +manba +mancy +manda +mande +mandi +mandy +mands +maned +maneh +manei +maney +manes +manet +manga +mange +mangi +mangy +mango +mania +manya +manic +manid +manie +manis +manit +maniu +manky +manks +manly +manna +manny +manno +manoc +manon +manor +manos +manqu +manse +manso +manta +manti +manty +manto +manue +manuf +manul +manus +manzu +maori +mapau +mapel +mapes +maple +mappy +mapss +maqui +marae +marah +maray +maraj +maral +maras +marat +marbi +march +marci +marcy +marco +marcs +mardi +mardy +marek +maren +mares +marfa +marga +marge +margi +margy +margo +maria +marya +marid +maryd +marie +maril +maryl +marin +maryn +mario +maris +marys +marja +marje +marji +marjy +marka +marko +marks +marla +marli +marly +marlo +marls +marna +marne +marni +maroa +maroc +marok +maron +maror +maros +marou +marra +marry +marse +marsh +marsi +marta +marte +marth +marti +marty +marts +martu +martz +marut +marva +marve +marvy +marzi +masai +masan +masao +maser +masha +mashe +mashy +masks +mason +masry +massa +masse +massy +masty +masts +matai +matar +matax +match +mated +matey +mateo +mater +mates +matha +mathe +mathi +maths +matie +matin +matka +matlo +matra +matsu +matta +matte +matti +matty +matts +matza +matzo +mauby +maude +maudy +mauds +mauer +maugh +mauls +maund +maura +maure +mauri +maury +mauro +mauts +mauve +maven +mavie +mavin +mavis +mavra +mawed +mawky +mawks +maxey +maxia +maxie +maxim +maxis +maxma +mazda +mazed +mazel +mazer +mazes +mazic +mazie +mazon +mazur +mazut +mbaya +mbira +mboya +mbori +mbuba +mcbee +mccoy +mcfee +mcgaw +mcgee +mchen +mcias +mckay +mckee +mckim +mcpas +mcrae +mdacs +mdlle +meach +meade +meads +mealy +meals +meany +means +meant +meara +mears +mease +meath +meaty +meats +meaul +meave +mebos +mecca +mecke +mecon +mecum +medal +medan +medea +media +medic +medii +medin +medio +medit +medle +medoc +medon +medor +meece +meech +meeds +meeks +meers +meese +meeth +meets +megan +megen +meges +meggi +meggy +meggs +mehta +mehul +meier +meyer +meigs +meiji +meile +meill +meiny +meith +mekka +melam +melan +melar +melas +melba +melch +melda +melds +melee +meles +melfa +melia +melic +melie +melis +mella +melli +melly +mello +mells +meloe +melon +melos +melts +melun +melva +memel +memos +menad +menam +menan +menat +mende +mendi +mendy +mends +menes +menic +menis +menlo +menno +menon +menow +mensa +mense +mensk +menta +menus +meous +meows +merak +meras +merat +merca +merce +merch +merci +mercy +merck +merde +mered +merel +merer +meres +merge +mergh +meril +meryl +meris +merit +merks +merla +merle +merls +merna +meroe +merom +merop +meros +merow +merri +merry +merse +merta +merth +mesad +mesal +mesas +mesel +mesem +meshy +mesic +mesne +meson +messe +messy +mesua +metad +metae +metal +metas +meted +metel +meter +metes +metho +meths +metic +metif +metin +metis +metol +metra +metre +metry +metro +metty +metts +metus +metze +meung +meuni +meuse +meute +mewar +mewed +mewer +mewls +mexia +mexsp +mezzo +mhorr +myall +miami +miaou +miaow +miasm +miass +miaul +miauw +micah +mycah +micas +micco +miche +michi +micht +micki +micky +micks +mycol +micra +micro +midas +middy +mider +midge +midgy +midis +midst +miens +myers +miett +miffy +miffs +miggs +might +mikal +mikan +miked +mikey +mikel +mikes +mikie +mikir +mikol +mikra +milam +milan +mylan +mylar +milch +milda +milde +miley +miler +miles +myles +milha +milia +milit +milka +milky +milko +milks +milla +mille +milli +milly +mills +milne +milon +milor +mylor +milos +milpa +milty +milts +mymar +mimas +mimed +mimeo +mimer +mimes +mimic +mimir +mimly +mimsy +mimus +mimzy +minae +minah +mynah +minar +minas +mynas +minbu +mince +minch +mincy +minco +minda +mindi +mindy +minds +mined +miner +mines +minge +mingy +mingo +minho +minya +minie +minim +minis +minke +minks +minna +minne +minni +minny +minoa +minor +minos +minot +minow +minsk +minta +minty +minto +mints +mintz +minum +minus +myoid +myoma +myope +myopy +myops +miqra +mirac +myrah +mirak +miran +mired +mires +mirex +mirid +mirky +mirks +myrle +mirly +mirna +myrna +myron +myrrh +myrta +mirth +mirvs +mirza +misce +miscf +misdo +mysel +miser +mises +misgo +misha +mysia +mysid +mysis +misky +misly +misos +missa +missi +missy +misti +misty +mists +mitch +miter +mites +myths +mitis +myton +mitra +mitre +mitty +mitts +mitua +mitzi +mitzl +mixed +mixen +mixer +mixes +mixie +mixup +mizar +mizen +mizzy +mjico +mlaga +mlitt +mlles +mllly +mmete +mnage +mneme +mnium +mnras +mnurs +moans +moapa +moats +mobby +mobed +mobil +moble +mocha +moche +mochy +mocks +mocoa +modal +model +modem +moder +modes +modge +modie +modif +modla +modoc +modus +moeck +mogan +moggy +mogos +mogul +moham +mohar +mohel +mohos +mohun +mohur +mohwa +moyen +moier +moyer +moile +moyle +moils +moina +moyna +moira +moyra +moire +moise +moism +moist +moity +mojos +mokas +mokes +mokha +mokpo +mokum +molal +molar +molas +moldy +molds +moler +moles +molet +molge +molys +molka +molla +molle +molli +molly +molls +molpe +molto +molts +molus +molvi +momes +momma +momme +mommi +mommy +momos +momus +monad +monah +monal +monas +monax +monck +monda +monde +mondo +monee +money +monel +moner +monet +monge +mongo +monia +monic +monie +moniz +monjo +monks +monny +monon +monos +monro +monte +month +monti +monty +monto +monts +montu +monza +mooch +moody +moods +mooed +moola +mools +moong +moony +moons +moore +moory +moorn +moors +moosa +moose +moost +mooth +moots +mopan +moped +mopey +moper +mopes +mopla +moppy +moppo +mopsy +mopus +moqui +morae +moray +moral +moran +morar +moras +morat +mordy +mordu +mordv +morea +morey +morel +mores +morez +morga +moria +moric +morie +morin +morly +mormo +morna +morne +morns +moroc +moron +moror +moros +morph +morra +morry +morro +morse +morta +morth +morty +morts +morus +mosan +mosby +mosca +mosey +mosel +moser +moses +mosgu +moshe +moshi +mosks +mosra +mossi +mossy +mosso +moste +mosts +mosul +mosur +motas +motch +moted +motey +motel +moter +motes +motet +mothy +moths +motif +motis +moton +motor +motos +motss +motte +motty +motto +motts +mouch +moudy +moues +mould +moule +mouly +mouls +moult +mound +mount +mourn +mouse +mousy +mouth +moved +mover +moves +movie +mowch +mowed +mower +mowha +mowie +mowra +mowse +mowth +moxas +moxee +moxie +mozes +mozos +mozza +mpers +mphil +mphps +mpret +mrida +mrike +mrsrm +msbus +msche +msdos +msent +msfor +msink +msphe +mster +mtbrp +mtech +mtier +mttff +muang +mucic +mucid +mucin +mucky +mucks +mucor +mucro +mucus +mudar +mudde +muddy +mudee +mudir +mudra +muffy +muffs +mufti +mufty +muggy +muggs +mugho +mugil +muhly +muire +muist +mujik +mukri +mukti +mukul +mulch +mulct +muled +muley +mules +mulet +mulga +mulki +mulla +mulls +mulry +mulse +multi +multo +mumbo +mummy +mumms +mumps +mumsy +mumus +munch +muncy +munda +mundy +mundt +munga +munge +mungy +mungo +mungs +munia +munic +munin +munro +muntz +muong +muons +mural +muran +muras +murat +murdo +mured +mures +murex +murga +murid +murky +murks +murly +murmi +murph +murra +murre +murry +murrs +murut +murva +murza +musal +musar +musca +musci +mused +muser +muses +muset +musgu +musha +mushy +music +musie +musil +musit +musky +musks +mussy +musth +musty +musts +mutch +muted +muter +mutes +mutic +muton +mutts +mutus +muzak +muzio +muzzy +mvssp +mvsxa +mweru +naacp +naafi +naara +nabac +nabak +nabal +nabby +nabes +nabis +nabla +nable +nabob +nache +nacho +nacre +nacry +nadab +nadda +nader +nadge +nadia +nadya +nadir +nadja +nador +naevi +nafis +nafud +nagey +nagel +naggy +naght +nagle +nagor +nahma +nahor +nahua +nahum +naiad +nayar +naias +naida +naifs +naily +nails +naima +naira +nairy +nairn +naish +naive +naked +naker +nakir +nakoo +nalda +naldo +naled +nalgo +nally +nalor +naman +namaz +nambe +namby +namda +named +namen +namer +names +namma +nammo +nammu +nampa +namur +nanak +nanas +nance +nanci +nancy +nanda +nandi +nandu +nanes +nanga +nanji +nanmu +nanna +nanni +nanny +nanon +nants +nantz +naoma +naomi +naoto +napal +napap +naper +napes +napoo +nappa +nappe +nappy +narah +narco +narcs +narda +nards +nardu +naren +nares +narev +narew +naric +naris +narka +narky +narks +narra +narva +nasab +nasal +nasat +nasby +nasca +nasch +nasda +nashe +nasho +nasia +nasya +nason +nassa +nassi +nasty +nasua +nasus +natal +natch +nates +nathe +natie +natka +natta +natty +natus +nauch +naumk +naunt +nauru +naval +navar +navel +naves +navet +navew +navig +navis +navvy +nawab +nawle +nawob +naxos +nazar +nazim +nazir +nazis +nberg +ncmos +ndola +neagh +neala +neale +nealy +neall +neaps +nears +neath +neats +nebby +nebel +neche +necho +necia +necks +necro +nedda +neddy +nedra +nedry +needy +needn +needs +neela +neeld +neele +neely +neems +neeps +neese +neeze +nefas +nefen +neffy +neffs +neger +negev +negro +negus +nehru +neifs +neigh +neila +neile +neill +neils +neisa +neysa +neist +neith +neiva +nejdi +nelan +nelda +nelia +nelie +nella +nelle +nelli +nelly +nelse +neman +nemas +nemea +nemos +nenes +nenni +nenta +neoga +neola +neoma +neona +neons +neoza +nepal +neper +nephi +nepil +nepit +nepos +neral +nerdy +nerds +nerin +nerka +nerol +neron +nerta +nerte +nerti +nerty +nerts +nertz +nerva +nerve +nervy +nesac +neses +nessa +nessi +nessy +nesta +nesty +nesto +nests +neter +netop +netta +nette +netti +netty +netts +neuks +neuma +neume +neums +neuss +nevai +nevat +nevel +neven +never +neves +nevil +nevin +nevis +nevoy +nevsa +nevus +newar +newby +newel +newer +newie +newly +newsy +newts +nexal +nexum +nexus +ngaio +ngala +ngapi +ngoko +ngoma +nguni +ngwee +nhlbi +niabi +nyack +nyaya +niais +nyala +niall +nyasa +niata +nibby +nibbs +nicad +nicer +niche +nichy +nicht +nicki +nicky +nicko +nicks +nicol +nicut +nidal +nided +nides +nidge +nidia +nydia +nidor +nidus +niece +niela +niels +niepa +nieve +nific +nifle +nifty +niftp +nigel +niger +nighs +night +nigre +nigua +nihal +nihhi +nihil +nihon +nikau +nikep +nikki +nikky +nikko +nikon +nikos +niles +nilla +nills +nylon +nilot +nilus +nimbi +nimby +nimes +nymil +nymph +nymss +ninde +nines +nynex +ninib +ninja +ninny +ninon +ninos +ninox +ninth +nintu +ninus +ninut +niobe +niolo +nyoro +niort +niota +nipas +nipha +niple +nippy +niris +nirls +nisan +nisdn +nisei +nisen +nishi +nissa +nyssa +nisse +nissy +nisus +nitch +niter +nitid +nitin +niton +nitos +nitre +nitro +nitta +nitti +nitty +nitza +niuan +nival +niven +nivre +niwot +nixed +nixer +nixes +nixie +nyxis +nixon +nizam +nizey +njave +njord +nkomo +nllst +noach +noami +nobby +nobel +nobie +nobis +noble +nobly +nobut +nocht +nocks +nodab +nodal +noddi +noddy +noded +nodes +nodus +noell +noels +noemi +nogai +nogal +nogas +noggs +nohes +nohex +nohow +noyau +noibn +noyes +noily +noils +noint +noyon +noire +noise +noisy +nokta +nolan +nolde +nolie +nolle +nolly +nolos +nolte +nomad +noman +nomap +nomas +nomen +nomes +nomic +nomoi +nomos +nonah +nonas +nonce +nonda +nondo +nones +nonet +nonya +nonic +nonie +nonyl +nonly +nonna +nonny +nooky +nooks +noons +noose +nopal +norad +norah +norby +norco +nordo +norge +noria +noric +norie +norit +norma +normi +normy +norml +norms +norna +norns +norri +norry +norrv +norse +norsk +north +norty +nosed +nosey +noser +noses +nosig +notal +notan +notch +noted +noter +notes +notis +notre +notts +notum +notus +nould +nouma +nouns +novae +novah +novak +novas +novel +novem +novia +novum +novus +noway +nowch +nowed +nowel +nowts +noxal +noxen +noxon +npeel +nroff +nspcc +nspmp +nssdc +nuaaw +nuadu +nubby +nubia +nucal +nucha +nucin +nucla +nuddy +nuder +nudes +nudge +nudie +nudum +nudzh +nuevo +nufud +nugae +nugmw +nuked +nukes +nukus +nullo +nulls +numac +numbs +numda +numen +numis +nummi +numps +numud +nunce +nunch +nunci +nunda +nunes +nunez +nunki +nunky +nunks +nunni +nunry +nuque +nurbs +nurds +nuris +nurly +nurls +nurmi +nurry +nurse +nursy +nusku +nutsy +nutty +nuzzi +nvlap +nvram +oacis +oadal +oaken +oakes +oakie +oakum +oared +oaric +oasal +oases +oasis +oasys +oasts +oaten +oater +oates +oaths +oatis +oaves +obala +obama +obara +obaza +obeah +obeid +obeys +obeli +obeng +oberg +obert +obese +obias +obiit +obion +obits +objet +oblat +obley +obmit +oboes +obola +obole +oboli +obols +obote +obrit +obuda +obulg +ocala +ocana +ocate +occam +occas +occur +ocean +ocher +ochna +ochoa +ochre +ochry +ochro +ociaa +ocyte +ocker +ocnus +ocoee +ocote +ocque +ocrea +octad +octal +octan +octet +octic +octyl +ocuby +oculi +odawa +odder +oddly +odeen +odele +odell +odeon +odets +odeum +odyle +odilo +odyls +odine +odiss +odist +odium +odont +odoom +odors +odour +oecus +oelet +oenin +oesel +ofays +offal +offed +offen +offer +offic +oflem +ofnps +ofori +ofris +often +ofter +oftly +ogams +ogata +ogawa +ogdan +ogden +ogdon +ogeed +ogees +ogema +ogham +oghuz +ogive +ogled +ogler +ogles +ogmic +ogren +ogres +ohara +ohare +ohaus +ohelo +ohias +ohing +ohley +ohmic +ohone +oyama +oyana +oicel +oicks +oidal +oidea +oidia +oyens +oyers +oiled +oiler +oylet +oilla +oinks +oisin +oizys +okays +okapi +okean +okehs +oketo +oklee +okras +okrug +okubo +oland +olavo +olcha +olchi +olden +older +oldie +olean +oleic +olein +olema +olena +olent +oleos +olepy +oleta +oleum +olios +oliva +olive +ollas +ollav +ollen +ollie +olnay +olnee +olney +olnek +ology +olona +olpae +olpes +olsen +olson +olton +olvan +olwen +omagh +omaha +omani +omari +omarr +omasa +omber +ombre +omega +omena +omens +omero +omers +omina +omits +omlah +omnes +omora +omrah +omura +omuta +onaga +onaka +onawa +oncer +onces +oncet +oncia +oncin +onder +oneal +oneco +onega +onego +oneil +onemo +onery +onfre +ongun +onida +onymy +onion +onium +onker +onkos +onlay +onlap +onley +onmun +onset +ontal +ontic +oobit +oohed +oolak +oolly +oomph +oopak +oopod +oorie +ootid +oozed +oozes +oozoa +opahs +opals +opata +opelt +opelu +opens +opeos +opera +opers +ophia +ophic +ophir +ophis +opine +oping +opium +oppen +opsin +opsis +opted +optez +optic +orach +oracy +orage +orale +orals +orang +orans +orant +oraon +orary +orate +orban +orbed +orbic +orbit +orcas +orcin +orcus +orczy +order +ordos +oread +oreas +orelu +orest +orfeo +orgal +organ +orgas +orgel +orgia +orgic +orgue +orial +orian +orias +oribi +orick +oriel +oriya +oryol +orion +orium +oryza +orkey +orlan +orles +orlet +orlin +orlon +orlop +orlos +orlov +orman +ormer +ormuz +ornas +ornes +ornie +ornis +orola +oromo +orono +orose +orosi +orpah +orpha +orpin +orpit +orran +orren +orrin +orris +orrow +orrum +orsay +orsel +orson +orten +ortet +ortho +ortyx +ortiz +ortol +orton +oruro +oruss +orvah +orvan +orvas +orvet +orvie +orvil +orwin +orzos +osage +osaka +osana +osber +oscan +oscar +oscin +oscrl +osdit +osela +osfcw +oshac +oshea +osher +oside +osier +osyka +osirm +osyth +osity +oskar +oslav +osler +osman +osmen +osmic +osmin +osmol +osone +osric +ossal +ossea +osseo +osset +ossia +ossie +ossip +ostap +oster +ostia +ostic +osugi +oswal +oswin +otary +otaru +otate +otego +otero +other +othin +otyak +otila +otina +otium +otkon +otley +otomi +ottar +otter +ottie +ottos +otway +ouabe +oueta +ought +ouida +ouija +oujda +oukia +oulap +ounce +oundy +ounds +ouphe +ouphs +ouray +ourie +ousel +ousia +ousts +outas +outby +outdo +outed +outen +outer +outgo +outhe +outly +outre +ouvre +ouzel +ouzos +ovalo +ovals +ovant +ovapa +ovary +ovate +ovens +overs +overt +ovest +oveta +ovett +ovida +ovile +ovine +ovism +ovist +ovoid +ovoli +ovolo +ovula +ovule +owain +owego +owena +owens +owght +owing +owler +owlet +owned +owner +owsen +owser +oxane +oxboy +oxbow +oxeye +oxfly +oxide +oxids +oxime +oxims +oxley +oxlip +oxman +oxter +ozark +ozena +ozias +ozkum +ozona +ozone +ozzie +paauw +pablo +pabst +pacay +pacas +paced +pacer +paces +pacha +pacht +packs +pacos +pacta +pacts +padda +paddy +paden +padge +padis +padle +padou +padre +padri +padua +padus +paean +paeon +pagan +pagas +paged +pager +pages +paget +pagne +pagod +pagus +pahmi +pahoa +pahos +payed +payee +payen +payer +paige +paiks +pails +paine +payne +payni +pains +paint +payor +pairs +pairt +paisa +paise +pakse +palay +palar +palas +palau +palch +palco +palea +paled +paley +paler +pales +palet +palew +palis +palki +palla +palli +pally +palls +pallu +palma +palmy +palmo +palms +palos +palpi +palps +palsy +palta +palua +palus +pamhy +pamir +pammi +pammy +pampa +panay +panak +panax +panda +pandy +paned +panel +panes +panga +pangi +pangs +panic +panna +panne +panos +panse +pansy +panta +panty +panto +pants +panus +panza +paola +paoli +paolo +papal +papas +papaw +papey +papen +paper +papio +papyr +papke +pappi +pappy +papst +papua +paque +parah +param +paran +parao +paras +parca +parch +parde +pardi +pardy +pardo +pards +pared +parel +paren +parer +pares +pareu +parge +pargo +parhe +parik +paris +parka +parke +parky +parks +parle +parli +parly +parma +parol +paron +paros +parra +parry +parrs +parse +parsi +parte +parti +party +parto +parts +parus +parve +pasay +pasan +pasch +pasco +paseo +pases +pasha +pashm +pasho +pasia +pasis +paske +paski +pasmo +pasol +passe +passy +passo +passu +pasta +paste +pasty +pasto +pasts +pasul +patao +patas +patch +pated +patee +patel +paten +pater +pates +pathe +pathy +paths +patia +patin +patio +patly +patmo +patna +paton +patsy +patta +patte +patti +patty +pattu +paugh +pauky +paula +paule +pauli +pauly +paull +paulo +pause +pauxi +pavan +paved +pavel +paven +paver +paves +pavia +pavid +pavin +pavis +pavla +pawaw +pawed +pawer +pawky +pawls +pawns +paxes +paxon +pazia +pazit +pbxes +pcdos +pcnfs +peace +peach +peage +peags +peake +peaky +peaks +peale +peals +peano +peans +peary +pearl +pears +peart +pease +peasy +peaty +peats +peavy +peban +pecan +pechs +pecht +pecky +pecks +pecos +pedal +pedee +peder +pedes +pedir +pedro +pedum +peeke +peeks +peele +peell +peels +peene +peens +peeoy +peepy +peeps +peery +peers +peert +peetz +peeve +peggi +peggy +peggs +pegma +peine +peins +peise +peize +pekan +pekes +pekin +pekoe +pelag +pelee +peles +pelew +pelfs +pella +pelon +pelta +pelts +peltz +pemba +penal +pence +penda +pendn +pends +penes +pengo +penis +penki +penna +penni +penny +pense +pensy +penta +penup +penza +peony +peons +peper +pepin +pepys +pepla +pepos +peppi +peppy +pepsi +perai +perak +perau +perca +perce +perch +percy +perdy +perdu +perea +peres +perez +peria +peril +peris +perit +perky +perks +perla +perle +perms +perni +peron +perot +perri +perry +perse +perth +perty +perun +pesah +pesek +pesky +pesos +pessa +peste +pesto +pests +petal +petar +petey +peter +petes +petfi +petie +petit +petos +petra +petre +petri +petro +petta +petti +petty +petto +petua +petum +petuu +peuhl +pewee +pewit +pexsi +pfaff +pfalz +pflag +pfosi +pfund +pgntt +phaca +phaea +phaet +phage +phagy +phaye +phaih +phail +phane +phany +phano +pharb +phard +phare +pharm +pharo +pharr +phase +phasm +pheal +pheba +phebe +phene +pheni +pheny +pheon +phial +phies +phyfe +phigs +phila +phyla +phile +phyle +phill +phyll +philo +phylo +phyma +phina +phira +phyre +physa +phyte +phlox +phobe +phoby +phoca +phoma +phone +phony +phono +phons +phora +phore +phose +phoss +photo +phots +phpht +phren +phuts +piaba +piala +piane +piano +pians +piasa +piast +pyatt +piaui +piave +pibal +picae +pical +picao +picas +picco +picea +pyche +pichi +picky +picks +picot +picra +picry +picul +picus +pidan +pydna +piece +piend +piero +piers +piert +pierz +piest +pieta +piete +piety +piezo +pygal +piggy +pight +pigly +pigmy +pygmy +piing +pyins +pikas +piked +pikey +pikel +piker +pikes +pikle +pilaf +pilar +pylar +pylas +pilau +pilaw +pilch +pilea +piled +pilei +piler +piles +pylic +pilin +pilis +pylle +pills +pilmy +pilon +pylon +pilos +pylos +pilot +pilum +pilus +piman +pimas +pimps +pinal +pinas +pinax +pinch +pinda +pindy +pined +piney +pinel +piner +pines +pinge +pingo +pings +pinic +pinyl +pinky +pinko +pinks +pinna +pinny +pinon +pinot +pynot +pinsk +pinta +pinte +pinto +pints +pinup +pinus +pyoid +pions +pyote +piotr +pyotr +pious +pioxe +pipal +piped +pipey +piper +pipes +pipet +pipid +pipil +pipit +pippa +pippy +pippo +pipra +piqua +pique +pyral +pyran +pyres +pyrex +pyric +pirny +pirns +pirog +pirol +pirot +pirri +pyrus +pisay +pisan +pisco +pisek +pishu +pisky +pismo +piste +pisum +pitas +pitau +pitch +pithy +pytho +piths +pitys +piton +pitri +pitta +pitts +piura +piuri +piute +pivot +piwut +pixel +pixes +pyxes +pixie +pyxie +pixys +pyxis +pizor +pizza +place +plack +plaga +plage +playa +plaid +plain +plays +plait +plana +plane +plang +plank +plano +plans +plant +plash +plasm +plass +plast +plata +plate +plath +platy +plato +plats +platt +plaud +plaza +plead +pleas +pleat +plebe +plebs +pleck +pleis +plena +pleny +pleon +plews +pliam +plica +plied +plier +plyer +plies +pliny +plink +plion +pliss +ploat +ploce +ploch +plock +plods +ploid +ploys +plomb +plonk +plook +plops +ploss +plote +plots +plott +plotx +plotz +plouk +plout +plows +pluck +pluff +plugs +pluma +plumb +plume +plumy +plump +plums +plunk +plupf +plush +pluto +pluvi +plzen +pmirr +pneum +poach +pobby +pobox +pocan +poche +pocky +pocks +pocul +pocus +podal +poddy +podes +podex +podge +podgy +podia +podos +poeas +poems +poesy +poets +pogey +pogge +poggy +pogue +pohai +pohna +poyen +poilu +poind +poine +point +poyou +poire +poise +pokan +poked +pokey +poker +pokes +pokie +pokom +polab +polad +polak +polar +poled +poley +poler +poles +polik +polio +polyp +polis +polys +polit +polje +polka +polki +polky +polly +polls +poloi +polos +pomak +pombe +pombo +pomey +pomel +pomes +pomme +pommy +pomos +pompa +pomps +ponca +ponce +pondy +pondo +ponds +poney +pones +ponga +pongo +pongs +ponja +ponos +ponto +ponzo +pooch +poock +poods +poofy +poofs +poohs +pooka +poole +pooli +pooly +pools +poona +poons +poopo +poops +poore +poori +poort +pooty +poove +popal +popes +popie +popov +poppa +poppy +poppo +popsy +poral +porch +pored +poree +porer +pores +poret +porge +porgy +porgo +poria +porky +porks +porny +porno +porns +poros +porry +porta +porte +porty +porto +ports +porum +porus +posca +posed +posey +posen +poser +poses +posho +posit +posix +posse +possy +posts +potch +poter +potoo +potos +potsy +potti +potty +potto +potts +potus +pouce +pouch +poucy +pouff +poufs +poule +poulp +poult +pound +pours +pousy +pouty +pouts +poway +powan +powel +power +powys +powny +poxed +poxes +pozna +pozzy +praam +prady +prado +praha +prahm +prahu +praya +prays +prams +prana +prand +prang +prank +praos +prase +prate +prato +prats +pratt +praus +prawn +prebo +predy +preed +preen +prees +pregl +preys +prela +prent +prepd +prepg +prepn +preps +presa +presb +prese +press +prest +preta +preux +preve +prexy +priam +price +pryce +prich +pricy +prick +pride +pridy +pried +prier +pryer +pries +prigs +prill +prima +prime +primi +primy +primo +primp +prims +prine +prink +print +prinz +prion +prior +pryor +prise +pryse +prism +priss +prius +privy +prize +proal +proas +probe +prodd +prods +proem +profs +progs +proke +prole +prome +promo +proms +prone +prong +proof +propr +props +prore +prose +prosy +proso +pross +prost +prote +proto +proud +prout +prove +provo +prowl +prows +proxy +prude +prudi +prudy +prune +prunt +pruss +pruta +pruth +psalm +psend +pseud +pshav +pshaw +psych +psize +pskov +psoae +psoai +psoas +psora +ptain +ptous +pubal +pubes +pubic +pubis +puces +pucka +pucks +pudda +puddy +pudge +pudgy +pudic +pudsy +puett +puffy +puffs +puget +puggi +puggy +pugil +pugin +puiia +puist +pujah +pujas +puked +puker +pukes +pukka +pulas +puled +puler +pules +pulex +pulik +pulis +pulka +pulli +pulls +pulpy +pulps +pulse +pumas +pumex +pumps +punak +punan +punas +punce +punch +punct +punga +pungi +pungy +pungs +punic +punka +punke +punky +punks +punkt +punny +punta +punti +punty +punto +punts +pupae +pupal +pupas +pupil +pupin +puppy +purau +purda +purdy +pured +puree +purey +purer +purga +purge +purim +purin +puris +purls +purre +purry +purrs +purse +pursy +purty +purus +pusan +pusey +puses +pushy +pussy +putid +puton +putti +putty +putto +putts +qaids +qanat +qatar +qeshm +qiana +qibla +qiyas +qishm +qophs +quack +quadi +quads +quaff +quags +quail +quais +quays +quake +quaky +quale +qualm +quant +quare +quark +quarl +quart +quash +quasi +quass +quata +quate +quauk +quave +quawk +qubba +queak +queal +quean +queen +queer +queet +quegh +queys +quell +quelt +queme +quent +queri +query +querl +quern +quest +queue +quica +quick +quids +quiet +quiff +quila +quill +quilt +quina +quink +quinn +quins +quint +quipo +quips +quipu +quira +quire +quirk +quirl +quirt +quist +quita +quite +quito +quits +quitt +quitu +qulin +quoad +quods +quoin +quoit +quota +quote +quoth +quott +quran +qursh +qurti +raama +raash +rabah +rabal +rabat +rabbi +rabia +rabic +rabid +rabin +rabot +raced +racep +racer +races +rache +racks +racon +radar +raddi +raddy +radek +radha +radie +radii +radio +radix +radke +radly +radom +radon +rafat +rafer +raffe +raffo +raffs +rafik +rafiq +rafty +rafts +rafvr +ragan +ragas +raged +ragee +ragen +rager +rages +raggy +raghu +ragis +rahab +rahal +rahel +rahul +raiae +rayah +rayan +raias +rayas +rayat +raids +rayed +raila +rayle +rails +raina +rayna +raine +rayne +rainy +rains +rayon +raise +rajab +rajah +rajas +rajes +rajiv +rakan +raked +rakee +rakel +raker +rakes +rakia +rakis +rakit +rales +ralli +rally +ralls +ralph +ramah +ramal +raman +rambo +ramed +ramee +ramey +ramer +ramet +ramex +ramie +ramin +rammi +rammy +ramon +ramos +ramps +ramta +ramus +ranal +rance +ranch +randa +randi +randy +randn +rands +ranee +raney +range +rangy +rania +ranid +ranie +ranis +ranit +ranks +ranli +ranna +ranny +ranty +rants +raouf +raoul +raped +raper +rapes +raphe +rapic +rapid +rappe +rarde +rared +rarer +rares +rased +rasen +raser +rases +rashi +rasht +rasia +rasla +rason +raspy +rasps +rasse +rasty +ratal +ratan +ratch +rated +ratel +rater +rates +ratha +rathe +ratib +ratio +raton +ratos +ratti +ratty +ratwa +rauch +rauli +raupo +raved +ravel +raven +raver +raves +ravia +ravid +ravin +raviv +rawer +rawin +rawky +rawly +raxed +raxes +razed +razee +razer +razes +razid +razoo +razor +rcldn +rcmac +rdbms +rdhos +reaal +reace +reach +react +readd +reade +ready +readl +reads +reaks +realm +reals +reamy +reams +reaps +rearm +rears +reasy +reask +reast +reata +reaum +reave +rebab +rebag +rebah +rebak +reban +rebar +rebba +rebbe +rebec +rebed +rebeg +rebel +rebia +rebid +rebob +rebop +rebox +rebud +rebuy +rebus +rebut +recap +recce +reccy +recco +recha +recip +recit +recks +recon +recor +recpt +recta +recti +recto +recur +recut +redan +redby +reddy +redds +reded +redes +redia +redid +redye +redig +redip +redly +redon +redos +redox +redry +redub +redue +redug +redux +reeba +reece +reeda +reede +reedy +reeds +reefy +reefs +reeky +reeks +reels +reena +reese +reesk +reest +reeta +reeva +reeve +refan +refed +refel +refer +reffo +refit +refix +refly +refry +regal +regan +regel +regen +reger +reges +reget +regga +reggi +reggy +regia +regie +regin +regis +regle +regma +regna +regur +rehab +rehem +rehid +rehoe +reice +reich +reify +reifs +reign +reiko +reims +reina +reyna +reine +reink +reyno +reins +reiss +reist +reith +reive +rejig +rekey +relay +relap +relax +reles +relet +relic +relig +relit +rella +relly +relot +reman +remap +remde +remen +remer +remet +remex +remit +remix +remop +rempe +remue +remus +renae +renay +renal +renan +rends +rendu +renee +reneg +renes +renet +renew +renga +renie +renig +renin +renky +renne +renny +rente +rento +rents +rentz +renzo +reoil +reown +repad +repay +repas +repeg +repel +repen +repew +repic +repin +reply +repos +repot +repps +repry +repro +repub +reran +reree +rerig +rerob +rerow +rerub +rerun +resay +resat +resaw +resee +reset +resew +resex +resht +resid +resin +resit +resod +resor +resow +reste +resty +restr +rests +resue +resun +resup +retag +retal +retan +retar +retax +retch +retem +retha +rethe +retia +retie +retin +retip +retma +retry +retro +reube +reuel +reune +reuse +revay +reval +revel +rever +revet +revie +revue +rewan +rewax +rewed +rewey +rewet +rewin +rewon +rexen +rexer +rexes +rfree +rhame +rhamn +rheae +rheas +rheba +rheda +rheen +rheic +rhein +rhema +rheme +rhene +rheta +rhett +rheum +rhila +rhyme +rhymy +rhina +rhynd +rhine +rhyne +rhino +rhyta +rhiza +rhoda +rhode +rhody +rhoea +rhoeo +rhomb +rhona +rhumb +riacs +rials +riana +riane +ryann +riant +riata +ribal +ribat +rybat +ribby +ribes +ricca +rycca +ricci +riced +ricey +ricer +rices +riche +richy +richt +ricin +ricki +ricky +ricks +rydal +riden +rider +ryder +rides +ridge +ridgy +riehl +rieka +riels +riess +rieth +rieti +rifer +riffi +riffs +rifle +rifty +rifts +rigby +rigel +riggs +right +rigid +rigol +rigor +riyal +ryked +riker +rykes +rikki +rilda +riled +riley +ryley +riles +rilke +rille +rilly +rills +rimal +rimas +rimed +rimer +rimes +rimma +rimpi +rinch +rinde +rindy +rinds +rynds +rinee +riner +ringe +ringy +ringo +rings +rinka +rinks +rinna +rinse +rioja +riots +ryots +ripal +riped +ripen +riper +ripes +ripon +ripup +ririe +risco +risen +riser +rises +rishi +risky +risks +rislu +rison +risqu +rissa +risus +ritch +ryter +rites +rithe +ritsu +ritus +ritzy +rival +rived +rivel +riven +river +rives +rivet +rizal +rizar +rizas +rizzi +rizzo +rmats +rnwmp +rnzaf +roach +roads +roald +roams +roana +roane +roann +roans +roark +roars +roast +roath +robbi +robby +robed +rober +robes +robet +robin +robyn +roble +robot +robur +robus +rocca +rocco +roche +rocky +rocks +rocta +roddy +rodeo +rodez +rodge +rodie +rodin +roede +roehm +rogan +roger +roget +rogue +roguy +rohan +rohob +rohun +royal +roybn +roice +royce +roydd +royet +roily +roils +royou +roist +rojak +rojas +rokee +rokey +roker +rolan +roley +roleo +roles +rolfe +rolla +rollo +rolls +rolph +romal +roman +romeo +romeu +romic +romie +rompy +romps +rompu +ronal +ronan +ronco +ronda +ronde +rondi +rondo +ronel +ronen +roneo +ronga +ronin +ronks +ronna +ronne +ronni +ronny +roods +rooed +roofy +roofs +rooke +rooky +rooks +roomy +rooms +roosa +roose +roost +rooti +rooty +roots +roove +roped +ropey +roper +ropes +roque +roral +roric +rorid +rorie +roris +rorke +rorry +rorty +rosal +rosan +rosat +rosco +rosed +rosel +rosen +roser +roses +roset +roshi +rosie +rosin +rosio +rosol +rospa +rosse +rossi +rossy +rotal +rotan +rotas +rotch +roter +rotes +rotge +rotls +rotor +rotos +rotow +rotse +rotta +rotte +rouen +roues +rouge +rough +rougy +rouky +round +roupy +roups +rouse +roust +route +routh +routs +roved +roven +rover +roves +rovet +rovit +rowan +rowdy +rowed +rowel +rowen +rower +rowet +rowte +rowth +rowty +roxie +rozek +rozel +rozet +rozum +rrhea +rsfsr +rspca +rstse +ruach +ruana +rubby +rubel +ruben +rubes +rubia +rubie +rubye +rubin +rubio +ruble +rubor +rubus +ruche +rucky +rucks +rudas +ruddy +rudds +ruder +rudge +rudie +rudin +rudra +ruely +ruelu +ruers +ruffe +ruffi +ruffo +ruffs +rufus +rugae +rugal +rugby +rugen +ruggy +ruyle +ruing +ruins +ruled +ruler +rules +rumal +ruman +rumba +rumbo +rumen +rumex +rumly +rummy +rumor +rumpf +rumpy +rumps +runby +runch +runck +rundi +runed +runer +runes +runge +rungs +runic +runny +runsy +runty +runts +rupee +rupia +rupie +rural +rurik +rusel +ruses +rushy +rusin +rusky +rusks +rusma +rusot +russe +russi +russo +rusty +rusts +rutan +rutch +ruthe +ruthi +ruthy +ruths +rutic +rutyl +rutin +rutty +ruvid +rvsvp +saadi +saare +sabah +sabal +saban +sabba +sabby +sabec +sabed +saber +sabes +sabia +sabik +sabin +sabir +sable +sably +sabme +sabot +sabra +sabre +sabzi +sacae +sacci +sacco +sacha +sachi +sachs +sacks +sacra +sacre +sacry +sacro +sacul +sadat +sades +sadhe +sadhu +sadic +sadie +sadye +sadis +sadly +sadoc +saeed +saeta +safar +safen +safer +safes +safir +safko +sagai +sagan +sagas +sager +sages +saggy +sagle +sagos +sagra +sagum +sahib +sahme +sayal +sayao +saice +sayce +saida +saidi +saids +saied +sayed +sayee +sayer +saiff +saify +saiga +saiid +sayid +saily +sails +saimy +sains +saint +saiph +saire +sayre +sairy +sayst +saite +saith +saito +saiva +sajou +sakai +sakdc +sakel +saker +sakes +sakha +sakis +sakta +sakti +salad +salay +salal +salar +salas +salat +salba +salbu +salem +salep +sales +salet +salic +salim +salix +salle +salli +sally +salma +salmi +salmo +salol +salon +salop +salot +salpa +salps +salsa +salse +salta +salty +salto +salts +salud +salue +salus +salva +salve +salvy +salvo +samaj +samal +saman +samar +samas +samau +samba +sambo +samek +samel +samen +samer +samia +samir +sammy +samoa +samos +sampi +sampo +samps +samto +samul +sanaa +sanai +sanbo +sancy +sanct +sande +sandi +sandy +sands +saned +saner +sanes +sanfo +sanga +sangh +sango +sangu +sanit +sanyu +sanka +sansi +sansk +santa +santy +santo +saone +sapan +sapek +sapid +sapin +sapir +sapit +saple +sapor +sappy +saqib +sarad +saraf +sarah +saran +sardo +sards +saree +sarex +sarge +sargo +sarid +sarif +sarin +sarip +saris +sarky +sarks +sarna +sarod +saron +saros +sarpo +sarra +sarre +sarsa +sarsi +sarto +sarts +saruk +sarum +sarus +sasak +sasan +sasha +sasin +sasse +sassy +satai +satay +satan +sated +satem +sates +satie +satin +satyr +satis +sauba +sauce +sauch +saucy +saudi +sauer +saugh +sauks +sauld +sauls +sault +sauna +saunt +saura +saury +sausa +saute +sauty +sauve +saval +saved +savey +saver +saves +savil +savin +savoy +savor +savvy +sawah +sawan +sawed +sawer +sawny +saxen +saxes +saxis +saxon +sazen +scabs +scada +scadc +scads +scaff +scags +scala +scald +scale +scalf +scaly +scall +scalp +scalt +scalx +scalz +scame +scamp +scams +scand +scans +scant +scape +scare +scarf +scary +scarn +scarp +scars +scart +scase +scats +scatt +scaul +scaum +scaup +scaur +scaut +scawd +scawl +sceat +scelp +scena +scend +scene +scent +scevo +schav +schiz +schmo +schou +schow +schug +schuh +schul +schwa +scian +scifi +scyld +scind +scion +sciot +scyth +sclar +sclat +sclav +sclaw +scler +sclim +scoad +scobs +scoff +scoke +scolb +scold +scomm +scone +scoon +scoop +scoot +scopa +scope +scopy +scopp +scops +score +scorn +scote +scots +scott +scouk +scoup +scour +scout +scove +scovy +scowl +scows +scrab +scrae +scrag +scray +scram +scran +scrap +scrat +scraw +scree +screw +scrim +scrin +scrip +scrit +scrob +scrod +scrog +scroo +scrow +scrub +scruf +scrum +scuba +scudi +scudo +scuds +scuff +scuft +sculk +scull +sculp +scult +scums +scups +scurf +scuse +scuta +scute +scuti +scuts +scutt +sdump +seale +sealy +seals +seami +seamy +seams +seana +seary +sears +seato +seats +seave +seavy +sebat +sebec +sebum +secam +secco +secno +secor +secos +secre +sects +secus +sedan +sedat +sedda +seder +sedge +sedgy +sedum +seech +seedy +seeds +seege +seeks +seely +seels +seema +seems +seena +seenu +seepy +seeps +seers +seeto +segal +segar +seggy +segni +segno +segol +segos +segou +segre +segue +sehyo +seifs +seige +seine +seise +seism +seity +seitz +seize +sekar +seker +sekiu +sekos +selah +selby +selda +seler +selfs +selia +selie +selig +selim +sella +selle +selli +selly +sello +sells +selma +selry +selva +semee +semel +semen +semes +semic +semih +semis +senal +senam +sence +senci +sends +seney +senex +sengi +senit +senna +senor +sensa +sense +senso +sensu +senti +sents +senvy +senza +seora +seoul +sepad +sepal +sepia +sepic +sepoy +seppa +septa +septi +septs +seqed +sequa +seqwl +serab +serac +serai +seral +seram +serau +seraw +sercq +sered +seree +sereh +serer +seres +serfs +serge +sergo +sergt +sergu +seric +serif +serin +serio +serle +sermo +seron +serov +serow +serra +serry +serta +serum +serut +serve +servo +sesia +sesma +sesra +sessa +sesti +setae +setal +sethi +seton +setts +setup +seugh +seuss +sevan +seven +sever +sevik +sevum +sewan +sewar +sewed +sewel +sewen +sewer +sewin +sexed +sexes +sexly +sexto +sexts +sezen +sfoot +sfree +sfrpg +shaba +shack +shade +shady +shado +shads +shaef +shaer +shaff +shaft +shags +shahi +shahs +shaia +shaya +shayn +shays +shaka +shake +shaky +shako +shaks +shaku +shala +shale +shaly +shall +shalt +shama +shame +shamo +shams +shana +shane +shang +shani +shank +shant +shape +shapy +shaps +shara +shard +share +shari +shark +sharl +sharn +sharp +shaul +shaum +shaun +shaup +shave +shawy +shawl +shawm +shawn +shaws +sheaf +sheal +shean +shear +sheas +sheat +sheba +shedd +sheds +shedu +sheeb +sheel +sheen +sheep +sheer +sheet +sheff +sheya +sheik +shela +sheld +shelf +shell +shema +shemu +shena +shend +sheng +shent +sheol +shepp +sherd +shere +sheri +sherj +sherl +sherm +sherr +sheth +sheva +shewa +shewn +shews +shiah +shiai +shyam +shiau +shice +shick +shide +shied +shieh +shiel +shien +shier +shyer +shies +shiff +shift +shiko +shilf +shilh +shily +shyly +shill +shims +shina +shine +shing +shiny +shins +shipp +ships +shipt +shira +shire +shiri +shirk +shirl +shiro +shirr +shirt +shish +shisn +shist +shita +shits +shiva +shive +shivy +shivs +shkod +shlep +shluh +shoad +shoal +shoat +shock +shode +shoed +shoer +shoes +shogi +shogs +shoya +shoyu +shoji +shojo +shola +shole +shona +shone +shood +shooi +shook +shool +shoon +shoop +shoor +shoos +shoot +shope +shops +shore +shorl +shorn +short +shote +shots +shott +shout +shove +showa +showd +showy +shown +shows +shrab +shraf +shrag +shram +shrap +shred +shree +shrew +shrip +shris +shrog +shrpg +shrub +shrug +shtik +shuba +shuck +shuff +shufu +shuha +shull +shuln +shuls +shult +shuma +shune +shunk +shuns +shunt +shure +shurf +shush +shute +shuts +shutz +shuzo +siafu +sials +siana +siang +sibby +sibbs +sibel +siber +sibie +sibyl +sybil +sybyl +sibiu +sible +syble +sybow +sicca +sycee +sicel +sicer +sices +syces +sicht +sicko +sicks +sicle +sycon +sided +sydel +sider +sides +sidhe +sidhu +sidia +sidky +sidle +sidon +sidra +sidth +sidur +siege +siena +syene +siepi +siest +sieur +sieva +sieve +sievy +sifac +syftn +sifts +sigel +sighs +sight +sigil +sigyn +sigla +sigma +signa +signe +signy +signs +sihon +sihun +sikar +siker +sikes +sykes +siket +sikhs +sikko +sikra +silas +sylas +silda +silds +silen +siler +silex +sylid +silyl +silin +sylis +silky +silks +silly +sills +silma +sylni +siloa +silos +sylow +sylph +silty +silts +silva +sylva +simah +simal +syman +simar +simas +simba +symer +simia +simla +simms +simon +symon +sympl +simps +simul +sinae +sinai +sinal +sinan +sinas +since +synch +syncs +sines +sinew +singe +synge +singh +sings +sinhs +sinic +sinis +sinky +sinks +synod +sinon +synop +synth +sinto +sintu +sinus +sioux +siped +siper +sipes +sipid +siple +sippy +sired +siree +siren +syren +sires +siret +sirex +syria +sirih +siris +sirki +sirky +syrma +siroc +sirop +siros +sirra +sirte +sirtf +sirup +syrup +syrus +sisak +sisal +sisco +sisel +sises +sysin +sissy +sissu +sisto +sitao +sitar +sitch +sited +sites +sithe +sitio +sitka +sitra +sitta +situp +situs +siums +siusi +sivan +sivas +siver +sivia +sivie +siwan +sixer +sixes +sixmo +sixte +sixth +sixty +sizal +sizar +sized +sizer +sizes +sjaak +skaff +skags +skail +skair +skald +skart +skate +skats +skean +skeat +skeed +skeeg +skeel +skeen +skeer +skees +skeet +skegs +skeie +skeif +skein +skelf +skell +skelm +skelp +skemp +skene +skeps +skere +skerl +skers +skete +skewy +skewl +skews +skiba +skice +skidi +skids +skied +skyed +skiey +skyey +skien +skier +skies +skiff +skift +skiis +skyla +skill +skime +skimo +skimp +skims +skink +skins +skint +skipp +skips +skyre +skirl +skirp +skirr +skirt +skite +skyte +skits +skive +skivy +skiwy +sklar +skoal +skoot +skout +skros +skuas +skuld +skulk +skull +skulp +skunk +skuse +slaby +slabs +slack +slade +slags +slain +slays +slait +slake +slaky +slamp +slams +slane +slang +slank +slant +slape +slapp +slaps +slare +slart +slash +slask +slate +slath +slaty +slats +slaum +slave +slavi +slavs +slaws +sleck +sleds +sleek +sleep +sleer +sleet +sleys +slemp +slent +slept +slete +slews +slice +slich +slick +slide +slier +slyer +sligo +slyke +slily +slyly +slime +slimy +slims +sline +sling +slink +slipe +slype +slips +slipt +slirt +slish +slite +slits +slive +sliwa +sloan +sloat +slobs +slock +sloes +slogs +sloid +sloyd +slojd +sloka +sloke +slone +slonk +sloom +sloop +sloot +slope +slopy +slops +slorp +slosh +slote +sloth +slots +slour +slows +slubs +slued +sluer +slues +sluff +slugs +sluig +sluit +slump +slums +slung +slunk +slurb +slurp +slurs +slush +sluts +smack +smaik +smail +small +smalm +smalt +smarm +smarr +smart +smasf +smash +smaze +smear +smeek +smeer +smell +smelt +smerk +smeth +smews +smich +smyer +smift +smiga +smile +smily +smils +smirk +smite +smith +smyth +smitt +smock +smogs +smoke +smoky +smoko +smolt +smook +smoos +smoot +smore +smote +smous +smout +smpte +smrgs +smurr +smuse +smush +smuts +snack +snads +snaff +snafu +snags +snail +snake +snaky +snape +snapy +snapp +snaps +snare +snary +snark +snarl +snash +snast +snath +snaws +snead +sneak +sneap +sneck +sneds +sneed +sneer +snell +snerp +snibs +snick +snide +snyed +snies +snyes +sniff +snift +snigs +snipe +snipy +snips +snirl +snirt +snite +snits +snitz +snivy +snobs +snock +snoek +snoga +snogs +snoke +snood +snook +snool +snoop +snoot +snore +snork +snort +snots +snout +snowy +snowk +snowl +snows +sntsc +snubs +snuck +snuff +snugs +snurl +snurp +snurt +soaky +soaks +soane +soapi +soapy +soaps +soary +soars +soave +sobby +sobel +sober +soble +sobor +socha +soche +sochi +socht +socii +socky +socko +socks +socle +sodas +soddy +sodic +sodio +sodom +sodus +sofar +sofas +sofer +sofia +sofie +sofko +softa +softy +softs +sogat +soger +soget +soggy +sohio +soyas +soign +soily +soils +soyot +soyuz +sojas +soken +sokes +sokil +sokol +sokul +solay +solan +solar +soldi +soldo +solea +soled +solei +solen +soler +soles +solfa +solid +solim +solio +solis +solly +solod +solon +solos +solti +soluk +solum +solus +solve +somal +somas +somet +somic +somis +somlo +somma +somme +somne +somni +sonar +soncy +sonde +sonds +sones +sonet +songy +songo +songs +sonia +sonya +sonic +sonja +sonly +sonni +sonny +sonsy +sooey +sooke +sooky +sooks +soong +soony +soord +sooth +sooty +soots +soper +sophi +sophy +sophs +sopor +soppy +soral +soras +sorbs +sorce +sorci +sorda +sordo +sords +soree +sorel +soren +sorer +sores +sorex +sorgo +sorns +sorra +sorry +sorty +sorts +sorus +sorva +sosia +sosie +sosna +soter +sotho +soths +sotie +sotik +sotol +sotos +sough +souks +soule +souly +souls +soult +soulx +soulz +sound +soupy +soups +sourd +soury +sours +sousa +souse +south +souza +sowan +sowar +sowed +sowel +sower +sowle +sowse +sowte +sozin +sozly +spaad +spaak +space +spacy +spack +spada +spade +spado +spaed +spaer +spaes +spahi +spaid +spaik +spail +spain +spair +spays +spait +spake +spald +spale +spall +spalt +spane +spang +spank +spann +spans +sparc +spare +spary +spark +sparm +sparr +spars +spart +spasm +spass +spate +spath +spats +spatz +spave +spawl +spawn +speak +speal +spean +spear +spece +speck +specs +spect +speed +speel +speen +speer +speil +speir +spekt +spelk +spell +spelt +spend +spent +speos +spere +sperm +spete +spewy +spews +sphex +spial +spica +spice +spicy +spick +spics +spied +spiel +spier +spyer +spies +spiff +spike +spiky +spiks +spile +spill +spilt +spina +spine +spiny +spink +spins +spira +spire +spiry +spiro +spirt +spise +spiss +spite +spits +spitz +spivs +splad +splay +splat +splet +split +spock +spode +spohr +spoil +spoke +spoky +spole +spong +spoof +spook +spool +spoom +spoon +spoor +spoot +spore +spory +sport +sposh +sposi +spots +spout +sprad +sprag +spray +sprat +spree +spret +sprew +sprig +sprit +sprod +sprot +sprue +sprug +spuds +spued +spues +spuke +spume +spumy +spung +spunk +spurl +spurn +spurs +spurt +sputa +spute +sqlds +squab +squad +squam +squat +squaw +squeg +squet +squib +squid +squin +squit +squiz +srini +sruti +ssbam +ssing +ssort +sspru +ssrms +sstor +staab +staal +stabs +stacc +stace +staci +stacy +stack +stade +stadt +staff +stage +stagg +stagy +stags +stahl +staia +staid +staig +stail +stain +staio +stair +stays +stake +stale +stalk +stall +stamp +stand +stane +stang +stank +stans +staph +stare +stary +stark +starn +starr +stars +start +starw +stash +state +stats +stauk +staun +staup +stave +stawn +stchi +stead +steak +steal +steam +stean +stech +steck +stedt +steed +steek +steel +steem +steen +steep +steer +stefa +steff +stegh +steid +stein +steyr +stela +stele +stell +stelu +stema +stems +stend +steng +steno +stent +steps +stept +stere +steri +sterk +stern +stero +stert +stets +steve +stevy +stewy +stews +styan +styca +stich +stick +stied +styed +stier +sties +styes +stife +stiff +stijl +stila +stilb +stile +style +styli +still +stilo +stylo +stilt +stilu +stime +stimy +stymy +stine +sting +stink +stint +stion +stipa +stipe +stipo +stire +stirk +stirp +stirs +stite +stith +stive +stivy +stoae +stoai +stoas +stoat +stobs +stock +stoep +stoff +stoga +stogy +stoic +stoit +stoke +stola +stold +stole +stoll +stoma +stome +stomy +stomp +stond +stone +stong +stony +stonk +stood +stoof +stook +stool +stoon +stoop +stoot +stopa +stope +stops +stopt +store +story +stork +storm +storz +stosh +stoss +stott +stoun +stoup +stour +stout +stove +stowe +stowp +stows +strad +strae +strag +stray +stram +strap +straw +stree +strey +strep +stret +strew +stria +strid +strig +strip +strit +strix +stroh +stroy +strom +strop +strow +strpg +strub +strue +strum +strut +struv +stsci +sttng +sttos +stubb +stube +stubs +stuck +stude +study +studs +stuff +stuka +stull +stulm +stump +stums +stung +stunk +stuns +stunt +stupa +stupe +stupp +sturk +sturm +sturt +stuss +suade +suant +suave +subah +subak +subas +subch +suber +subet +subic +subir +subra +subst +succi +sucks +sucre +sudan +suddy +sudds +sudes +sudic +sudor +sudra +sudsy +suede +suelo +suent +suers +suety +suets +sueve +suevi +suffr +sufis +sugan +sugar +sugat +sughs +sugih +sugis +suina +suine +suing +suint +suyog +suist +suite +suity +suits +sukey +sukin +sulci +sulea +sulfa +sulfo +sulka +sulky +sulks +sulla +sully +sulus +sumac +sumak +sumas +sumba +sumen +sumer +summa +sumos +sumph +sumps +sumpt +sunay +sunda +sunet +sunil +sunna +sunni +sunny +sunns +sunol +sunup +suomi +supai +supat +supen +super +supes +suppe +suppl +supra +supvr +surah +sural +suras +surat +surds +sured +surer +sures +surfy +surfs +surge +surgy +surya +surly +surma +surra +surry +surtr +susah +susan +sushi +susie +sussi +sussy +susso +suter +sutor +sutra +sutta +suzan +suzie +suzzy +svelt +svend +svign +svres +swabs +swack +swage +swags +swail +swain +sways +swale +swami +swamy +swamp +swane +swang +swank +swann +swans +swape +swaps +sward +sware +swarf +swarm +swart +swash +swath +swati +swats +swazi +sweal +swear +sweat +swede +sweep +sweer +sweet +swego +sweyn +swell +swelp +swelt +swept +swerd +swick +swift +swigs +swile +swill +swimy +swims +swine +swing +swink +swipe +swipy +swird +swire +swirl +swish +swiss +swith +switz +swive +swizz +swobs +swoln +swonk +swoon +swoop +swope +swops +sword +swore +sworn +swosh +swots +swoun +swung +swure +szell +szold +taata +tabac +tabbi +tabby +tabel +taber +tabes +tabet +tabib +tabic +tabid +tabis +tabla +table +tabog +taboo +tabor +tabus +tabut +tacan +tacca +taccs +taces +tacet +tache +tachi +tachs +tacye +tacit +tacky +tacks +tacna +tacos +tacso +tacts +tadeo +tades +tadio +taegu +taels +taffy +tafia +tagal +tagel +tager +taggy +tagua +tagus +tahar +tahil +tahin +tahoe +tahrs +tahua +taich +tayer +taiga +tayib +tayir +taily +tails +taima +taimi +taine +taino +tains +taint +taipi +taipo +taira +tayra +tairn +taise +taish +taite +tajes +tajik +takao +takar +taked +taken +takeo +taker +takes +takin +takyr +talak +talao +talar +talas +talca +talck +talco +talcs +taled +taler +tales +talia +talya +talie +talio +talis +talys +talky +talks +talli +tally +tallu +talma +talmo +talon +talos +talpa +taluk +talus +tamah +tamal +tamar +tamas +tambo +tamed +tamer +tames +tamil +tamis +tamma +tammi +tammy +tamms +tampa +tamps +tamra +tamul +tamus +tanah +tanak +tanan +tandi +tandy +taney +tanga +tangi +tangy +tango +tangs +tanha +tania +tanya +tanis +tanka +tanks +tanna +tanny +tanoa +tansy +tanta +tanti +tanto +tanzy +taopi +tapaj +tapas +taped +tapen +taper +tapes +tapet +tapia +tapir +tapis +tapit +tapoa +tappa +tapul +taqua +taraf +tarah +tarai +taran +tarau +tarde +tardy +tardo +tarea +tared +tareq +tares +tarfa +targe +tarie +tarim +tarin +taryn +tarmi +tarne +tarns +taroc +tarok +taros +tarot +tarps +tarra +tarre +tarri +tarry +tarrs +tarse +tarsi +tarte +tarty +tarts +tartu +tarve +tasco +tasha +tasia +tasks +tasse +tasso +taste +tasty +tatar +tater +tates +tatia +tatie +tatoo +tatou +tatta +tatty +tatum +taube +taula +tauli +taunt +taupe +taupo +tauri +tauts +tavey +tavel +taver +tavgi +tavia +tavie +tavis +tavoy +tawed +tawer +tawgi +tawie +tawny +tawpi +tawpy +tawse +tawsy +taxed +taxer +taxes +taxin +taxir +taxis +taxon +taxor +taxus +tazia +tazza +tazze +tcawi +tchad +tchai +tchao +tchwi +tcpip +tcsec +tdrss +teach +teaey +teaer +teage +teays +teaks +teals +teams +teary +tears +teart +tease +teasy +teaty +teats +teave +teaze +tebet +techy +tecla +tecon +tecta +tecum +tedda +teddi +teddy +tedge +tedie +tedra +teece +teels +teems +teena +teeny +teens +teest +teeth +teety +teffs +tefft +tegan +tegea +tegua +tehee +teian +teide +teyde +teiid +teilo +teind +teise +tejon +tekya +tekke +tekla +tekoa +telae +telar +teleg +telei +teles +telex +telia +telic +telyn +tella +telly +tello +tells +tellt +teloi +telos +teman +tembe +tembu +temin +temne +tempa +tempe +tempi +tempo +temps +tempt +temse +tenai +tench +tendo +tends +tenes +tenet +tenez +tengu +tenia +tenio +tenla +tenne +tenno +tennu +tenon +tenor +tense +tenso +tenth +tenty +tents +tenue +tepal +tepas +tepee +tepic +tepid +tepoy +tepor +terah +terai +terap +teras +terce +terek +teres +tereu +terga +teria +teryl +teryn +terle +terma +termo +terms +terna +terne +terni +terns +terra +terre +terri +terry +terse +terti +terza +terzo +tesla +tessa +tessi +tessy +testa +teste +testy +tests +tetch +tetel +teths +teton +tetra +tetty +tetum +teuch +teugh +tevet +tevis +tewed +tewel +tewer +tewit +tewly +texan +texas +texon +texts +tezel +tflap +thach +thack +thain +thais +thala +thana +thane +thanh +thank +thant +thapa +thare +tharf +tharm +tharp +thatd +thatn +thats +thave +thawy +thawn +thaws +theah +theat +thebe +theca +theda +theek +theer +theet +theft +thegn +theia +theyd +thein +their +theis +thema +theme +thens +theol +theor +theos +theow +thera +there +therm +thero +these +thess +theta +thete +thewy +thews +thick +thida +thief +thier +thigh +thilk +thill +thyme +thymi +thymy +thyms +thine +thing +think +thins +thiol +thira +third +thirl +thirt +thisn +thoas +thock +thoer +thoft +thoke +thokk +thole +tholi +thoma +thone +thong +thoom +thoon +thora +thore +thorn +thoro +thorp +thorr +thort +those +thoth +thous +thowt +thram +thrap +thraw +thrax +three +threw +thrip +throb +throe +throu +throw +thrum +thruv +thsos +thuan +thuds +thugs +thuya +thuja +thule +thulr +thumb +thump +thund +thung +thunk +thuoc +thury +thurl +thurm +thurs +thurt +tiana +tiang +tiara +tibby +tibbs +tibbu +tibey +tiber +tibet +tibia +tybie +tibur +tical +ticca +ticer +tyche +tycho +ticky +ticks +ticon +ticul +tidal +tiddy +tided +tides +tydie +tieck +tyees +tiena +tiens +tiers +tiffa +tiffi +tiffy +tiffs +tiger +tight +tigon +tigre +tigua +tihwa +tyigh +tyika +tying +tyken +tikes +tykes +tikis +tikka +tikor +tikur +tilak +tilda +tilde +tildi +tildy +tiled +tiler +tyler +tiles +tilia +tilla +tilli +tilly +tillo +tills +tilth +tilty +tilts +tylus +timar +timbe +timbo +timed +timer +times +tymes +timet +timex +timid +timmi +timmy +timms +timne +timon +tymon +timor +timur +tynan +tinct +tinea +tined +tyned +tyner +tines +tynes +tinge +tingi +tings +tinia +tinya +tinne +tinni +tinny +tinsy +tinta +tinty +tints +tioga +tiona +tious +typal +typed +typey +typer +types +typha +typic +tipis +tipit +tiple +typos +tippy +tippo +typps +tipsy +tipup +tiran +tiraz +tired +tyred +tyree +tirer +tires +tyres +tirks +tirls +tirma +tirol +tyrol +tiros +tyros +tyrus +tirve +tirza +tisar +tisbe +tisha +tisic +tyson +tissu +tyste +tisza +titan +titar +titer +tithe +tythe +titis +title +titos +titre +titty +titus +tiver +tiwaz +tizes +tizzy +tlaco +tmema +toady +toads +toano +toast +tobey +tobie +tobye +tobin +tobys +tobit +tobol +tocci +today +toddy +todea +todus +toefl +toffy +toffs +tofte +tofts +tofus +togae +togas +toged +togue +toher +toyah +toyed +toyer +toile +toils +toyon +toyos +toise +toist +toity +toits +tokay +toked +token +toker +tokes +tokio +tokyo +tolan +tolar +tolas +toldo +toled +toler +toles +tolyl +tolly +tolls +tolna +tolus +tomah +toman +tomas +tombe +tombs +tomes +tomia +tomin +tomme +tommi +tommy +tomsk +tonal +tondi +tondo +toned +toney +toner +tones +tonga +tongs +tonia +tonya +tonic +tonie +tonye +tonka +tonna +tonne +tonry +tonto +tonus +toois +tooke +toole +tools +toona +toone +toons +toosh +tooth +toots +topas +topau +topaz +toped +topee +toper +topes +tophe +tophi +tophs +topia +topic +topis +topog +topoi +topos +toppy +topsy +topsl +toque +torah +toral +toran +toras +torch +torcs +tored +torey +tores +toret +toric +torie +torii +torin +torma +toros +torot +torre +torry +torse +torsi +torsk +torso +torta +torte +torto +torts +torun +torus +torve +tosca +tosch +toshy +tossy +total +toted +totem +toter +totes +totty +totum +touch +tough +tould +tound +toure +tourn +tours +tourt +touse +tousy +toust +touts +tovah +tovar +tovey +tovet +towai +towan +towed +towel +tower +towie +towne +towny +towns +towsy +toxey +toxic +toxin +toxon +tozee +tozer +trabu +trace +traci +tracy +track +tract +trade +trady +traer +tragi +traik +trail +train +trays +trait +trama +trame +tramp +trams +trank +tranq +trans +trant +trapa +traps +trapt +trash +trasy +trask +trass +trave +trawl +tread +treas +treat +treed +treey +treen +trees +trefa +trego +treys +treks +trela +trelu +trema +trend +trent +tresa +tress +trest +trets +treva +trews +triac +triad +trial +trias +tribe +trica +trice +trici +trick +tryck +tried +trier +tries +trifa +triga +trigo +trigs +trike +trill +tryma +trims +tryms +trina +trine +trini +triny +trink +trinl +triol +trion +tryon +trior +trios +trypa +tripe +tripy +tripl +tripp +trips +tript +trisa +trish +trist +tryst +trite +trixi +trixy +trmtr +troad +troak +troas +troat +troca +troch +trock +troco +trode +trodi +troff +troft +trogs +troic +trois +troys +troke +troll +tromp +trona +tronc +trone +tronk +troop +troot +trooz +trope +tropy +troth +trots +troue +troup +trout +trouv +trove +trows +trubu +truce +truck +truda +trude +trudi +trudy +trued +truer +trues +truff +trugs +trula +truly +trull +trump +trunk +truro +trush +truss +trust +truth +tsade +tsadi +tsana +tsars +tscpf +tseng +tsere +tsine +tsked +tsort +tsuba +tsubo +tsuda +tsuga +tsuma +tuant +tuarn +tuart +tuath +tubac +tubae +tubal +tubar +tubas +tubba +tubby +tubed +tuber +tubes +tubig +tubik +tucky +tucks +tucum +tudel +tudor +tufan +tufas +tuffs +tufty +tufts +tugui +tuyer +tuism +tukra +tules +tulia +tulip +tulle +tully +tulsa +tulsi +tulua +tumer +tumid +tumli +tummy +tumor +tumps +tunal +tunas +tunca +tuned +tuner +tunes +tunga +tungo +tungs +tunic +tunis +tunka +tunna +tunny +tupek +tupik +tupis +tuple +tuque +turbo +turco +turds +turfy +turfs +turgy +turin +turio +turki +turks +turku +turma +turne +turns +turon +turps +turro +turse +turus +turvy +tushy +tushs +tusky +tusks +tussy +tutee +tutin +tutly +tutor +tutti +tutty +tutto +tutus +tuxes +tuzla +tvtwm +twaes +twain +twait +twale +twalt +twana +twang +twank +twant +twats +tweag +tweak +twedy +tweed +tweeg +tweel +tween +tweet +tweil +twere +twerp +twice +twick +twier +twyer +twigs +twila +twyla +twill +twilt +twimc +twine +twiny +twink +twins +twint +twire +twirk +twirl +twirp +twisp +twist +twite +twits +twitt +twixt +twoes +tzaam +tzars +tzong +uayeb +ualis +uapdu +uaupe +ubald +uball +ubana +uchee +uckia +udale +udall +udasi +udder +udela +udele +udell +udine +udish +uella +ugali +uglis +ugric +uhlan +uhllo +uhuru +uigur +uinal +uinta +ujiji +ukase +ukiah +ulama +ulana +ulane +ulani +ulans +ulcer +ulcus +uledi +uleki +ulema +ulent +ulick +ulita +uller +ullin +ullur +ulman +ulmer +ulmic +ulmin +ulmus +ulnad +ulnae +ulnar +ulnas +uloid +ulose +ulous +ulpan +ulphi +ulric +ultan +ultor +ultra +ultun +uluhi +ululu +ulund +ulvan +ulvas +umaua +umbel +umber +umble +umbos +umbra +umbre +umeko +umest +umiac +umiak +umiaq +umiri +umist +ummps +umont +umped +umpty +umset +unact +unadd +unais +unami +unamo +unapt +unare +unary +unark +unarm +unaus +unbag +unbay +unbar +unbed +unbet +unbid +unbit +unbog +unboy +unbow +unbox +unbud +uncap +uncia +uncio +uncle +uncoy +uncos +uncow +uncus +uncut +undam +undee +unden +under +undid +undye +undig +undim +undis +undog +undon +undry +undro +undub +undue +undug +uneye +unfar +unfed +unfew +unfit +unfix +unfur +ungag +unger +unget +ungka +ungod +ungot +ungum +unhad +unhap +unhat +unhcr +unhex +unhid +unhip +unhit +unhot +uniat +unice +unics +unido +unify +uninn +union +unism +unist +unite +unity +units +unius +unjam +unked +unkey +unken +unket +unkid +unkin +unlay +unlap +unlaw +unlax +unled +unlet +unlid +unlie +unlit +unmad +unman +unmet +unmew +unmix +unnet +unnew +unode +unoil +unold +unona +unorn +unown +unpay +unpeg +unpen +unpin +unpot +unput +unray +unram +unred +unrid +unrig +unrip +unrow +unrra +unrun +unrwa +unsad +unsay +unsee +unset +unsew +unsex +unshy +unsin +unsly +unson +unsty +unsun +untap +untar +untax +untie +until +untin +untop +unurn +unuse +unwan +unwas +unwax +unweb +unwed +unwet +unwig +unwit +unwon +unwry +unzen +unzip +upaya +upali +uparm +upbay +upbar +upbid +upbye +upbow +upbuy +upcry +upcut +updos +updry +upeat +upend +upfly +upget +upham +upher +upjet +uplay +upleg +uplit +upmix +upolu +upped +upper +uppop +uprid +uprip +uprun +upsey +upset +upsit +upson +upsun +upsup +uptie +upton +upupa +upway +upwax +uraei +urali +urana +urare +urari +urase +urata +urate +urbai +urban +urbas +urbia +urbic +urdar +urdee +urdur +ureal +ureas +uredo +ureic +ureid +urena +urent +urged +urgel +urger +urges +uriah +urial +urian +urias +urich +uriel +urien +uriia +urina +urine +urion +urita +urite +urlar +urled +urman +urmia +urnae +urnal +urous +ursae +ursal +ursas +ursel +ursid +urson +ursuk +ursus +urubu +urucu +urutu +usaaf +usafa +usage +usant +usara +usbeg +usbek +usecc +usent +users +ushak +ushas +usher +ushga +usine +using +usita +uskok +uskub +uslta +usnas +usnea +usnic +usnin +usphs +uspto +usque +ussct +uster +usual +usure +usury +usurp +utchy +utees +utend +uteri +utero +utham +uther +utica +utick +utile +utley +utqgs +utrum +utsuk +utter +uvala +uvate +uveal +uveas +uviol +uvito +uvres +uvrou +uvula +uvver +uwcsa +uwton +uxmal +uzara +uzbak +uzbeg +uzbek +uzial +uziel +uzzia +vaasa +vabis +vache +vacla +vacoa +vacua +vacuo +vaden +vader +vadim +vadis +vadso +vaduz +vafio +vagal +vagas +vague +vagus +vails +vaios +vaire +vairy +vairs +vaish +vajra +vakia +vakil +valda +valer +vales +valet +valew +valid +valyl +valle +valli +vally +vallo +valma +valmy +valor +valry +valsa +valse +value +valva +valve +vaman +vamos +vamps +vance +vanda +vaned +vanes +vange +vangs +vania +vanya +vanir +vanna +vanni +vanny +vapid +vapor +vappa +varah +varan +varas +varda +vardy +varec +varia +vario +varix +varna +varro +varus +varve +vasal +vases +vasya +vasos +vasta +vasti +vasty +vasts +vates +vatic +vatus +vaudy +vault +vaunt +vaxbi +vealy +veals +veats +vedas +vedda +vedet +vedic +vedis +vedro +veega +veena +veeps +veery +veers +vefry +vegan +vegas +vegie +vehme +veily +veils +veiny +veins +vejoz +velal +velar +velda +velds +veldt +velic +vella +velma +velon +velte +velum +velva +venae +venal +venda +vends +vened +vener +venez +venge +venie +venin +venlo +venom +venta +vento +vents +venue +venus +vepse +veray +verby +verbs +verda +verde +verdi +verey +verek +verel +verge +vergi +verla +verna +verne +verny +veron +verpa +verre +verry +versa +verse +verso +verst +verty +verts +vertu +verus +verve +vespa +vesta +vesty +vests +vetch +veter +vetus +veuve +vevay +vexed +vexer +vexes +vexil +vezza +vhsic +viage +vials +viand +vyase +vibes +vibex +vibhu +vibix +vicar +vicco +viced +vices +vichy +vicia +vicki +vicky +vycor +vicua +vicus +vidal +vidar +vidda +video +vidya +vidor +vidry +vidua +viens +viers +vieta +vieva +viewy +views +vifda +vigas +vigen +vigia +vigil +vigny +vigor +vying +vijay +vijao +vikki +vikky +vilas +viler +villa +ville +villi +vills +vilma +vimen +vimpa +vinal +vinas +vinca +vince +vinci +vinea +vined +viner +vyner +vines +vinet +vinew +vingt +vinia +vinic +vinie +vinyl +vinna +vinni +vinny +vinod +vinos +vinta +vinum +viola +viole +viols +viper +vipul +viral +viren +vireo +vires +virga +virge +virgy +virgo +virid +virls +viron +virtu +virus +visas +visby +visct +vised +vises +viseu +visie +visit +visne +vison +visor +vista +visto +vitae +vital +vitek +vithi +vitia +vitis +vitra +vitry +vitro +vitta +vitus +viuva +vivas +vivat +vivax +vivda +vivek +viver +vives +vivia +vivid +vivie +vivle +vivos +vivre +vixen +vizir +vizor +vizza +vizzy +vlach +vlada +vladi +vlund +vmcms +vnern +vobis +vocab +vocal +vocat +voces +vodas +voder +vodka +vodum +vodun +vogel +vogie +vogue +vogul +voice +voids +voila +voile +volar +voled +voles +volet +volga +volin +volny +volos +volow +volpe +volta +volte +volti +volto +volts +voltz +volva +volvo +vomer +vomit +vonni +vonny +voraz +votal +votaw +voted +voter +votes +vouch +vouge +vouli +voust +vowed +vowel +vower +vpisu +vraic +vries +vrita +vroom +vrouw +vrows +vtarj +vtern +vucom +vuggy +vuggs +vughs +vulgo +vullo +vulva +waaaf +waacs +waadt +waafs +waals +waapa +waasi +waban +wabby +wacke +wacky +wacko +wacks +wadai +waddy +waded +wader +wades +wadge +wadis +wadna +waefu +waers +wafer +waffs +wafty +wafts +waged +wager +wages +waget +wagga +waggy +wagon +wahoo +wayan +wayao +waifs +waily +wails +waine +wayne +wains +waird +wairs +waise +waist +waite +waits +waive +wajda +wakan +wakas +waked +waken +waker +wakes +wakhi +wakif +wakon +waldo +waled +waley +waler +wales +walke +walks +walla +walli +wally +walls +walsh +walth +walty +waltz +wamel +wames +wamus +wanda +wandy +wando +wands +waned +waney +waner +wanes +wanga +wanky +wanle +wanly +wanna +wanny +wanty +wants +wanze +wappo +warba +warch +warda +warde +wards +wared +warer +wares +warga +warks +warly +warms +warne +warns +warnt +warps +warri +warse +warst +warta +warth +warty +warts +warua +warve +wasat +wasco +wasel +washy +washo +wasir +wasnt +waspy +wasps +wasta +waste +wasty +wasts +watap +watch +water +watha +watts +wauch +waugh +wauks +wauls +wauna +wauns +waura +wausa +wauve +waved +wavey +waver +waves +wawah +wawls +wawro +waxed +waxen +waxer +waxes +wazir +wburg +weaky +weaks +weald +weals +weans +weare +weary +wears +weave +webby +weber +wecht +wedel +wedge +wedgy +weeda +weedy +weeds +weeks +weems +weeny +weens +weent +weepy +weeps +weesh +weest +weety +weets +weeze +wefty +wefts +wehee +weide +weigh +weihs +weill +weird +weirs +weism +weiss +wekas +wekau +welby +welch +welcy +welda +welds +welly +wells +welsh +welty +welts +wemmy +wench +wenda +wende +wendi +wendy +wends +wendt +wenny +wenoa +weott +werby +weren +werra +wersh +wertz +wesco +weser +wesla +wesle +weste +westy +westm +wests +wetly +wever +wevet +wezen +whack +whale +whaly +whall +whalm +whalp +whame +whamo +whamp +whams +whand +whang +whank +whaps +whare +wharf +wharl +wharp +whart +whase +whata +whatd +whats +whauk +whaup +whaur +wheal +wheam +wheat +wheel +wheem +wheen +wheep +wheer +whees +wheft +whein +wheys +wheki +whelk +whelm +whelp +whens +where +whets +whewl +whews +whewt +whiba +which +whick +whids +whiff +whift +whigs +while +whilk +whill +whils +whims +whine +whing +whiny +whins +whips +whipt +whirl +whirr +whirs +whish +whisk +whisp +whiss +whist +white +whyte +whity +whits +whitt +whizz +whoas +whole +wholl +whomp +whone +whoof +whoop +whoot +whops +whore +whory +whorl +whort +whose +whoso +whsle +whuff +whulk +whump +whush +whute +wyano +wiatt +wyatt +wicca +wicht +wicky +wicks +widdy +widen +wider +wides +widow +width +wield +wierd +wiese +wyeth +wifed +wifes +wifie +wigan +wiggy +wight +wiyat +wiyot +wilco +wilda +wilde +wylde +wilds +wiled +wyled +wiley +wilek +wilen +wylen +wyler +wiles +wyles +wilga +wilie +wylie +willa +willi +willy +wills +wilma +wylma +wilno +wilow +wilts +wiltz +wyman +wymer +wimpy +wimps +wince +winch +windy +winds +wynds +windz +wined +winey +winer +wyner +wines +wingy +wingo +wings +winks +winly +winna +winne +wynne +winni +winny +wynny +wynns +winos +wynot +winou +winze +wyola +wiota +wiped +wiper +wipes +wired +wirer +wires +wiros +wirra +wirth +wirtz +wisby +wised +wisen +wiser +wises +wisha +wishy +wisht +wisla +wyson +wysox +wispy +wisps +wisse +wiste +wysty +wists +witan +witch +wited +wyted +witen +wites +wytes +witha +withe +withy +witte +witty +wived +wiver +wyver +wives +wixom +wizen +wizes +wlity +wloka +wmscr +woady +woads +woald +wocas +wodan +woden +wodge +wodgy +woful +wogul +woibe +wojak +wokas +woken +woldy +wolds +wolfe +wolff +wolfy +wolfs +wolly +wolof +wolve +woman +womby +wombs +women +womps +wonga +wonky +wonks +wonna +wonts +woody +woods +wooed +wooer +woofy +woofs +woold +woolf +wooly +wools +woomp +woons +woops +woosh +wootz +woozy +wopsy +wordy +words +worky +works +world +wormy +worms +worry +worse +worst +worth +worts +wotan +wouch +wough +would +wound +woven +wowed +wraac +wraaf +wrack +wracs +wrafs +wramp +wrand +wrang +wrans +wraps +wrapt +wrast +wrath +wrawl +wreak +wreat +wreck +wrens +wrest +wrick +wride +wried +wrier +wryer +wries +wryly +wring +wrist +write +writh +writs +wrive +wroke +wrong +wroot +wrote +wroth +wrung +wudge +wuhan +wulfe +wundt +wunna +wuppe +wurly +wurst +wurtz +wusih +wuzzy +wwops +xdmcp +xebec +xenia +xenic +xenyl +xenon +xenos +xeres +xeric +xerox +xerus +xever +xhosa +xicak +xylan +xylem +xylia +xylic +xylyl +xylol +xylon +xinca +xingu +xyrid +xyris +xysti +xysts +xoana +xport +xsect +xtian +xurel +xview +xviii +xwsds +xxiii +zabra +zabti +zacek +zacks +zadar +zadoc +zadok +zagut +zayat +zayin +zaire +zakah +zakat +zalea +zales +zalma +zaman +zambo +zamia +zamir +zande +zandt +zante +zanza +zanze +zapas +zappa +zappy +zapus +zaque +zarah +zared +zarfs +zarga +zaria +zarla +zawde +zaxes +zazen +zeals +zebec +zebra +zebub +zebus +zeeba +zeena +zeins +zeism +zeiss +zeist +zelda +zelde +zelig +zella +zelle +zelma +zelos +zemmi +zemni +zenas +zenda +zendo +zenia +zenic +zeona +zerda +zerla +zerma +zeros +zesty +zests +zetas +zetes +zetta +zhang +zhmud +ziara +zibet +ziega +ziffs +zygal +zigan +zygon +zihar +zilch +zilla +zills +zimbi +zymes +zymic +zymin +zimme +zimmi +zimmy +zinah +zincy +zinck +zinco +zincs +zineb +zingg +zingy +zings +zinke +zinky +zipah +zippy +zirai +zirak +ziram +ziska +zitah +zitis +ziwot +zizel +zizia +zizit +zlote +zloty +zmudz +zoaea +zoara +zocco +zoeae +zoeal +zoeas +zogan +zohak +zohar +zoila +zoism +zoist +zokor +zoldi +zolle +zolly +zomba +zombi +zonal +zonar +zonda +zoned +zoner +zones +zonic +zonks +zonta +zooid +zooks +zooms +zoona +zoons +zooty +zoque +zorah +zoril +zoris +zorro +zoser +zosma +zowie +zprsn +zrich +zrike +zucco +zudda +zuian +zulch +zullo +zulus +zunis +zupus +zurek +zwart +zweig +zwick diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index 6b85d7e..382bb62 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -14,7 +14,7 @@ async def wordle_collection(mongodb: MongoDatabase) -> MongoCollection: @pytest.fixture async def wordle_game(wordle_collection: MongoCollection, test_user_id: int) -> WordleGame: """Fixture to create a new game""" - game = WordleGame(user_id=test_user_id, word="test") + game = WordleGame(user_id=test_user_id) await wordle_collection.insert_one(game.dict(by_alias=True)) yield game @@ -24,7 +24,7 @@ async def test_start_new_game(wordle_collection: MongoCollection, test_user_id: result = await wordle_collection.find_one({"user_id": test_user_id}) assert result is None - await crud.start_new_wordle_game(wordle_collection, test_user_id, "test") + await crud.start_new_wordle_game(wordle_collection, test_user_id) result = await wordle_collection.find_one({"user_id": test_user_id}) assert result is not None From db499f3742030501512fb45a927ca10308dfb61f Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 21:10:43 +0200 Subject: [PATCH 03/14] WORDLE --- database/constants.py | 2 + database/crud/wordle.py | 58 ++++++-- database/schemas/mongo.py | 24 +++- database/utils/caches.py | 32 +++-- didier/cogs/games.py | 41 +++++- didier/cogs/tasks.py | 21 +-- didier/data/embeds/wordle.py | 130 ++++++++++++++++++ didier/didier.py | 18 +-- didier/utils/types/datetime.py | 2 +- tests/test_database/conftest.py | 11 +- .../test_database/test_crud/test_birthdays.py | 13 +- .../test_database/test_crud/test_currency.py | 13 +- .../test_crud/test_custom_commands.py | 29 ++-- .../test_database/test_crud/test_dad_jokes.py | 3 +- tests/test_database/test_crud/test_tasks.py | 11 +- .../test_crud/test_ufora_announcements.py | 10 +- .../test_crud/test_ufora_courses.py | 8 +- tests/test_database/test_crud/test_users.py | 5 +- tests/test_database/test_crud/test_wordle.py | 14 +- tests/test_database/test_utils/test_caches.py | 6 +- 20 files changed, 350 insertions(+), 101 deletions(-) create mode 100644 database/constants.py create mode 100644 didier/data/embeds/wordle.py diff --git a/database/constants.py b/database/constants.py new file mode 100644 index 0000000..0b0da00 --- /dev/null +++ b/database/constants.py @@ -0,0 +1,2 @@ +WORDLE_GUESS_COUNT = 6 +WORDLE_WORD_LENGTH = 5 diff --git a/database/crud/wordle.py b/database/crud/wordle.py index 4f4e650..9ebd75f 100644 --- a/database/crud/wordle.py +++ b/database/crud/wordle.py @@ -1,32 +1,47 @@ from typing import Optional from database.enums import TempStorageKey -from database.mongo_types import MongoCollection -from database.schemas.mongo import WordleGame +from database.mongo_types import MongoDatabase +from database.schemas.mongo import TemporaryStorage, WordleGame from database.utils.datetime import today_only_date -__all__ = ["get_active_wordle_game", "make_wordle_guess", "start_new_wordle_game"] +__all__ = [ + "get_active_wordle_game", + "make_wordle_guess", + "start_new_wordle_game", + "set_daily_word", + "reset_wordle_games", +] -async def get_active_wordle_game(collection: MongoCollection, user_id: int) -> Optional[WordleGame]: +async def get_active_wordle_game(database: MongoDatabase, user_id: int) -> Optional[WordleGame]: """Find a player's active game""" - return await collection.find_one({"user_id": user_id}) + collection = database[WordleGame.collection()] + result = await collection.find_one({"user_id": user_id}) + if result is None: + return None + + return WordleGame(**result) -async def start_new_wordle_game(collection: MongoCollection, user_id: int) -> WordleGame: +async def start_new_wordle_game(database: MongoDatabase, user_id: int) -> WordleGame: """Start a new game""" + collection = database[WordleGame.collection()] game = WordleGame(user_id=user_id) await collection.insert_one(game.dict(by_alias=True)) return game -async def make_wordle_guess(collection: MongoCollection, user_id: int, guess: str): +async def make_wordle_guess(database: MongoDatabase, user_id: int, guess: str): """Make a guess in your current game""" + collection = database[WordleGame.collection()] await collection.update_one({"user_id": user_id}, {"$push": {"guesses": guess}}) -async def get_daily_word(collection: MongoCollection) -> Optional[str]: +async def get_daily_word(database: MongoDatabase) -> Optional[str]: """Get the word of today""" + collection = database[TemporaryStorage.collection()] + result = await collection.find_one({"key": TempStorageKey.WORDLE_WORD, "day": today_only_date()}) if result is None: return None @@ -34,16 +49,33 @@ async def get_daily_word(collection: MongoCollection) -> Optional[str]: return result["word"] -async def set_daily_word(collection: MongoCollection, word: str): +async def set_daily_word(database: MongoDatabase, word: str, *, forced: bool = False) -> str: """Set the word of today This does NOT overwrite the existing word if there is one, so that it can safely run - on startup every time + on startup every time. + + In order to always overwrite the current word, set the "forced"-kwarg to True. + + Returns the word that was chosen. If one already existed, return that instead. """ - current_word = await get_daily_word(collection) + collection = database[TemporaryStorage.collection()] + + current_word = None if forced else await get_daily_word(collection) if current_word is not None: - return + return current_word await collection.update_one( - {"key": TempStorageKey.WORDLE_WORD}, {"day": today_only_date(), "word": word}, upsert=True + {"key": TempStorageKey.WORDLE_WORD}, {"$set": {"day": today_only_date(), "word": word}}, upsert=True ) + + # Remove all active games + await reset_wordle_games(database) + + return word + + +async def reset_wordle_games(database: MongoDatabase): + """Reset all active games""" + collection = database[WordleGame.collection()] + await collection.drop() diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index 90b9deb..8cbed86 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -4,14 +4,14 @@ from typing import Optional from bson import ObjectId from overrides import overrides -from pydantic import BaseModel, Field, conlist +from pydantic import BaseModel, Field, validator __all__ = ["MongoBase", "TemporaryStorage", "WordleGame"] from database.utils.datetime import today_only_date -class PyObjectId(str): +class PyObjectId(ObjectId): """Custom type for bson ObjectIds""" @classmethod @@ -71,12 +71,20 @@ class TemporaryStorage(MongoCollection): class WordleStats(BaseModel): """Model that holds stats about a player's Wordle performance""" - guess_distribution: conlist(int, min_items=6, max_items=6) = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) + guess_distribution: list[int] = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) last_guess: Optional[datetime.date] = None win_rate: float = 0 current_streak: int = 0 max_streak: int = 0 + @validator("guess_distribution") + def validate_guesses_length(cls, value: list[int]): + """Check that the distribution of guesses is of the correct length""" + if len(value) != 6: + raise ValueError(f"guess_distribution must be length 6, found {len(value)}") + + return value + class GameStats(MongoCollection): """Collection that holds stats about how well a user has performed in games""" @@ -94,10 +102,18 @@ class WordleGame(MongoCollection): """Collection that holds people's active Wordle games""" day: datetime.date = Field(default_factory=lambda: today_only_date()) - guesses: conlist(str, min_items=0, max_items=6) = Field(default_factory=list) + guesses: list[str] = Field(default_factory=list) user_id: int @staticmethod @overrides def collection() -> str: return "wordle" + + @validator("guesses") + def validate_guesses_length(cls, value: list[int]): + """Check that the amount of guesses is of the correct length""" + if len(value) > 6: + raise ValueError(f"guess_distribution must be no longer than 6 elements, found {len(value)}") + + return value diff --git a/database/utils/caches.py b/database/utils/caches.py index edc3a5e..4e35147 100644 --- a/database/utils/caches.py +++ b/database/utils/caches.py @@ -1,14 +1,18 @@ from abc import ABC, abstractmethod +from typing import Generic, TypeVar from overrides import overrides from sqlalchemy.ext.asyncio import AsyncSession -from database.crud import ufora_courses +from database.crud import ufora_courses, wordle +from database.mongo_types import MongoDatabase -__all__ = ["CacheManager"] +__all__ = ["CacheManager", "UforaCourseCache"] + +T = TypeVar("T") -class DatabaseCache(ABC): +class DatabaseCache(ABC, Generic[T]): """Base class for a simple cache-like structure The goal of this class is to store data for Discord auto-completion results @@ -31,10 +35,10 @@ class DatabaseCache(ABC): self.data.clear() @abstractmethod - async def refresh(self, database_session: AsyncSession): + async def refresh(self, database_session: T): """Refresh the data stored in this cache""" - async def invalidate(self, database_session: AsyncSession): + async def invalidate(self, database_session: T): """Invalidate the data stored in this cache""" await self.refresh(database_session) @@ -45,7 +49,7 @@ class DatabaseCache(ABC): return [self.data[index] for index, value in enumerate(self.data_transformed) if query in value] -class UforaCourseCache(DatabaseCache): +class UforaCourseCache(DatabaseCache[AsyncSession]): """Cache to store the names of Ufora courses""" # Also store the aliases to add additional support @@ -90,14 +94,26 @@ class UforaCourseCache(DatabaseCache): return sorted(list(results)) +class WordleCache(DatabaseCache[MongoDatabase]): + """Cache to store the current daily Wordle word""" + + async def refresh(self, database_session: MongoDatabase): + word = await wordle.get_daily_word(database_session) + if word is not None: + self.data = [word] + + class CacheManager: """Class that keeps track of all caches""" ufora_courses: UforaCourseCache + wordle_word: WordleCache def __init__(self): self.ufora_courses = UforaCourseCache() + self.wordle_word = WordleCache() - async def initialize_caches(self, database_session: AsyncSession): + async def initialize_caches(self, postgres_session: AsyncSession, mongo_db: MongoDatabase): """Initialize the contents of all caches""" - await self.ufora_courses.refresh(database_session) + await self.ufora_courses.refresh(postgres_session) + await self.wordle_word.refresh(mongo_db) diff --git a/didier/cogs/games.py b/didier/cogs/games.py index 765448f..ed7c27e 100644 --- a/didier/cogs/games.py +++ b/didier/cogs/games.py @@ -1,9 +1,17 @@ from typing import Optional +import discord from discord import app_commands from discord.ext import commands +from database.constants import WORDLE_GUESS_COUNT, WORDLE_WORD_LENGTH +from database.crud.wordle import ( + get_active_wordle_game, + make_wordle_guess, + start_new_wordle_game, +) from didier import Didier +from didier.data.embeds.wordle import WordleEmbed, WordleErrorEmbed class Games(commands.Cog): @@ -15,11 +23,42 @@ class Games(commands.Cog): self.client = client @app_commands.command(name="wordle", description="Play Wordle!") - async def wordle(self, ctx: commands.Context, guess: Optional[str] = None): + async def wordle(self, interaction: discord.Interaction, guess: Optional[str] = None): """View your active Wordle game If an argument is provided, make a guess instead """ + await interaction.response.defer(ephemeral=True) + + # Guess is wrong length + if guess is not None and len(guess) != 0 and len(guess) != WORDLE_WORD_LENGTH: + embed = WordleErrorEmbed(message=f"Guess must be 5 characters, but `{guess}` is {len(guess)}.").to_embed() + return await interaction.followup.send(embed=embed) + + active_game = await get_active_wordle_game(self.client.mongo_db, interaction.user.id) + if active_game is None: + active_game = await start_new_wordle_game(self.client.mongo_db, interaction.user.id) + + # Trying to guess with a complete game + if len(active_game.guesses) == WORDLE_GUESS_COUNT and guess: + embed = WordleErrorEmbed(message="You've already completed today's Wordle.\nTry again tomorrow!").to_embed() + return await interaction.followup.send(embed=embed) + + # Make a guess + if guess: + # The guess is not a real word + if guess not in self.client.wordle_words: + embed = WordleErrorEmbed(message=f"`{guess}` is not a valid word.").to_embed() + return await interaction.followup.send(embed=embed) + + await make_wordle_guess(self.client.mongo_db, interaction.user.id, guess) + + # Don't re-request the game, we already have it + # just append locally + active_game.guesses.append(guess) + + embed = WordleEmbed(game=active_game, word=self.client.database_caches.wordle_word.data[0]).to_embed() + await interaction.followup.send(embed=embed) async def setup(client: Didier): diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index d37990e..836b28c 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -9,7 +9,6 @@ from database import enums from database.crud.birthdays import get_birthdays_on_day from database.crud.ufora_announcements import remove_old_announcements from database.crud.wordle import set_daily_word -from database.schemas.mongo import TemporaryStorage from didier import Didier from didier.data.embeds.ufora.announcements import fetch_ufora_announcements from didier.decorators.tasks import timed_task @@ -74,12 +73,15 @@ class Tasks(commands.Cog): return await ctx.reply(f"Found no tasks matching `{name}`.", mention_author=False) task = self._tasks[name] - await task() + await task(forced=True) + await self.client.confirm_message(ctx.message) @tasks.loop(time=SOCIALLY_ACCEPTABLE_TIME) @timed_task(enums.TaskType.BIRTHDAYS) - async def check_birthdays(self): + async def check_birthdays(self, **kwargs): """Check if it's currently anyone's birthday""" + _ = kwargs + now = tz_aware_now().date() async with self.client.postgres_session as session: birthdays = await get_birthdays_on_day(session, now) @@ -99,8 +101,10 @@ class Tasks(commands.Cog): @tasks.loop(minutes=10) @timed_task(enums.TaskType.UFORA_ANNOUNCEMENTS) - async def pull_ufora_announcements(self): + async def pull_ufora_announcements(self, **kwargs): """Task that checks for new Ufora announcements & logs them in a channel""" + _ = kwargs + # In theory this shouldn't happen but just to please Mypy if settings.UFORA_RSS_TOKEN is None or settings.UFORA_ANNOUNCEMENTS_CHANNEL is None: return @@ -123,11 +127,11 @@ class Tasks(commands.Cog): await remove_old_announcements(session) @tasks.loop(time=DAILY_RESET_TIME) - async def reset_wordle_word(self): + async def reset_wordle_word(self, forced: bool = False): """Reset the daily Wordle word""" db = self.client.mongo_db - collection = db[TemporaryStorage.collection()] - await set_daily_word(collection, random.choice(self.client.wordle_words)) + word = await set_daily_word(db, random.choice(tuple(self.client.wordle_words))) + self.client.database_caches.wordle_word.data = [word] @reset_wordle_word.before_loop async def _before_reset_wordle_word(self): @@ -145,7 +149,8 @@ class Tasks(commands.Cog): async def setup(client: Didier): """Load the cog - Initially reset the Wordle word + Initially fetch the wordle word from the database, or reset it + if there hasn't been a reset yet today """ cog = Tasks(client) await client.add_cog(cog) diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py new file mode 100644 index 0000000..d012550 --- /dev/null +++ b/didier/data/embeds/wordle.py @@ -0,0 +1,130 @@ +import enum +from dataclasses import dataclass +from typing import Optional + +import discord +from overrides import overrides + +from database.constants import WORDLE_GUESS_COUNT, WORDLE_WORD_LENGTH +from database.schemas.mongo import WordleGame +from didier.data.embeds.base import EmbedBaseModel +from didier.utils.types.datetime import int_to_weekday, tz_aware_now + +__all__ = ["WordleEmbed", "WordleErrorEmbed"] + + +def footer() -> str: + """Create the footer to put on the embed""" + today = tz_aware_now() + return f"{int_to_weekday(today.weekday())} {today.strftime('%d/%m/%Y')}" + + +class WordleColour(enum.IntEnum): + """Colours for the Wordle embed""" + + EMPTY = 0 + WRONG_LETTER = 1 + WRONG_POSITION = 2 + CORRECT = 3 + + +@dataclass +class WordleEmbed(EmbedBaseModel): + """Embed for a Wordle game""" + + game: Optional[WordleGame] + word: str + + def _letter_colour(self, guess: str, index: int) -> WordleColour: + """Get the colour for a guess at a given position""" + if guess[index] == self.word[index]: + return WordleColour.CORRECT + + wrong_letter = 0 + wrong_position = 0 + + for i, letter in enumerate(self.word): + if letter == guess[index] and guess[i] != guess[index]: + wrong_letter += 1 + + if i <= index and guess[i] == guess[index] and letter != guess[index]: + wrong_position += 1 + + if i >= index: + if wrong_position == 0: + break + + if wrong_position <= wrong_letter: + return WordleColour.WRONG_POSITION + + return WordleColour.WRONG_LETTER + + def _guess_colours(self, guess: str) -> list[WordleColour]: + """Create the colour codes for a specific guess""" + return [self._letter_colour(guess, i) for i in range(WORDLE_WORD_LENGTH)] + + def colour_code_game(self) -> list[list[WordleColour]]: + """Create the colour codes for an entire game""" + colours = [] + + # Add all the guesses + if self.game is not None: + for guess in self.game.guesses: + colours.append(self._guess_colours(guess)) + + # Fill the rest with empty spots + for _ in range(WORDLE_GUESS_COUNT - len(colours)): + colours.append([WordleColour.EMPTY] * WORDLE_WORD_LENGTH) + + return colours + + def _colours_to_emojis(self, colours: list[list[WordleColour]]) -> list[list[str]]: + """Turn the colours of the board into Discord emojis""" + colour_map = { + WordleColour.EMPTY: ":white_large_square:", + WordleColour.WRONG_LETTER: ":black_large_square:", + WordleColour.WRONG_POSITION: ":orange_square:", + WordleColour.CORRECT: ":green_square:", + } + + emojis = [] + for row in colours: + emojis.append(list(map(lambda char: colour_map[char], row))) + + return emojis + + @overrides + def to_embed(self) -> discord.Embed: + colours = self.colour_code_game() + + embed = discord.Embed(colour=discord.Colour.blue(), title="Wordle") + emojis = self._colours_to_emojis(colours) + + rows = [" ".join(row) for row in emojis] + + for i, guess in enumerate(self.game.guesses): + rows[i] += f" ||{guess.upper()}||" + + embed.description = "\n\n".join(rows) + + # If the game is over, reveal the word + if len(self.game.guesses) == WORDLE_GUESS_COUNT or (self.game.guesses and self.game.guesses[-1] == self.word): + embed.description += f"\n\nThe word was **{self.word.upper()}**!" + + embed.set_footer(text=footer()) + + return embed + + +@dataclass +class WordleErrorEmbed(EmbedBaseModel): + """Embed to send error messages to the user""" + + message: str + + @overrides + def to_embed(self) -> discord.Embed: + embed = discord.Embed(colour=discord.Colour.red(), title="Wordle") + embed.description = self.message + embed.set_footer(text=footer()) + return embed diff --git a/didier/didier.py b/didier/didier.py index ea0ac41..fc57a83 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -27,7 +27,7 @@ class Didier(commands.Bot): error_channel: discord.abc.Messageable initial_extensions: tuple[str, ...] = () http_session: ClientSession - wordle_words: tuple[str] = tuple() + wordle_words: set[str, ...] = set() def __init__(self): activity = discord.Activity(type=discord.ActivityType.playing, name=settings.DISCORD_STATUS_MESSAGE) @@ -64,14 +64,14 @@ class Didier(commands.Bot): # Load the Wordle dictionary self._load_wordle_words() - # Load extensions - await self._load_initial_extensions() - await self._load_directory_extensions("didier/cogs") - # Initialize caches self.database_caches = CacheManager() async with self.postgres_session as session: - await self.database_caches.initialize_caches(session) + await self.database_caches.initialize_caches(session, self.mongo_db) + + # Load extensions + await self._load_initial_extensions() + await self._load_directory_extensions("didier/cogs") # Create aiohttp session self.http_session = ClientSession() @@ -107,13 +107,9 @@ class Didier(commands.Bot): def _load_wordle_words(self): """Load the dictionary of Wordle words""" - words = [] - with open("files/dictionaries/words-english-wordle.txt", "r") as fp: for line in fp: - words.append(line.strip()) - - self.wordle_words = tuple(words) + self.wordle_words.add(line.strip()) async def resolve_message(self, reference: discord.MessageReference) -> discord.Message: """Fetch a message from a reference""" diff --git a/didier/utils/types/datetime.py b/didier/utils/types/datetime.py index 42f58a9..7b2d5c1 100644 --- a/didier/utils/types/datetime.py +++ b/didier/utils/types/datetime.py @@ -10,7 +10,7 @@ LOCAL_TIMEZONE = zoneinfo.ZoneInfo("Europe/Brussels") def int_to_weekday(number: int) -> str: # pragma: no cover # it's useless to write a test for this """Get the Dutch name of a weekday from the number""" - return ["Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"][number] + return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"][number] def str_to_date(date_str: str, formats: Union[list[str], str] = "%d/%m/%Y") -> datetime.date: diff --git a/tests/test_database/conftest.py b/tests/test_database/conftest.py index b2556c4..dc5ce2a 100644 --- a/tests/test_database/conftest.py +++ b/tests/test_database/conftest.py @@ -1,6 +1,7 @@ import datetime import pytest +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import users from database.schemas.relational import ( @@ -22,7 +23,7 @@ def test_user_id() -> int: @pytest.fixture -async def user(postgres, test_user_id) -> User: +async def user(postgres: AsyncSession, test_user_id: int) -> User: """Fixture to create a user""" _user = await users.get_or_add(postgres, test_user_id) await postgres.refresh(_user) @@ -30,7 +31,7 @@ async def user(postgres, test_user_id) -> User: @pytest.fixture -async def bank(postgres, user: User) -> Bank: +async def bank(postgres: AsyncSession, user: User) -> Bank: """Fixture to fetch the test user's bank""" _bank = user.bank await postgres.refresh(_bank) @@ -38,7 +39,7 @@ async def bank(postgres, user: User) -> Bank: @pytest.fixture -async def ufora_course(postgres) -> UforaCourse: +async def ufora_course(postgres: AsyncSession) -> UforaCourse: """Fixture to create a course""" course = UforaCourse(name="test", code="code", year=1, log_announcements=True) postgres.add(course) @@ -47,7 +48,7 @@ async def ufora_course(postgres) -> UforaCourse: @pytest.fixture -async def ufora_course_with_alias(postgres, ufora_course: UforaCourse) -> UforaCourse: +async def ufora_course_with_alias(postgres: AsyncSession, ufora_course: UforaCourse) -> UforaCourse: """Fixture to create a course with an alias""" alias = UforaCourseAlias(course_id=ufora_course.course_id, alias="alias") postgres.add(alias) @@ -57,7 +58,7 @@ async def ufora_course_with_alias(postgres, ufora_course: UforaCourse) -> UforaC @pytest.fixture -async def ufora_announcement(ufora_course: UforaCourse, postgres) -> UforaAnnouncement: +async def ufora_announcement(postgres: AsyncSession, ufora_course: UforaCourse) -> UforaAnnouncement: """Fixture to create an announcement""" announcement = UforaAnnouncement(course_id=ufora_course.course_id, publication_date=datetime.datetime.now()) postgres.add(announcement) diff --git a/tests/test_database/test_crud/test_birthdays.py b/tests/test_database/test_crud/test_birthdays.py index 21639b1..e7f2242 100644 --- a/tests/test_database/test_crud/test_birthdays.py +++ b/tests/test_database/test_crud/test_birthdays.py @@ -1,13 +1,14 @@ from datetime import datetime, timedelta from freezegun import freeze_time +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import birthdays as crud from database.crud import users from database.schemas.relational import User -async def test_add_birthday_not_present(postgres, user: User): +async def test_add_birthday_not_present(postgres: AsyncSession, user: User): """Test setting a user's birthday when it doesn't exist yet""" assert user.birthday is None @@ -18,7 +19,7 @@ async def test_add_birthday_not_present(postgres, user: User): assert user.birthday.birthday == bd_date -async def test_add_birthday_overwrite(postgres, user: User): +async def test_add_birthday_overwrite(postgres: AsyncSession, user: User): """Test that setting a user's birthday when it already exists overwrites it""" bd_date = datetime.today().date() await crud.add_birthday(postgres, user.user_id, bd_date) @@ -31,7 +32,7 @@ async def test_add_birthday_overwrite(postgres, user: User): assert user.birthday.birthday == new_bd_date -async def test_get_birthday_exists(postgres, user: User): +async def test_get_birthday_exists(postgres: AsyncSession, user: User): """Test getting a user's birthday when it exists""" bd_date = datetime.today().date() await crud.add_birthday(postgres, user.user_id, bd_date) @@ -42,14 +43,14 @@ async def test_get_birthday_exists(postgres, user: User): assert bd.birthday == bd_date -async def test_get_birthday_not_exists(postgres, user: User): +async def test_get_birthday_not_exists(postgres: AsyncSession, user: User): """Test getting a user's birthday when it doesn't exist""" bd = await crud.get_birthday_for_user(postgres, user.user_id) assert bd is None @freeze_time("2022/07/23") -async def test_get_birthdays_on_day(postgres, user: User): +async def test_get_birthdays_on_day(postgres: AsyncSession, user: User): """Test getting all birthdays on a given day""" await crud.add_birthday(postgres, user.user_id, datetime.today().replace(year=2001)) @@ -61,7 +62,7 @@ async def test_get_birthdays_on_day(postgres, user: User): @freeze_time("2022/07/23") -async def test_get_birthdays_none_present(postgres): +async def test_get_birthdays_none_present(postgres: AsyncSession): """Test getting all birthdays when there are none""" birthdays = await crud.get_birthdays_on_day(postgres, datetime.today()) assert len(birthdays) == 0 diff --git a/tests/test_database/test_crud/test_currency.py b/tests/test_database/test_crud/test_currency.py index e5cdc0c..8bd7e8f 100644 --- a/tests/test_database/test_crud/test_currency.py +++ b/tests/test_database/test_crud/test_currency.py @@ -2,13 +2,14 @@ import datetime import pytest from freezegun import freeze_time +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import currency as crud from database.exceptions import currency as exceptions from database.schemas.relational import Bank -async def test_add_dinks(postgres, bank: Bank): +async def test_add_dinks(postgres: AsyncSession, bank: Bank): """Test adding dinks to an account""" assert bank.dinks == 0 await crud.add_dinks(postgres, bank.user_id, 10) @@ -17,7 +18,7 @@ async def test_add_dinks(postgres, bank: Bank): @freeze_time("2022/07/23") -async def test_claim_nightly_available(postgres, bank: Bank): +async def test_claim_nightly_available(postgres: AsyncSession, bank: Bank): """Test claiming nightlies when it hasn't been done yet""" await crud.claim_nightly(postgres, bank.user_id) await postgres.refresh(bank) @@ -28,7 +29,7 @@ async def test_claim_nightly_available(postgres, bank: Bank): @freeze_time("2022/07/23") -async def test_claim_nightly_unavailable(postgres, bank: Bank): +async def test_claim_nightly_unavailable(postgres: AsyncSession, bank: Bank): """Test claiming nightlies twice in a day""" await crud.claim_nightly(postgres, bank.user_id) @@ -39,7 +40,7 @@ async def test_claim_nightly_unavailable(postgres, bank: Bank): assert bank.dinks == crud.NIGHTLY_AMOUNT -async def test_invest(postgres, bank: Bank): +async def test_invest(postgres: AsyncSession, bank: Bank): """Test investing some Dinks""" bank.dinks = 100 postgres.add(bank) @@ -52,7 +53,7 @@ async def test_invest(postgres, bank: Bank): assert bank.invested == 20 -async def test_invest_all(postgres, bank: Bank): +async def test_invest_all(postgres: AsyncSession, bank: Bank): """Test investing all dinks""" bank.dinks = 100 postgres.add(bank) @@ -65,7 +66,7 @@ async def test_invest_all(postgres, bank: Bank): assert bank.invested == 100 -async def test_invest_more_than_owned(postgres, bank: Bank): +async def test_invest_more_than_owned(postgres: AsyncSession, bank: Bank): """Test investing more Dinks than you own""" bank.dinks = 100 postgres.add(bank) diff --git a/tests/test_database/test_crud/test_custom_commands.py b/tests/test_database/test_crud/test_custom_commands.py index 88810d4..6f141bc 100644 --- a/tests/test_database/test_crud/test_custom_commands.py +++ b/tests/test_database/test_crud/test_custom_commands.py @@ -1,5 +1,6 @@ import pytest from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import custom_commands as crud from database.exceptions.constraints import DuplicateInsertException @@ -7,7 +8,7 @@ from database.exceptions.not_found import NoResultFoundException from database.schemas.relational import CustomCommand -async def test_create_command_non_existing(postgres): +async def test_create_command_non_existing(postgres: AsyncSession): """Test creating a new command when it doesn't exist yet""" await crud.create_command(postgres, "name", "response") @@ -16,7 +17,7 @@ async def test_create_command_non_existing(postgres): assert commands[0].name == "name" -async def test_create_command_duplicate_name(postgres): +async def test_create_command_duplicate_name(postgres: AsyncSession): """Test creating a command when the name already exists""" await crud.create_command(postgres, "name", "response") @@ -24,7 +25,7 @@ async def test_create_command_duplicate_name(postgres): await crud.create_command(postgres, "name", "other response") -async def test_create_command_name_is_alias(postgres): +async def test_create_command_name_is_alias(postgres: AsyncSession): """Test creating a command when the name is taken by an alias""" await crud.create_command(postgres, "name", "response") await crud.create_alias(postgres, "name", "n") @@ -33,7 +34,7 @@ async def test_create_command_name_is_alias(postgres): await crud.create_command(postgres, "n", "other response") -async def test_create_alias(postgres): +async def test_create_alias(postgres: AsyncSession): """Test creating an alias when the name is still free""" command = await crud.create_command(postgres, "name", "response") await crud.create_alias(postgres, command.name, "n") @@ -43,13 +44,13 @@ async def test_create_alias(postgres): assert command.aliases[0].alias == "n" -async def test_create_alias_non_existing(postgres): +async def test_create_alias_non_existing(postgres: AsyncSession): """Test creating an alias when the command doesn't exist""" with pytest.raises(NoResultFoundException): await crud.create_alias(postgres, "name", "alias") -async def test_create_alias_duplicate(postgres): +async def test_create_alias_duplicate(postgres: AsyncSession): """Test creating an alias when another alias already has this name""" command = await crud.create_command(postgres, "name", "response") await crud.create_alias(postgres, command.name, "n") @@ -58,7 +59,7 @@ async def test_create_alias_duplicate(postgres): await crud.create_alias(postgres, command.name, "n") -async def test_create_alias_is_command(postgres): +async def test_create_alias_is_command(postgres: AsyncSession): """Test creating an alias when the name is taken by a command""" await crud.create_command(postgres, "n", "response") command = await crud.create_command(postgres, "name", "response") @@ -67,7 +68,7 @@ async def test_create_alias_is_command(postgres): await crud.create_alias(postgres, command.name, "n") -async def test_create_alias_match_by_alias(postgres): +async def test_create_alias_match_by_alias(postgres: AsyncSession): """Test creating an alias for a command when matching the name to another alias""" command = await crud.create_command(postgres, "name", "response") await crud.create_alias(postgres, command.name, "a1") @@ -75,21 +76,21 @@ async def test_create_alias_match_by_alias(postgres): assert alias.command == command -async def test_get_command_by_name_exists(postgres): +async def test_get_command_by_name_exists(postgres: AsyncSession): """Test getting a command by name""" await crud.create_command(postgres, "name", "response") command = await crud.get_command(postgres, "name") assert command is not None -async def test_get_command_by_cleaned_name(postgres): +async def test_get_command_by_cleaned_name(postgres: AsyncSession): """Test getting a command by the cleaned version of the name""" command = await crud.create_command(postgres, "CAPITALIZED NAME WITH SPACES", "response") found = await crud.get_command(postgres, "capitalizednamewithspaces") assert command == found -async def test_get_command_by_alias(postgres): +async def test_get_command_by_alias(postgres: AsyncSession): """Test getting a command by an alias""" command = await crud.create_command(postgres, "name", "response") await crud.create_alias(postgres, command.name, "a1") @@ -99,12 +100,12 @@ async def test_get_command_by_alias(postgres): assert command == found -async def test_get_command_non_existing(postgres): +async def test_get_command_non_existing(postgres: AsyncSession): """Test getting a command when it doesn't exist""" assert await crud.get_command(postgres, "name") is None -async def test_edit_command(postgres): +async def test_edit_command(postgres: AsyncSession): """Test editing an existing command""" command = await crud.create_command(postgres, "name", "response") await crud.edit_command(postgres, command.name, "new name", "new response") @@ -112,7 +113,7 @@ async def test_edit_command(postgres): assert command.response == "new response" -async def test_edit_command_non_existing(postgres): +async def test_edit_command_non_existing(postgres: AsyncSession): """Test editing a command that doesn't exist""" with pytest.raises(NoResultFoundException): await crud.edit_command(postgres, "name", "n", "r") diff --git a/tests/test_database/test_crud/test_dad_jokes.py b/tests/test_database/test_crud/test_dad_jokes.py index 22c28c2..f34d0fa 100644 --- a/tests/test_database/test_crud/test_dad_jokes.py +++ b/tests/test_database/test_crud/test_dad_jokes.py @@ -1,10 +1,11 @@ from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import dad_jokes as crud from database.schemas.relational import DadJoke -async def test_add_dad_joke(postgres): +async def test_add_dad_joke(postgres: AsyncSession): """Test creating a new joke""" statement = select(DadJoke) result = (await postgres.execute(statement)).scalars().all() diff --git a/tests/test_database/test_crud/test_tasks.py b/tests/test_database/test_crud/test_tasks.py index c4c7ba0..b13b221 100644 --- a/tests/test_database/test_crud/test_tasks.py +++ b/tests/test_database/test_crud/test_tasks.py @@ -3,6 +3,7 @@ import datetime import pytest from freezegun import freeze_time from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import tasks as crud from database.enums import TaskType @@ -16,7 +17,7 @@ def task_type() -> TaskType: @pytest.fixture -async def task(postgres, task_type: TaskType) -> Task: +async def task(postgres: AsyncSession, task_type: TaskType) -> Task: """Fixture to create a task""" task = Task(task=task_type) postgres.add(task) @@ -24,21 +25,21 @@ async def task(postgres, task_type: TaskType) -> Task: return task -async def test_get_task_by_enum_present(postgres, task: Task, task_type: TaskType): +async def test_get_task_by_enum_present(postgres: AsyncSession, task: Task, task_type: TaskType): """Test getting a task by its enum type when it exists""" result = await crud.get_task_by_enum(postgres, task_type) assert result is not None assert result == task -async def test_get_task_by_enum_not_present(postgres, task_type: TaskType): +async def test_get_task_by_enum_not_present(postgres: AsyncSession, task_type: TaskType): """Test getting a task by its enum type when it doesn't exist""" result = await crud.get_task_by_enum(postgres, task_type) assert result is None @freeze_time("2022/07/24") -async def test_set_execution_time_exists(postgres, task: Task, task_type: TaskType): +async def test_set_execution_time_exists(postgres: AsyncSession, task: Task, task_type: TaskType): """Test setting the execution time of an existing task""" await postgres.refresh(task) assert task.previous_run is None @@ -49,7 +50,7 @@ async def test_set_execution_time_exists(postgres, task: Task, task_type: TaskTy @freeze_time("2022/07/24") -async def test_set_execution_time_doesnt_exist(postgres, task_type: TaskType): +async def test_set_execution_time_doesnt_exist(postgres: AsyncSession, task_type: TaskType): """Test setting the execution time of a non-existing task""" statement = select(Task).where(Task.task == task_type) results = list((await postgres.execute(statement)).scalars().all()) diff --git a/tests/test_database/test_crud/test_ufora_announcements.py b/tests/test_database/test_crud/test_ufora_announcements.py index 1aa45ee..34f4222 100644 --- a/tests/test_database/test_crud/test_ufora_announcements.py +++ b/tests/test_database/test_crud/test_ufora_announcements.py @@ -1,16 +1,18 @@ import datetime +from sqlalchemy.ext.asyncio import AsyncSession + from database.crud import ufora_announcements as crud from database.schemas.relational import UforaAnnouncement, UforaCourse -async def test_get_courses_with_announcements_none(postgres): +async def test_get_courses_with_announcements_none(postgres: AsyncSession): """Test getting all courses with announcements when there are none""" results = await crud.get_courses_with_announcements(postgres) assert len(results) == 0 -async def test_get_courses_with_announcements(postgres): +async def test_get_courses_with_announcements(postgres: AsyncSession): """Test getting all courses with announcements""" course_1 = UforaCourse(name="test", code="code", year=1, log_announcements=True) course_2 = UforaCourse(name="test2", code="code2", year=1, log_announcements=False) @@ -22,14 +24,14 @@ async def test_get_courses_with_announcements(postgres): assert results[0] == course_1 -async def test_create_new_announcement(ufora_course: UforaCourse, postgres): +async def test_create_new_announcement(postgres: AsyncSession, ufora_course: UforaCourse): """Test creating a new announcement""" await crud.create_new_announcement(postgres, 1, course=ufora_course, publication_date=datetime.datetime.now()) await postgres.refresh(ufora_course) assert len(ufora_course.announcements) == 1 -async def test_remove_old_announcements(ufora_announcement: UforaAnnouncement, postgres): +async def test_remove_old_announcements(postgres: AsyncSession, ufora_announcement: UforaAnnouncement): """Test removing all stale announcements""" course = ufora_announcement.course ufora_announcement.publication_date -= datetime.timedelta(weeks=2) diff --git a/tests/test_database/test_crud/test_ufora_courses.py b/tests/test_database/test_crud/test_ufora_courses.py index 34748c0..140bc4a 100644 --- a/tests/test_database/test_crud/test_ufora_courses.py +++ b/tests/test_database/test_crud/test_ufora_courses.py @@ -1,20 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from database.crud import ufora_courses as crud from database.schemas.relational import UforaCourse -async def test_get_course_by_name_exact(postgres, ufora_course: UforaCourse): +async def test_get_course_by_name_exact(postgres: AsyncSession, ufora_course: UforaCourse): """Test getting a course by its name when the query is an exact match""" match = await crud.get_course_by_name(postgres, "Test") assert match == ufora_course -async def test_get_course_by_name_substring(postgres, ufora_course: UforaCourse): +async def test_get_course_by_name_substring(postgres: AsyncSession, ufora_course: UforaCourse): """Test getting a course by its name when the query is a substring""" match = await crud.get_course_by_name(postgres, "es") assert match == ufora_course -async def test_get_course_by_name_alias(postgres, ufora_course_with_alias: UforaCourse): +async def test_get_course_by_name_alias(postgres: AsyncSession, ufora_course_with_alias: UforaCourse): """Test getting a course by its name when the name doesn't match, but the alias does""" match = await crud.get_course_by_name(postgres, "ali") assert match == ufora_course_with_alias diff --git a/tests/test_database/test_crud/test_users.py b/tests/test_database/test_crud/test_users.py index e852298..96d3383 100644 --- a/tests/test_database/test_crud/test_users.py +++ b/tests/test_database/test_crud/test_users.py @@ -1,10 +1,11 @@ from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from database.crud import users as crud from database.schemas.relational import User -async def test_get_or_add_non_existing(postgres): +async def test_get_or_add_non_existing(postgres: AsyncSession): """Test get_or_add for a user that doesn't exist""" await crud.get_or_add(postgres, 1) statement = select(User) @@ -15,7 +16,7 @@ async def test_get_or_add_non_existing(postgres): assert res[0].nightly_data is not None -async def test_get_or_add_existing(postgres): +async def test_get_or_add_existing(postgres: AsyncSession): """Test get_or_add for a user that does exist""" user = await crud.get_or_add(postgres, 1) bank = user.bank diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index 382bb62..a1720de 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -19,24 +19,24 @@ async def wordle_game(wordle_collection: MongoCollection, test_user_id: int) -> yield game -async def test_start_new_game(wordle_collection: MongoCollection, test_user_id: int): +async def test_start_new_game(mongodb: MongoDatabase, wordle_collection: MongoCollection, test_user_id: int): """Test starting a new game""" result = await wordle_collection.find_one({"user_id": test_user_id}) assert result is None - await crud.start_new_wordle_game(wordle_collection, test_user_id) + await crud.start_new_wordle_game(mongodb, test_user_id) result = await wordle_collection.find_one({"user_id": test_user_id}) assert result is not None -async def test_get_active_wordle_game_none(wordle_collection: MongoCollection, test_user_id: int): +async def test_get_active_wordle_game_none(mongodb: MongoDatabase, test_user_id: int): """Test getting an active game when there is none""" - result = await crud.get_active_wordle_game(wordle_collection, test_user_id) + result = await crud.get_active_wordle_game(mongodb, test_user_id) assert result is None -async def test_get_active_wordle_game(wordle_collection: MongoCollection, wordle_game: WordleGame): +async def test_get_active_wordle_game(mongodb: MongoDatabase, wordle_game: WordleGame): """Test getting an active game when there is none""" - result = await crud.get_active_wordle_game(wordle_collection, wordle_game.user_id) - assert result == wordle_game.dict(by_alias=True) + result = await crud.get_active_wordle_game(mongodb, wordle_game.user_id) + assert result.dict(by_alias=True) == wordle_game.dict(by_alias=True) diff --git a/tests/test_database/test_utils/test_caches.py b/tests/test_database/test_utils/test_caches.py index 69a6ff2..b613737 100644 --- a/tests/test_database/test_utils/test_caches.py +++ b/tests/test_database/test_utils/test_caches.py @@ -1,8 +1,10 @@ +from sqlalchemy.ext.asyncio import AsyncSession + from database.schemas.relational import UforaCourse from database.utils.caches import UforaCourseCache -async def test_ufora_course_cache_refresh_empty(postgres, ufora_course_with_alias: UforaCourse): +async def test_ufora_course_cache_refresh_empty(postgres: AsyncSession, ufora_course_with_alias: UforaCourse): """Test loading the data for the Ufora Course cache when it's empty""" cache = UforaCourseCache() await cache.refresh(postgres) @@ -12,7 +14,7 @@ async def test_ufora_course_cache_refresh_empty(postgres, ufora_course_with_alia assert cache.aliases == {"alias": "test"} -async def test_ufora_course_cache_refresh_not_empty(postgres, ufora_course_with_alias: UforaCourse): +async def test_ufora_course_cache_refresh_not_empty(postgres: AsyncSession, ufora_course_with_alias: UforaCourse): """Test loading the data for the Ufora Course cache when it's not empty anymore""" cache = UforaCourseCache() cache.data = ["Something"] From 4a137bcad87fbafe8df814dec48dab091e50410f Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 21:25:07 +0200 Subject: [PATCH 04/14] Allow force-resetting the game --- database/schemas/mongo.py | 15 +++++++++++++++ didier/cogs/games.py | 4 +++- didier/cogs/tasks.py | 2 +- didier/data/embeds/wordle.py | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index 8cbed86..d065bd4 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -6,6 +6,8 @@ from bson import ObjectId from overrides import overrides from pydantic import BaseModel, Field, validator +from database.constants import WORDLE_GUESS_COUNT + __all__ = ["MongoBase", "TemporaryStorage", "WordleGame"] from database.utils.datetime import today_only_date @@ -117,3 +119,16 @@ class WordleGame(MongoCollection): raise ValueError(f"guess_distribution must be no longer than 6 elements, found {len(value)}") return value + + def is_game_over(self, word: str) -> bool: + """Check if the current game is over""" + # No guesses yet + if not self.guesses: + return False + + # Max amount of guesses allowed + if len(self.guesses) == WORDLE_GUESS_COUNT: + return True + + # Found the correct word + return self.guesses[-1] == word diff --git a/didier/cogs/games.py b/didier/cogs/games.py index ed7c27e..caefb93 100644 --- a/didier/cogs/games.py +++ b/didier/cogs/games.py @@ -47,10 +47,12 @@ class Games(commands.Cog): # Make a guess if guess: # The guess is not a real word - if guess not in self.client.wordle_words: + if guess.lower() not in self.client.wordle_words: embed = WordleErrorEmbed(message=f"`{guess}` is not a valid word.").to_embed() return await interaction.followup.send(embed=embed) + guess = guess.lower() + await make_wordle_guess(self.client.mongo_db, interaction.user.id, guess) # Don't re-request the game, we already have it diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index 836b28c..8260e2e 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -130,7 +130,7 @@ class Tasks(commands.Cog): async def reset_wordle_word(self, forced: bool = False): """Reset the daily Wordle word""" db = self.client.mongo_db - word = await set_daily_word(db, random.choice(tuple(self.client.wordle_words))) + word = await set_daily_word(db, random.choice(tuple(self.client.wordle_words)), forced=forced) self.client.database_caches.wordle_word.data = [word] @reset_wordle_word.before_loop diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index d012550..daf3314 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -108,7 +108,7 @@ class WordleEmbed(EmbedBaseModel): embed.description = "\n\n".join(rows) # If the game is over, reveal the word - if len(self.game.guesses) == WORDLE_GUESS_COUNT or (self.game.guesses and self.game.guesses[-1] == self.word): + if self.game.is_game_over(self.word): embed.description += f"\n\nThe word was **{self.word.upper()}**!" embed.set_footer(text=footer()) From bdaf8a1dc5898842e2848001551ff7891a87d3a6 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 21:32:26 +0200 Subject: [PATCH 05/14] Add kwargs to embeds, hide wordle spoilers --- didier/data/embeds/base.py | 2 +- didier/data/embeds/google/google_search.py | 2 +- didier/data/embeds/ufora/announcements.py | 2 +- didier/data/embeds/urban_dictionary.py | 2 +- didier/data/embeds/wordle.py | 19 +++++++++++-------- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/didier/data/embeds/base.py b/didier/data/embeds/base.py index 8cab519..45be7a0 100644 --- a/didier/data/embeds/base.py +++ b/didier/data/embeds/base.py @@ -13,7 +13,7 @@ class EmbedBaseModel(ABC): """Abstract base class for a model that can be turned into a Discord embed""" @abstractmethod - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs: dict) -> discord.Embed: """Turn this model into a Discord embed""" raise NotImplementedError diff --git a/didier/data/embeds/google/google_search.py b/didier/data/embeds/google/google_search.py index 7d31859..dcbad9c 100644 --- a/didier/data/embeds/google/google_search.py +++ b/didier/data/embeds/google/google_search.py @@ -33,7 +33,7 @@ class GoogleSearch(EmbedBaseModel): return embed @overrides - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs: dict) -> discord.Embed: if not self.data.results or self.data.status_code != HTTPStatus.OK: return self._error_embed() diff --git a/didier/data/embeds/ufora/announcements.py b/didier/data/embeds/ufora/announcements.py index d906ea0..a766e79 100644 --- a/didier/data/embeds/ufora/announcements.py +++ b/didier/data/embeds/ufora/announcements.py @@ -47,7 +47,7 @@ class UforaNotification(EmbedBaseModel): self.published_dt = self._published_datetime() self._published = self._get_published() - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs: dict) -> discord.Embed: """Turn the notification into an embed""" embed = discord.Embed(colour=discord.Colour.from_rgb(30, 100, 200)) diff --git a/didier/data/embeds/urban_dictionary.py b/didier/data/embeds/urban_dictionary.py index 4f42378..afaf4c5 100644 --- a/didier/data/embeds/urban_dictionary.py +++ b/didier/data/embeds/urban_dictionary.py @@ -46,7 +46,7 @@ class Definition(EmbedPydantic): return string_utils.abbreviate(field, max_length=Limits.EMBED_FIELD_VALUE_LENGTH) @overrides - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs: dict) -> discord.Embed: embed = discord.Embed(colour=colours.urban_dictionary_green()) embed.set_author(name="Urban Dictionary") diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index daf3314..3f088f7 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -94,7 +94,9 @@ class WordleEmbed(EmbedBaseModel): return emojis @overrides - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: + only_colours = kwargs.get("only_colours", False) + colours = self.colour_code_game() embed = discord.Embed(colour=discord.Colour.blue(), title="Wordle") @@ -102,15 +104,16 @@ class WordleEmbed(EmbedBaseModel): rows = [" ".join(row) for row in emojis] - for i, guess in enumerate(self.game.guesses): - rows[i] += f" ||{guess.upper()}||" + # Don't reveal anything if we only want to show the colours + if not only_colours: + for i, guess in enumerate(self.game.guesses): + rows[i] += f" ||{guess.upper()}||" + + # If the game is over, reveal the word + if self.game.is_game_over(self.word): + rows.append(f"\n\nThe word was **{self.word.upper()}**!") embed.description = "\n\n".join(rows) - - # If the game is over, reveal the word - if self.game.is_game_over(self.word): - embed.description += f"\n\nThe word was **{self.word.upper()}**!" - embed.set_footer(text=footer()) return embed From 5f9a57cd83164ec51ad9005497a8d6136e423fab Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 21:49:58 +0200 Subject: [PATCH 06/14] Try to fix actions, fix broken test --- database/schemas/mongo.py | 2 +- docker-compose.test.yml | 1 + readme.md | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index d065bd4..aeced32 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -103,7 +103,7 @@ class GameStats(MongoCollection): class WordleGame(MongoCollection): """Collection that holds people's active Wordle games""" - day: datetime.date = Field(default_factory=lambda: today_only_date()) + day: datetime.datetime = Field(default_factory=lambda: today_only_date()) guesses: list[str] = Field(default_factory=list) user_id: int diff --git a/docker-compose.test.yml b/docker-compose.test.yml index a446613..064d93b 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -17,5 +17,6 @@ services: - MONGO_INITDB_ROOT_USERNAME=pytest - MONGO_INITDB_ROOT_PASSWORD=pytest - MONGO_INITDB_DATABASE=didier_pytest + command: [--auth] ports: - "27018:27017" diff --git a/readme.md b/readme.md index 1060271..e2340c6 100644 --- a/readme.md +++ b/readme.md @@ -30,10 +30,10 @@ A separate database is used in the tests, as it would obviously not be ideal whe ```shell # Starting the database -docker-compose up -d db +docker compose up -d # Starting the database used in tests -docker-compose up -d db-pytest +docker compose -f docker-compose.test.yml up -d ``` ### Commands From d4dae7826d27e8be302212d9bfeb3b4306e57dd8 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 22:02:59 +0200 Subject: [PATCH 07/14] Require no auth for mongo in tests --- .github/workflows/python.yml | 2 -- database/engine.py | 13 ++++++++++--- docker-compose.test.yml | 3 --- pyproject.toml | 3 +-- settings.py | 2 ++ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 7ef8dab..572b40e 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -49,8 +49,6 @@ jobs: - 27018:27017 env: MONGO_DB: didier_pytest - MONGO_USER: pytest - MONGO_PASSWORD: pytest steps: - uses: actions/checkout@v3 - name: Setup Python diff --git a/database/engine.py b/database/engine.py index e98c8df..24af24f 100644 --- a/database/engine.py +++ b/database/engine.py @@ -28,7 +28,14 @@ DBSession = sessionmaker( ) # MongoDB client -encoded_mongo_username = quote_plus(settings.MONGO_USER) -encoded_mongo_password = quote_plus(settings.MONGO_PASS) -mongo_url = f"mongodb://{encoded_mongo_username}:{encoded_mongo_password}@{settings.MONGO_HOST}:{settings.MONGO_PORT}/" +if not settings.TESTING: + encoded_mongo_username = quote_plus(settings.MONGO_USER) + encoded_mongo_password = quote_plus(settings.MONGO_PASS) + mongo_url = ( + f"mongodb://{encoded_mongo_username}:{encoded_mongo_password}@{settings.MONGO_HOST}:{settings.MONGO_PORT}/" + ) +else: + # Require no authentication when testing + mongo_url = f"mongodb://{settings.MONGO_HOST}:{settings.MONGO_PORT}/" + mongo_client = motor.motor_asyncio.AsyncIOMotorClient(mongo_url) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 064d93b..4033ad4 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -14,9 +14,6 @@ services: image: mongo:5.0 restart: always environment: - - MONGO_INITDB_ROOT_USERNAME=pytest - - MONGO_INITDB_ROOT_PASSWORD=pytest - MONGO_INITDB_DATABASE=didier_pytest - command: [--auth] ports: - "27018:27017" diff --git a/pyproject.toml b/pyproject.toml index 60927e6..6e5a61d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,9 +42,8 @@ ignore_missing_imports = true [tool.pytest.ini_options] asyncio_mode = "auto" env = [ + "TESTING = 1", "MONGO_DB = didier_pytest", - "MONGO_USER = pytest", - "MONGO_PASS = pytest", "MONGO_HOST = localhost", "MONGO_PORT = 27018", "POSTGRES_DB = didier_pytest", diff --git a/settings.py b/settings.py index 71f41ce..ec6039c 100644 --- a/settings.py +++ b/settings.py @@ -8,6 +8,7 @@ env.read_env() __all__ = [ "SANDBOX", + "TESTING", "LOGFILE", "POSTGRES_DB", "POSTGRES_USER", @@ -28,6 +29,7 @@ __all__ = [ """General config""" SANDBOX: bool = env.bool("SANDBOX", True) +TESTING: bool = env.bool("TESTING", False) LOGFILE: str = env.str("LOGFILE", "didier.log") SEMESTER: int = env.int("SEMESTER", 2) YEAR: int = env.int("YEAR", 3) From c4c9461ca32b3e81f23459f7955212d332970ba2 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 27 Jul 2022 22:08:00 +0200 Subject: [PATCH 08/14] Fix typing --- didier/data/embeds/wordle.py | 4 ++-- didier/didier.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index 3f088f7..3f54d1e 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -105,7 +105,7 @@ class WordleEmbed(EmbedBaseModel): rows = [" ".join(row) for row in emojis] # Don't reveal anything if we only want to show the colours - if not only_colours: + if not only_colours and self.game is not None: for i, guess in enumerate(self.game.guesses): rows[i] += f" ||{guess.upper()}||" @@ -126,7 +126,7 @@ class WordleErrorEmbed(EmbedBaseModel): message: str @overrides - def to_embed(self) -> discord.Embed: + def to_embed(self, **kwargs: dict) -> discord.Embed: embed = discord.Embed(colour=discord.Colour.red(), title="Wordle") embed.description = self.message embed.set_footer(text=footer()) diff --git a/didier/didier.py b/didier/didier.py index fc57a83..f305cad 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -27,7 +27,7 @@ class Didier(commands.Bot): error_channel: discord.abc.Messageable initial_extensions: tuple[str, ...] = () http_session: ClientSession - wordle_words: set[str, ...] = set() + wordle_words: set[str] = set() def __init__(self): activity = discord.Activity(type=discord.ActivityType.playing, name=settings.DISCORD_STATUS_MESSAGE) From a0781a046b1a73f9a04fb534e7214297e2c2cd7d Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sat, 30 Jul 2022 00:24:03 +0200 Subject: [PATCH 09/14] Add more tests --- tests/test_database/test_crud/test_wordle.py | 38 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index a1720de..d0988fc 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -1,8 +1,12 @@ +from datetime import datetime, timedelta + import pytest +from freezegun import freeze_time from database.crud import wordle as crud +from database.enums import TempStorageKey from database.mongo_types import MongoCollection, MongoDatabase -from database.schemas.mongo import WordleGame +from database.schemas.mongo import TemporaryStorage, WordleGame @pytest.fixture @@ -37,6 +41,36 @@ async def test_get_active_wordle_game_none(mongodb: MongoDatabase, test_user_id: async def test_get_active_wordle_game(mongodb: MongoDatabase, wordle_game: WordleGame): - """Test getting an active game when there is none""" + """Test getting an active game when there is one""" result = await crud.get_active_wordle_game(mongodb, wordle_game.user_id) assert result.dict(by_alias=True) == wordle_game.dict(by_alias=True) + + +async def test_get_daily_word_none(mongodb: MongoDatabase): + """Test getting the daily word when the database is empty""" + result = await crud.get_daily_word(mongodb) + assert result is None + + +@freeze_time("2022-07-30") +async def test_get_daily_word_not_today(mongodb: MongoDatabase): + """Test getting the daily word when there is an entry, but not for today""" + day = datetime.today() - timedelta(days=1) + collection = mongodb[TemporaryStorage.collection()] + + word = "testword" + await collection.insert_one({"key": TempStorageKey.WORDLE_WORD, "day": day, "word": word}) + + assert await crud.get_daily_word(mongodb) is None + + +@freeze_time("2022-07-30") +async def test_get_daily_word_present(mongodb: MongoDatabase): + """Test getting the daily word when there is one for today""" + day = datetime.today() + collection = mongodb[TemporaryStorage.collection()] + + word = "testword" + await collection.insert_one({"key": TempStorageKey.WORDLE_WORD, "day": day, "word": word}) + + assert await crud.get_daily_word(mongodb) == word From e4e77502e8a492bb28801a5d0dc8226ebb97ff83 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sat, 30 Jul 2022 16:14:32 +0200 Subject: [PATCH 10/14] Test all crud stuff up until now --- database/crud/wordle.py | 2 +- didier/cogs/games.py | 1 - pyproject.toml | 4 ++ tests/test_database/test_crud/test_wordle.py | 59 ++++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/database/crud/wordle.py b/database/crud/wordle.py index 9ebd75f..acd0242 100644 --- a/database/crud/wordle.py +++ b/database/crud/wordle.py @@ -61,7 +61,7 @@ async def set_daily_word(database: MongoDatabase, word: str, *, forced: bool = F """ collection = database[TemporaryStorage.collection()] - current_word = None if forced else await get_daily_word(collection) + current_word = None if forced else await get_daily_word(database) if current_word is not None: return current_word diff --git a/didier/cogs/games.py b/didier/cogs/games.py index caefb93..1a4453a 100644 --- a/didier/cogs/games.py +++ b/didier/cogs/games.py @@ -52,7 +52,6 @@ class Games(commands.Cog): return await interaction.followup.send(embed=embed) guess = guess.lower() - await make_wordle_guess(self.client.mongo_db, interaction.user.id, guess) # Don't re-request the game, we already have it diff --git a/pyproject.toml b/pyproject.toml index 6e5a61d..2cc4cda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,3 +53,7 @@ env = [ "POSTGRES_PORT = 5433", "DISCORD_TOKEN = token" ] +markers = [ + "mongo: tests that use MongoDB", + "postgres: tests that use PostgreSQL" +] diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index d0988fc..7c61e84 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -23,6 +23,7 @@ async def wordle_game(wordle_collection: MongoCollection, test_user_id: int) -> yield game +@pytest.mark.mongo async def test_start_new_game(mongodb: MongoDatabase, wordle_collection: MongoCollection, test_user_id: int): """Test starting a new game""" result = await wordle_collection.find_one({"user_id": test_user_id}) @@ -34,24 +35,28 @@ async def test_start_new_game(mongodb: MongoDatabase, wordle_collection: MongoCo assert result is not None +@pytest.mark.mongo async def test_get_active_wordle_game_none(mongodb: MongoDatabase, test_user_id: int): """Test getting an active game when there is none""" result = await crud.get_active_wordle_game(mongodb, test_user_id) assert result is None +@pytest.mark.mongo async def test_get_active_wordle_game(mongodb: MongoDatabase, wordle_game: WordleGame): """Test getting an active game when there is one""" result = await crud.get_active_wordle_game(mongodb, wordle_game.user_id) assert result.dict(by_alias=True) == wordle_game.dict(by_alias=True) +@pytest.mark.mongo async def test_get_daily_word_none(mongodb: MongoDatabase): """Test getting the daily word when the database is empty""" result = await crud.get_daily_word(mongodb) assert result is None +@pytest.mark.mongo @freeze_time("2022-07-30") async def test_get_daily_word_not_today(mongodb: MongoDatabase): """Test getting the daily word when there is an entry, but not for today""" @@ -64,6 +69,7 @@ async def test_get_daily_word_not_today(mongodb: MongoDatabase): assert await crud.get_daily_word(mongodb) is None +@pytest.mark.mongo @freeze_time("2022-07-30") async def test_get_daily_word_present(mongodb: MongoDatabase): """Test getting the daily word when there is one for today""" @@ -74,3 +80,56 @@ async def test_get_daily_word_present(mongodb: MongoDatabase): await collection.insert_one({"key": TempStorageKey.WORDLE_WORD, "day": day, "word": word}) assert await crud.get_daily_word(mongodb) == word + + +@pytest.mark.mongo +@freeze_time("2022-07-30") +async def test_set_daily_word_none_present(mongodb: MongoDatabase): + """Test setting the daily word when there is none""" + assert await crud.get_daily_word(mongodb) is None + word = "testword" + await crud.set_daily_word(mongodb, word) + assert await crud.get_daily_word(mongodb) == word + + +@pytest.mark.mongo +@freeze_time("2022-07-30") +async def test_set_daily_word_present(mongodb: MongoDatabase): + """Test setting the daily word when there already is one""" + word = "testword" + await crud.set_daily_word(mongodb, word) + await crud.set_daily_word(mongodb, "another word") + assert await crud.get_daily_word(mongodb) == word + + +@pytest.mark.mongo +@freeze_time("2022-07-30") +async def test_set_daily_word_force_overwrite(mongodb: MongoDatabase): + """Test setting the daily word when there already is one, but "forced" is set to True""" + word = "testword" + await crud.set_daily_word(mongodb, word) + word = "anotherword" + await crud.set_daily_word(mongodb, word, forced=True) + assert await crud.get_daily_word(mongodb) == word + + +@pytest.mark.mongo +async def test_make_wordle_guess(mongodb: MongoDatabase, wordle_game: WordleGame, test_user_id: int): + """Test making a guess in your current game""" + guess = "guess" + await crud.make_wordle_guess(mongodb, test_user_id, guess) + wordle_game = await crud.get_active_wordle_game(mongodb, test_user_id) + assert wordle_game.guesses == [guess] + + other_guess = "otherguess" + await crud.make_wordle_guess(mongodb, test_user_id, other_guess) + wordle_game = await crud.get_active_wordle_game(mongodb, test_user_id) + assert wordle_game.guesses == [guess, other_guess] + + +@pytest.mark.mongo +async def test_reset_wordle_games(mongodb: MongoDatabase, wordle_game: WordleGame, test_user_id: int): + """Test dropping the collection of active games""" + assert await crud.get_active_wordle_game(mongodb, test_user_id) is not None + await crud.reset_wordle_games(mongodb) + assert await crud.get_active_wordle_game(mongodb, test_user_id) is None From bf41acd9f4725bec28da5b0cff286ace4ab37d1a Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sat, 30 Jul 2022 17:50:09 +0200 Subject: [PATCH 11/14] Make game stats crud functions, split mongo schemas out a bit --- database/crud/game_stats.py | 59 ++++++++ database/crud/wordle.py | 3 +- database/schemas/mongo.py | 134 ------------------ database/schemas/mongo/__init__.py | 0 database/schemas/mongo/common.py | 53 +++++++ database/schemas/mongo/game_stats.py | 40 ++++++ database/schemas/mongo/temporary_storage.py | 16 +++ database/schemas/mongo/wordle.py | 44 ++++++ didier/data/embeds/wordle.py | 2 +- .../test_crud/test_game_stats.py | 8 ++ tests/test_database/test_crud/test_wordle.py | 3 +- 11 files changed, 225 insertions(+), 137 deletions(-) create mode 100644 database/crud/game_stats.py delete mode 100644 database/schemas/mongo.py create mode 100644 database/schemas/mongo/__init__.py create mode 100644 database/schemas/mongo/common.py create mode 100644 database/schemas/mongo/game_stats.py create mode 100644 database/schemas/mongo/temporary_storage.py create mode 100644 database/schemas/mongo/wordle.py create mode 100644 tests/test_database/test_crud/test_game_stats.py diff --git a/database/crud/game_stats.py b/database/crud/game_stats.py new file mode 100644 index 0000000..90e2e98 --- /dev/null +++ b/database/crud/game_stats.py @@ -0,0 +1,59 @@ +import datetime +from typing import Union + +from database.mongo_types import MongoDatabase +from database.schemas.mongo.game_stats import GameStats + +__all__ = ["get_game_stats", "complete_wordle_game"] + +from database.utils.datetime import today_only_date + + +async def get_game_stats(database: MongoDatabase, user_id: int) -> GameStats: + """Get a user's game stats + + If no entry is found, it is first created + """ + collection = database[GameStats.collection()] + stats = await collection.find_one({"user_id": user_id}) + if stats is not None: + return GameStats(**stats) + + stats = GameStats(user_id=user_id) + await collection.insert_one(stats.dict(by_alias=True)) + return stats + + +async def complete_wordle_game(database: MongoDatabase, user_id: int, win: bool, guesses: int): + """Update the user's Wordle stats""" + stats = await get_game_stats(database, user_id) + + update: dict[str, dict[str, Union[int, datetime.datetime]]] = {"$inc": {"wordle.games": 1}} + + if win: + update["$inc"]["wordle.wins"] = 1 + update["$inc"][f"wordle.guess_distribution.{guesses}"] = 1 + + # Update streak + today = today_only_date() + last_win = stats.wordle.last_win + update["$set"]["wordle.last_win"] = today + + if last_win is None or (today - last_win).days > 1: + # Never won a game before or streak is over + update["$set"]["wordle.current_streak"] = 1 + stats.wordle.current_streak = 1 + else: + # On a streak: increase counter + update["$inc"]["wordle.current_streak"] = 1 + stats.wordle.current_streak += 1 + + # Update max streak if necessary + if stats.wordle.current_streak > stats.wordle.max_streak: + update["$set"]["wordle.max_streak"] = stats.wordle.current_streak + else: + # Streak is over + update["$set"]["wordle.current_streak"] = 0 + + collection = database[GameStats.collection()] + collection.update_one({"_id": stats.id}, update) diff --git a/database/crud/wordle.py b/database/crud/wordle.py index acd0242..4b2a312 100644 --- a/database/crud/wordle.py +++ b/database/crud/wordle.py @@ -2,7 +2,8 @@ from typing import Optional from database.enums import TempStorageKey from database.mongo_types import MongoDatabase -from database.schemas.mongo import TemporaryStorage, WordleGame +from database.schemas.mongo.temporary_storage import TemporaryStorage +from database.schemas.mongo.wordle import WordleGame from database.utils.datetime import today_only_date __all__ = [ diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py deleted file mode 100644 index aeced32..0000000 --- a/database/schemas/mongo.py +++ /dev/null @@ -1,134 +0,0 @@ -import datetime -from abc import ABC, abstractmethod -from typing import Optional - -from bson import ObjectId -from overrides import overrides -from pydantic import BaseModel, Field, validator - -from database.constants import WORDLE_GUESS_COUNT - -__all__ = ["MongoBase", "TemporaryStorage", "WordleGame"] - -from database.utils.datetime import today_only_date - - -class PyObjectId(ObjectId): - """Custom type for bson ObjectIds""" - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, value: str): - """Check that a string is a valid bson ObjectId""" - if not ObjectId.is_valid(value): - raise ValueError(f"Invalid ObjectId: '{value}'") - - return ObjectId(value) - - @classmethod - def __modify_schema__(cls, field_schema: dict): - field_schema.update(type="string") - - -class MongoBase(BaseModel): - """Base model that properly sets the _id field, and adds one by default""" - - id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") - - class Config: - """Configuration for encoding and construction""" - - allow_population_by_field_name = True - arbitrary_types_allowed = True - json_encoders = {ObjectId: str, PyObjectId: str} - use_enum_values = True - - -class MongoCollection(MongoBase, ABC): - """Base model for the 'main class' in a collection - - This field stores the name of the collection to avoid making typos against it - """ - - @staticmethod - @abstractmethod - def collection() -> str: - raise NotImplementedError - - -class TemporaryStorage(MongoCollection): - """Collection for lots of random things that don't belong in a full-blown collection""" - - key: str - - @staticmethod - @overrides - def collection() -> str: - return "temporary" - - -class WordleStats(BaseModel): - """Model that holds stats about a player's Wordle performance""" - - guess_distribution: list[int] = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) - last_guess: Optional[datetime.date] = None - win_rate: float = 0 - current_streak: int = 0 - max_streak: int = 0 - - @validator("guess_distribution") - def validate_guesses_length(cls, value: list[int]): - """Check that the distribution of guesses is of the correct length""" - if len(value) != 6: - raise ValueError(f"guess_distribution must be length 6, found {len(value)}") - - return value - - -class GameStats(MongoCollection): - """Collection that holds stats about how well a user has performed in games""" - - user_id: int - wordle: Optional[WordleStats] = None - - @staticmethod - @overrides - def collection() -> str: - return "game_stats" - - -class WordleGame(MongoCollection): - """Collection that holds people's active Wordle games""" - - day: datetime.datetime = Field(default_factory=lambda: today_only_date()) - guesses: list[str] = Field(default_factory=list) - user_id: int - - @staticmethod - @overrides - def collection() -> str: - return "wordle" - - @validator("guesses") - def validate_guesses_length(cls, value: list[int]): - """Check that the amount of guesses is of the correct length""" - if len(value) > 6: - raise ValueError(f"guess_distribution must be no longer than 6 elements, found {len(value)}") - - return value - - def is_game_over(self, word: str) -> bool: - """Check if the current game is over""" - # No guesses yet - if not self.guesses: - return False - - # Max amount of guesses allowed - if len(self.guesses) == WORDLE_GUESS_COUNT: - return True - - # Found the correct word - return self.guesses[-1] == word diff --git a/database/schemas/mongo/__init__.py b/database/schemas/mongo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/schemas/mongo/common.py b/database/schemas/mongo/common.py new file mode 100644 index 0000000..dcddb54 --- /dev/null +++ b/database/schemas/mongo/common.py @@ -0,0 +1,53 @@ +from abc import ABC, abstractmethod + +from bson import ObjectId +from pydantic import BaseModel, Field + +__all__ = ["PyObjectId", "MongoBase", "MongoCollection"] + + +class PyObjectId(ObjectId): + """Custom type for bson ObjectIds""" + + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, value: str): + """Check that a string is a valid bson ObjectId""" + if not ObjectId.is_valid(value): + raise ValueError(f"Invalid ObjectId: '{value}'") + + return ObjectId(value) + + @classmethod + def __modify_schema__(cls, field_schema: dict): + field_schema.update(type="string") + + +class MongoBase(BaseModel): + """Base model that properly sets the _id field, and adds one by default""" + + id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") + + class Config: + """Configuration for encoding and construction""" + + allow_population_by_field_name = True + arbitrary_types_allowed = True + json_encoders = {ObjectId: str, PyObjectId: str} + use_enum_values = True + + +class MongoCollection(MongoBase, ABC): + """Base model for the 'main class' in a collection + + This field stores the name of the collection to avoid making typos against it + """ + + @staticmethod + @abstractmethod + def collection() -> str: + """Getter for the name of the collection, in order to avoid typos""" + raise NotImplementedError diff --git a/database/schemas/mongo/game_stats.py b/database/schemas/mongo/game_stats.py new file mode 100644 index 0000000..af08117 --- /dev/null +++ b/database/schemas/mongo/game_stats.py @@ -0,0 +1,40 @@ +import datetime +from typing import Optional + +from overrides import overrides +from pydantic import BaseModel, Field, validator + +from database.schemas.mongo.common import MongoCollection + +__all__ = ["GameStats", "WordleStats"] + + +class WordleStats(BaseModel): + """Model that holds stats about a player's Wordle performance""" + + guess_distribution: list[int] = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) + last_win: Optional[datetime.datetime] = None + wins: int = 0 + games: int = 0 + current_streak: int = 0 + max_streak: int = 0 + + @validator("guess_distribution") + def validate_guesses_length(cls, value: list[int]): + """Check that the distribution of guesses is of the correct length""" + if len(value) != 6: + raise ValueError(f"guess_distribution must be length 6, found {len(value)}") + + return value + + +class GameStats(MongoCollection): + """Collection that holds stats about how well a user has performed in games""" + + user_id: int + wordle: WordleStats = WordleStats() + + @staticmethod + @overrides + def collection() -> str: + return "game_stats" diff --git a/database/schemas/mongo/temporary_storage.py b/database/schemas/mongo/temporary_storage.py new file mode 100644 index 0000000..deb444b --- /dev/null +++ b/database/schemas/mongo/temporary_storage.py @@ -0,0 +1,16 @@ +from overrides import overrides + +from database.schemas.mongo.common import MongoCollection + +__all__ = ["TemporaryStorage"] + + +class TemporaryStorage(MongoCollection): + """Collection for lots of random things that don't belong in a full-blown collection""" + + key: str + + @staticmethod + @overrides + def collection() -> str: + return "temporary" diff --git a/database/schemas/mongo/wordle.py b/database/schemas/mongo/wordle.py new file mode 100644 index 0000000..b03189b --- /dev/null +++ b/database/schemas/mongo/wordle.py @@ -0,0 +1,44 @@ +import datetime + +from overrides import overrides +from pydantic import Field, validator + +from database.constants import WORDLE_GUESS_COUNT +from database.schemas.mongo.common import MongoCollection +from database.utils.datetime import today_only_date + +__all__ = ["WordleGame"] + + +class WordleGame(MongoCollection): + """Collection that holds people's active Wordle games""" + + day: datetime.datetime = Field(default_factory=lambda: today_only_date()) + guesses: list[str] = Field(default_factory=list) + user_id: int + + @staticmethod + @overrides + def collection() -> str: + return "wordle" + + @validator("guesses") + def validate_guesses_length(cls, value: list[int]): + """Check that the amount of guesses is of the correct length""" + if len(value) > 6: + raise ValueError(f"guess_distribution must be no longer than 6 elements, found {len(value)}") + + return value + + def is_game_over(self, word: str) -> bool: + """Check if the current game is over""" + # No guesses yet + if not self.guesses: + return False + + # Max amount of guesses allowed + if len(self.guesses) == WORDLE_GUESS_COUNT: + return True + + # Found the correct word + return self.guesses[-1] == word diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index 3f54d1e..44439c0 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -6,7 +6,7 @@ import discord from overrides import overrides from database.constants import WORDLE_GUESS_COUNT, WORDLE_WORD_LENGTH -from database.schemas.mongo import WordleGame +from database.schemas.mongo.wordle import WordleGame from didier.data.embeds.base import EmbedBaseModel from didier.utils.types.datetime import int_to_weekday, tz_aware_now diff --git a/tests/test_database/test_crud/test_game_stats.py b/tests/test_database/test_crud/test_game_stats.py new file mode 100644 index 0000000..c48570a --- /dev/null +++ b/tests/test_database/test_crud/test_game_stats.py @@ -0,0 +1,8 @@ +import pytest + +from database.mongo_types import MongoDatabase + + +@pytest.mark.mongo +async def test_get_stats_non_existent(mongodb: MongoDatabase, test_user_id: int): + """Test getting a user's stats when the db is empty""" diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index 7c61e84..3ddc979 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -6,7 +6,8 @@ from freezegun import freeze_time from database.crud import wordle as crud from database.enums import TempStorageKey from database.mongo_types import MongoCollection, MongoDatabase -from database.schemas.mongo import TemporaryStorage, WordleGame +from database.schemas.mongo.temporary_storage import TemporaryStorage +from database.schemas.mongo.wordle import WordleGame @pytest.fixture From b74f79463966a4aaaca7e8def31fd0f2563fb2e7 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sat, 30 Jul 2022 18:27:58 +0200 Subject: [PATCH 12/14] First tests for game stats --- database/crud/dad_jokes.py | 2 +- database/crud/game_stats.py | 6 +- database/utils/datetime.py | 5 +- pyproject.toml | 1 + .../test_crud/test_game_stats.py | 57 ++++++++++++++++++- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/database/crud/dad_jokes.py b/database/crud/dad_jokes.py index c481ec3..3d673de 100644 --- a/database/crud/dad_jokes.py +++ b/database/crud/dad_jokes.py @@ -16,7 +16,7 @@ async def add_dad_joke(session: AsyncSession, joke: str) -> DadJoke: return dad_joke -async def get_random_dad_joke(session: AsyncSession) -> DadJoke: +async def get_random_dad_joke(session: AsyncSession) -> DadJoke: # pragma: no cover # randomness is untestable """Return a random database entry""" statement = select(DadJoke).order_by(func.random()) row = (await session.execute(statement)).first() diff --git a/database/crud/game_stats.py b/database/crud/game_stats.py index 90e2e98..6410b50 100644 --- a/database/crud/game_stats.py +++ b/database/crud/game_stats.py @@ -24,15 +24,15 @@ async def get_game_stats(database: MongoDatabase, user_id: int) -> GameStats: return stats -async def complete_wordle_game(database: MongoDatabase, user_id: int, win: bool, guesses: int): +async def complete_wordle_game(database: MongoDatabase, user_id: int, win: bool, guesses: int = 0): """Update the user's Wordle stats""" stats = await get_game_stats(database, user_id) - update: dict[str, dict[str, Union[int, datetime.datetime]]] = {"$inc": {"wordle.games": 1}} + update: dict[str, dict[str, Union[int, datetime.datetime]]] = {"$inc": {"wordle.games": 1}, "$set": {}} if win: update["$inc"]["wordle.wins"] = 1 - update["$inc"][f"wordle.guess_distribution.{guesses}"] = 1 + update["$inc"][f"wordle.guess_distribution.{guesses - 1}"] = 1 # Update streak today = today_only_date() diff --git a/database/utils/datetime.py b/database/utils/datetime.py index c1796d6..0952f63 100644 --- a/database/utils/datetime.py +++ b/database/utils/datetime.py @@ -7,8 +7,9 @@ LOCAL_TIMEZONE = zoneinfo.ZoneInfo("Europe/Brussels") def today_only_date() -> datetime.datetime: - """Mongo can't handle datetime.date, so we need datetime + """Mongo can't handle datetime.date, so we need a datetime instance We do, however, only care about the date, so remove all the rest """ - return datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) + today = datetime.date.today() + return datetime.datetime(year=today.year, month=today.month, day=today.day) diff --git a/pyproject.toml b/pyproject.toml index 2cc4cda..45c7c9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ omit = [ "./didier/utils/discord/colours.py", "./didier/utils/discord/constants.py", "./didier/utils/discord/flags/*", + "./didier/views/modals/*" ] [tool.isort] diff --git a/tests/test_database/test_crud/test_game_stats.py b/tests/test_database/test_crud/test_game_stats.py index c48570a..4b7606f 100644 --- a/tests/test_database/test_crud/test_game_stats.py +++ b/tests/test_database/test_crud/test_game_stats.py @@ -1,8 +1,63 @@ import pytest +from freezegun import freeze_time +from database.crud import game_stats as crud from database.mongo_types import MongoDatabase +from database.schemas.mongo.game_stats import GameStats +from database.utils.datetime import today_only_date + + +async def insert_game_stats(mongodb: MongoDatabase, stats: GameStats): + """Helper function to insert some stats""" + collection = mongodb[GameStats.collection()] + await collection.insert_one(stats.dict(by_alias=True)) @pytest.mark.mongo -async def test_get_stats_non_existent(mongodb: MongoDatabase, test_user_id: int): +async def test_get_stats_non_existent_creates(mongodb: MongoDatabase, test_user_id: int): """Test getting a user's stats when the db is empty""" + collection = mongodb[GameStats.collection()] + assert await collection.find_one({"user_id": test_user_id}) is None + await crud.get_game_stats(mongodb, test_user_id) + assert await collection.find_one({"user_id": test_user_id}) is not None + + +@pytest.mark.mongo +async def test_get_stats_existing_returns(mongodb: MongoDatabase, test_user_id: int): + """Test getting a user's stats when there's already an entry present""" + stats = GameStats(user_id=test_user_id) + stats.wordle.games = 20 + await insert_game_stats(mongodb, stats) + found_stats = await crud.get_game_stats(mongodb, test_user_id) + assert found_stats.wordle.games == 20 + + +@pytest.mark.mongo +@freeze_time("2022-07-30") +async def test_complete_wordle_game_won(mongodb: MongoDatabase, test_user_id: int): + """Test completing a wordle game when you win""" + await crud.complete_wordle_game(mongodb, test_user_id, win=True, guesses=2) + stats = await crud.get_game_stats(mongodb, test_user_id) + assert stats.wordle.guess_distribution == [0, 1, 0, 0, 0, 0] + assert stats.wordle.games == 1 + assert stats.wordle.wins == 1 + assert stats.wordle.current_streak == 1 + assert stats.wordle.max_streak == 1 + assert stats.wordle.last_win == today_only_date() + + +@pytest.mark.mongo +@freeze_time("2022-07-30") +async def test_complete_wordle_game_lost(mongodb: MongoDatabase, test_user_id: int): + """Test completing a wordle game when you lose""" + stats = GameStats(user_id=test_user_id) + stats.wordle.current_streak = 10 + await insert_game_stats(mongodb, stats) + + await crud.complete_wordle_game(mongodb, test_user_id, win=False) + stats = await crud.get_game_stats(mongodb, test_user_id) + + # Check that streak was broken + assert stats.wordle.current_streak == 0 + assert stats.wordle.games == 1 + assert stats.wordle.guess_distribution == [0, 0, 0, 0, 0, 0] From 8dbf68cac08dbaabf78c0c438714a8197d004a72 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 3 Aug 2022 16:27:33 +0200 Subject: [PATCH 13/14] Don't count testing code as coverage --- database/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/engine.py b/database/engine.py index 24af24f..b3e3947 100644 --- a/database/engine.py +++ b/database/engine.py @@ -28,7 +28,7 @@ DBSession = sessionmaker( ) # MongoDB client -if not settings.TESTING: +if not settings.TESTING: # pragma: no cover encoded_mongo_username = quote_plus(settings.MONGO_USER) encoded_mongo_password = quote_plus(settings.MONGO_PASS) mongo_url = ( From e36322b4a759b577f6613b462e47340778ca2478 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Wed, 3 Aug 2022 16:36:24 +0200 Subject: [PATCH 14/14] Add missing await --- database/crud/game_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/crud/game_stats.py b/database/crud/game_stats.py index 6410b50..5634b49 100644 --- a/database/crud/game_stats.py +++ b/database/crud/game_stats.py @@ -56,4 +56,4 @@ async def complete_wordle_game(database: MongoDatabase, user_id: int, win: bool, update["$set"]["wordle.current_streak"] = 0 collection = database[GameStats.collection()] - collection.update_one({"_id": stats.id}, update) + await collection.update_one({"_id": stats.id}, update)