From 011b6df63a9aa0f553817b42466c6133a28acd4b Mon Sep 17 00:00:00 2001 From: Joe Petrus Date: Fri, 11 Feb 2022 12:21:42 -0500 Subject: [PATCH] Games: Add MasterWord A simple wordle clone. --- Base/res/apps/MasterWrod.af | 4 + Base/res/icons/16x16/app-masterword.png | Bin 0 -> 198 bytes Base/res/icons/32x32/app-masterword.png | Bin 0 -> 582 bytes Base/res/words.txt | 2091 ++++++++++++++++++++++ Base/usr/share/man/man6/MasterWord.md | 15 + Userland/Games/CMakeLists.txt | 1 + Userland/Games/MasterWord/CMakeLists.txt | 13 + Userland/Games/MasterWord/WordGame.cpp | 259 +++ Userland/Games/MasterWord/WordGame.h | 73 + Userland/Games/MasterWord/main.cpp | 134 ++ 10 files changed, 2590 insertions(+) create mode 100644 Base/res/apps/MasterWrod.af create mode 100644 Base/res/icons/16x16/app-masterword.png create mode 100644 Base/res/icons/32x32/app-masterword.png create mode 100644 Base/res/words.txt create mode 100644 Base/usr/share/man/man6/MasterWord.md create mode 100644 Userland/Games/MasterWord/CMakeLists.txt create mode 100644 Userland/Games/MasterWord/WordGame.cpp create mode 100644 Userland/Games/MasterWord/WordGame.h create mode 100644 Userland/Games/MasterWord/main.cpp diff --git a/Base/res/apps/MasterWrod.af b/Base/res/apps/MasterWrod.af new file mode 100644 index 0000000000..6a363f0334 --- /dev/null +++ b/Base/res/apps/MasterWrod.af @@ -0,0 +1,4 @@ +[App] +Name=MasterWord +Executable=/bin/MasterWord +Category=Games diff --git a/Base/res/icons/16x16/app-masterword.png b/Base/res/icons/16x16/app-masterword.png new file mode 100644 index 0000000000000000000000000000000000000000..90035a459d7831bb1d455d745c1a1612acf6a6ac GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9F5M?jcysy3fAP_V+& z#WBR9ck5(Fz6J#z7I%BM`mo=dKN;Cd@G5Ds+!StnQ?i@$@}@P~va_nss5=;n?6#Tm z?wBfr!IwEJB-*wy26?Rg$!4LT`)hr3h|vfGG;2fujCRoV6D sjodxPr8|~xjbl^|wvtVF_4l0gf^I$eU0I9%0^Pvi>FVdQ&MBb@0Nd9>YXATM literal 0 HcmV?d00001 diff --git a/Base/res/icons/32x32/app-masterword.png b/Base/res/icons/32x32/app-masterword.png new file mode 100644 index 0000000000000000000000000000000000000000..48620b55fb4a9c7b790216691c409fe0795c418a GIT binary patch literal 582 zcmV-M0=fN(P)Q2JKfm(hcR|1bJfH+Jf&!ojnZOHjQoOcq zGl4*6U?h?NNJK^keiKiM*Vb(&ASjWcILGJ#AWkWmtu05H#H zGJ!N_;pzl3fsp}4IB+DA7#RQ<7!i~s2NIEi@%jW5ArVo8NNS$LtF^pxd#>9i0tQpU#<#$S1eQwN3is6qE*80p$lU)`pzi$Wyq|M* z9RU#~#yN~BLfJ>a_Vd21SHSzy#(QD=h4B1MGEl3d6yLn_zTmV{>#En^KQ_NFqNoZv zuOy%?vYH`^iNo3X;L6e1h#g( zOyJ0&{kr$S!TOG8N~jZ_CGgjKUe&d}(Dw_=Nbh~E?tN_w+dd247gCBX3w^)n=gs?4 z{M69466mvFr#rbFbDvmx-Fv?b@3wn%0sRSq6NNs)rwE+g3RejsQ>$yz+Prz*d%ugo z|FR%*-WN=nQv8|sy9k&9FnMjyrTBKjC7I=Vk6jrsB`kPL3ogxHmB3Po9&6k5KR&rs U*T`w2O#lD@07*qoM6N<$f^M1vjQ{`u literal 0 HcmV?d00001 diff --git a/Base/res/words.txt b/Base/res/words.txt new file mode 100644 index 0000000000..3d04ed9e43 --- /dev/null +++ b/Base/res/words.txt @@ -0,0 +1,2091 @@ +# grep -iroE '[a-zA-Z]{2,}' --include "*.md" . | sort -u -f > all_words.txt +# grep -iFxf all_words.txt /usr/share/dict/words > words.txt +abbreviation +ability +able +aborting +about +above +absolute +abstraction +AC +accelerated +acceleration +accept +acceptable +access +accessed +accesses +accessible +accessing +according +account +accountability +Acer +achieve +achieved +acronym +across +ACT +active +Acts +actual +actually +adapter +ADD +added +adding +addition +additional +additionally +address +addressed +adds +adjust +adjusting +administrator +adopt +adopted +adopting +advanced +advantage +advice +aesthetically +AF +after +afterwards +Ag +again +against +aggressively +ahead +AK +alias +align +alignment +alive +all +allocate +allocated +allocates +allocation +allow +allowing +allows +alongside +Alpine +already +also +alt +alternate +alternative +alternatively +although +always +AM +AMD +amending +amount +An +analysis +analyzer +AND +Android +announce +anon +anonymous +another +answer +any +anybody +anymore +anyone +anything +anywhere +apart +API +Apis +app +appear +appears +append +application +applied +applies +approach +appropriate +appropriately +apt +arbitrary +Arch +architecture +archive +archiving +are +area +argument +Arm +around +array +Art +article +artificial +ASCII +Ask +asked +asks +aspirational +assert +assertion +assigned +assignment +assist +assume +assumes +assuming +At +attempt +attempting +attribute +attribution +audio +author +auto +automatically +available +avoid +avoiding +away +back +background +backing +bad +Banner +BAR +bare +base +based +bash +BASIC +basically +Be +Beau +beautifier +because +become +becomes +been +before +beforehand +Begin +behalf +behavior +behind +being +belong +below +better +between +beyond +big +bin +binaries +binary +bind +BIOS +bit +bitmap +Black +blasting +blinking +blob +block +blocking +blog +Board +Boards +body +boiler +Book +Boolean +boot +bootable +booted +booting +both +bottom +boundaries +bouquet +box +BR +brace +Branch +brand +breakdown +brew +Bridge +broken +browse +browser +browsing +BSD +buffer +Bug +buggy +build +builder +building +built +bunch +but +by +CAB +cable +cache +call +called +calling +Can +cannot +canonical +capabilities +capable +capacity +capital +capitalize +capture +captured +Card +CARE +Case +cask +CAT +catch +cater +cause +caused +CD +centered +certain +Ch +change +changed +changing +channel +char +character +cheat +check +checked +checking +checkmark +checkout +child +children +chipset +chipsets +choice +choose +Chosen +Ci +circle +circumvent +clang +class +classes +clause +clean +cleanup +clear +click +clicking +client +clipboard +clone +cloning +close +closed +clunky +clutter +code +coded +coding +coexist +coffee +coherent +collection +Colon +column +COM +combination +combiner +come +coming +comma +command +comment +commented +commit +Common +commonly +communicate +communication +community +compatibility +compatible +compilation +compile +compiled +compiler +compiles +compiling +complain +completeness +complexity +component +composited +comprehension +compressed +compromise +computer +concept +conceptually +concrete +concurrent +condition +conf +configuration +configure +configured +configures +configuring +conflicting +conform +confusing +connect +connected +connecting +connection +consequently +consider +considered +consistent +consisting +consists +console +Constant +constraint +construct +constructed +constructing +construction +constructor +contact +contain +contains +content +context +contiguous +contrast +control +controller +convenience +convenient +convention +conversion +convert +converted +cool +copied +copies +copy +copying +copyright +CORE +correct +correctly +corresponding +corrupted +corruption +cost +costly +could +Count +counted +counting +couple +courage +course +coverage +CP +CPU +crawl +create +created +creates +creating +Creation +Creator +criteria +critical +crop +Cross +Cu +curious +curl +current +currently +cursor +custom +customization +customize +customized +customizing +CZ +dark +data +database +dataflow +DD +DE +dead +deal +Debian +debug +debugger +debugging +decide +decided +decimal +decision +declaration +declare +declared +declaring +decode +decoded +decoding +decompress +default +deferred +define +defined +defines +definitely +definition +delete +deleted +deleting +delimited +Dell +denote +depend +dependencies +dependency +dependent +depending +depends +deployment +derived +derives +describe +described +describes +describing +description +descriptive +design +desired +desktop +destructor +detail +detailed +detect +detectable +detecting +determine +determined +deterministic +dev +devel +developer +development +device +diagonal +dialog +did +difference +different +differs +difficulty +diffs +Dir +direct +directing +direction +directive +directly +directories +directory +disable +disabled +disabling +disclaimer +discord +discouraged +discovering +discrete +disk +diskless +display +displayed +distribution +divvy +do +doc +document +documentation +documented +doing +Dom +Don +done +doohickey +Down +download +downloaded +downloading +dpi +drag +dragging +draw +drive +driver +drop +dude +due +dump +durable +during +dynamic +dynamically +each +eagerly +earlier +Early +easier +easiest +easily +East +easy +Echo +edge +edit +editing +editor +effect +efficient +Eg +either +element +elevated +else +EM +email +embed +embedded +emerged +emit +emoji +empty +emulate +emulated +emulation +emulator +en +enable +enabled +enables +enabling +encoded +encoding +encountered +end +ending +endpoint +enforces +engine +English +enough +ensure +ensures +entering +entire +entities +entries +entry +environment +EQ +equal +equivalent +equivalently +ERA +err +error +especially +essential +essentially +established +establishes +etc +Eth +Ethernet +evaluation +even +event +eventually +ever +every +everything +exactly +example +except +exception +exclude +excluded +excluding +Exe +executable +executables +execute +executed +execution +exist +existing +exists +exit +expand +expansion +expect +expected +experienced +experimental +expert +explain +explains +explicit +explore +Explorer +export +expose +exposed +exposing +expression +ext +extend +extends +extension +extensively +extra +extract +extremely +face +facilities +fact +factor +fail +failed +failing +failure +fairly +fake +false +familiar +fast +faster +fatal +fault +feature +fedora +feel +few +fewer +ff +fiddling +Field +fight +figure +figured +file +filename +fill +filtering +final +finalize +finally +find +finding +fine +fini +finish +finishes +firewall +firmware +first +fix +fixed +flag +flashing +flexible +float +floating +focal +focus +folder +follow +followed +following +font +foo +for +Force +Forces +forget +fork +form +format +formatted +formatter +formatters +formatting +fortify +forward +forwarded +found +four +frame +framework +free +frequently +fresh +Friend +from +front +FTP +full +fully +fun +function +functionality +functioning +further +furthermore +fuse +future +fuzzing +gag +gateway +GB +Gemini +general +generally +generate +generated +generates +generation +German +get +getter +getters +getting +gigabyte +git +GitHub +give +giving +gl +global +glyph +GMP +GNU +Go +goal +goes +going +gone +Good +goofy +Google +gotten +GPU +grab +graph +graphical +Greater +group +grow +grub +guaranteed +guard +guest +GUI +guide +guided +guideline +hacking +half +halt +halted +halting +hand +handle +handling +Handy +happen +happening +happens +hard +harder +hardware +hash +hashes +have +haven +having +HE +header +heap +heavy +height +held +hello +Help +Helper +helpful +hence +Here +hex +hexadecimal +hidden +high +higher +highlight +highlighter +highlighting +historical +hit +hold +Home +homebrew +horizontal +Host +hosted +hostile +how +however +HP +HT +HTML +HTTP +human +ich +icon +IDE +Idea +ideal +identically +identified +identifier +identifiers +idiomatic +IF +ignore +ignoring +iii +image +immediately +implement +implementation +implemented +implementing +implicit +implies +import +important +improve +IN +include +included +includes +including +incorrect +increment +incrementally +indented +Index +indexing +indicate +Indices +individually +Inf +influenced +info +information +inherit +inheritance +init +initial +initialization +initialize +initialized +initializer +inline +input +insert +insertion +inside +insight +inspect +inspired +install +installation +installed +installing +instance +instantiated +instantiation +instead +instrumentation +int +integer +integrate +integrated +integration +Intel +intended +inter +interact +interface +internally +Internet +interpret +interprets +into +intrinsic +introduce +introduction +intrusive +investigate +Invocation +Invocations +invoke +invokes +involve +Io +IP +IPA +ISO +issue +IT +item +iterators +itself +jagged +Jam +JavaScript +Joe +join +JPEG +judgement +jury +just +keep +kept +kernel +Key +keyboard +keyword +killed +kind +Kit +know +known +label +lack +ladybug +Lang +language +large +largely +larger +last +later +latest +launch +launcher +layout +lazily +leading +leaf +least +leave +leaving +Left +legacy +Len +length +let +letter +level +Lib +libraries +library +license +light +like +Likely +likewise +limit +limitation +limited +Line +link +linked +linker +lint +linters +Linux +list +listed +literal +littering +Little +live +living +LL +load +loaded +loading +local +locate +located +location +lock +locker +locking +log +logged +logging +logical +Long +longer +look +lookup +loop +loosely +losing +Lot +Low +Lower +lowercase +lowest +Mac +machine +macro +made +magic +magically +Main +mainstream +make +making +malicious +managed +manager +manipulate +manipulated +manipulation +manual +manually +many +Map +mapping +Mark +marked +marketplace +markup +Master +match +matchers +matches +matching +matter +Max +maximize +maximum +May +maybe +MB +MD +ME +mean +meaning +meaningful +meaningless +Means +medium +meet +Member +Members +membership +memory +mentioned +menu +mess +message +Meta +metal +meth +Method +Methods +mib +mice +Microsoft +Middle +might +mime +mind +mini +minimizing +minimum +minute +mirror +Miss +missing +mode +model +modeling +modem +Modern +modifier +modify +module +Mold +moment +monitor +More +most +mostly +motherboard +Mount +mouse +move +moved +moving +MT +much +multi +multiple +must +my +Na +name +named +naming +Nan +Native +natively +navigate +NE +nearest +necessary +need +needed +negative +neighbor +nesting +Net +network +networking +never +New +newer +newline +newly +newt +next +NF +nicely +Ninja +nix +NM +No +node +non +None +nonsensical +NOR +normal +normally +NOT +note +noted +nothing +notice +noticed +noting +NOW +nowadays +null +number +Numbers +numeric +numerical +numerous +object +occurs +octal +OEM +OF +off +official +offline +often +OG +OK +Old +older +omit +ON +onboard +once +one +online +only +onto +oom +Open +opened +opening +operand +operate +operating +operation +operator +opt +optimally +optimization +optimized +option +optional +optionally +OR +order +org +original +originally +OSes +other +otherwise +our +ourselves +out +outdated +outgoing +outgrow +output +outside +over +overall +overflow +overhead +overload +overridden +override +overriding +overview +overwrite +own +owned +owner +ownership +owns +PA +package +packet +Page +paint +painted +Painter +painting +palette +panel +panic +parallel +parameter +parent +parentheses +parse +parser +parsing +part +parted +partition +partly +pass +passed +passes +passing +password +past +paste +patch +path +pattern +PC +Pentium +per +performance +performed +period +persist +persistent +person +phase +physical +pi +pick +picked +PID +piece +ping +pitch +pixel +pkg +Place +placed +placement +plain +plan +planning +plate +platform +playback +Player +playground +please +pleasing +pledge +plumb +pluralization +plus +point +pointed +pointer +pointing +poll +Port +portal +possible +possibly +Post +potentially +power +practice +pre +precede +preceding +precision +prediction +prefer +preferred +prefix +prefixed +prepared +preprocessing +preprocessor +present +Presentation +presented +presently +prettier +pretty +prevent +previous +primary +Prime +principle +printed +printing +Private +PRO +probably +problem +proc +procedure +proceed +proceeding +process +processes +processor +profile +profiling +program +programming +project +prominently +prompt +prop +propagate +propagated +propagating +propagation +Proper +properly +properties +property +Prot +protect +protocol +provide +provided +provides +proxy +public +pull +punctuation +push +put +putting +PX +Python +qt +qualified +qualifier +qualify +querier +question +quick +quickly +quite +RA +radio +RAM +random +range +rare +raspberry +raw +Re +reaches +read +readable +Reader +Reading +ready +real +realize +really +reason +reasonably +reasoning +reboot +rebuild +rebuilds +rebuilt +receive +recognize +recognized +recommend +recommendation +recommended +reconfigure +recoverable +redirects +reduce +ref +refer +reference +referencing +referred +referring +reflect +refresh +Reg +regard +regenerate +regexp +region +regression +regular +related +relating +relative +release +relevant +reliable +relies +reload +relying +remember +remote +remove +removed +removing +render +rendition +reoccurring +repeat +replace +replaced +replacement +repo +report +repos +repositories +repository +represent +representation +representing +represents +request +requested +requesting +require +required +requirement +requires +requiring +resampled +reserved +reset +reside +resize +resized +resizing +resolution +resorting +resource +respect +respectively +response +responsibility +responsible +rest +restart +restarting +result +retaining +return +returned +returning +reveal +revert +Revolution +RF +Right +RM +Root +rough +roughly +row +ruc +rule +Rules +run +runner +running +runtime +rust +safe +safely +said +sake +SALT +same +sample +samurai +sanitize +sanitizer +sanitizers +satisfaction +save +say +scale +scaled +scaling +Scan +schedule +scheduler +scope +scoped +screen +screenshot +Script +scrolling +SCSI +SD +seamlessly +search +second +Secret +section +security +sed +see +seeing +seems +segment +segmentation +segmented +segregated +select +selected +selecting +selection +selectively +selector +self +sending +sent +Separate +separated +separately +separating +separation +sequence +sequential +sequentially +serenity +serial +serialize +series +serve +served +server +service +serving +session +Set +setter +setting +setup +several +sh +share +shared +Shell +shift +ship +Short +shortcut +should +show +shown +shutdown +side +sign +signal +signed +significant +significantly +similar +similarly +simple +simpler +simplest +simplicity +simply +since +single +Singleton +site +size +sized +skip +slave +slightly +slot +slow +slowly +Small +smaller +smart +smooth +smoother +Snake +sniffing +snip +snippet +SO +socket +soft +software +sole +solely +solves +some +somebody +someday +someone +something +sometimes +somewhat +somewhere +Sound +source +sourcing +space +Span +spawn +spawned +spawning +Speaker +speaking +Speaks +spec +special +specific +specifically +specification +specified +specifier +specifiers +specifies +specify +speed +speeding +Split +spreadsheet +squint +SSA +stability +stable +stack +stage +staged +Standard +start +started +starting +startup +state +statement +static +stay +STD +stdio +step +still +stop +stopping +storage +store +stored +strategy +stretch +string +stroke +strongly +structure +stuck +student +studio +stuff +style +styling +sub +subclasses +subdirectory +subframes +subjectively +subset +succeed +successful +successfully +such +suffice +suffix +suffixes +suggests +suitable +summarizes +summary +sunrise +super +superclass +supplied +supply +support +supported +supporting +suppose +supposed +sure +surface +surprisingly +surrounded +suspect +swap +switch +symptom +sync +synchronous +syntax +system +tab +tabulation +tailor +take +taken +taking +tap +tar +tarballs +Target +task +taskbar +team +technical +technique +telling +template +temporarily +tends +term +terminal +terminate +terminated +terminates +terse +test +tested +text +than +that +the +their +them +themselves +then +there +therefore +thereof +these +they +thin +thing +thingy +third +this +those +though +thought +thread +three +through +throughout +throwing +thumb +thus +tick +tied +time +timer +to +together +toggle +token +too +tool +toolbar +tooling +top +total +towards +trace +track +tracked +tracking +trackpad +trailing +transfer +transferred +transferring +transform +transmitted +transparently +Tree +tricky +tries +trivially +troubleshooting +true +try +ttys +tui +turn +turned +turning +tweaking +two +type +typing +Ubuntu +undefined +under +understand +underway +undo +unfilled +unfortunately +unifies +uninitialized +unique +universal +UNIX +unknown +unless +unlike +unprivileged +unrecognized +unsigned +unsupported +untested +until +unused +unveil +unzip +up +update +updated +updating +uploading +upon +uppercase +uptime +Uri +URL +usage +USB +use +used +useful +user +username +using +usual +usually +utility +valid +validate +validity +value +VAR +variable +variant +variety +various +varying +vector +verify +Version +Versions +vertical +Very +VGA +via +vice +video +view +virtual +visible +visual +visualizer +void +volatile +VX +wait +Wall +want +wanting +warning +was +wasm +watch +way +we +weak +weakly +Web +Website +went +what +wheel +when +whenever +where +whether +which +while +whilst +whiptail +White +WHO +why +wide +widget +width +WiFi +wiki +Wikipedia +wildcat +Will +willing +win +window +Windows +wipe +wireless +Wise +wish +wishes +with +within +without +wizard +won +Word +work +workaround +workflow +working +workspace +workstation +World +worry +Worth +would +wrapped +wrapper +writable +writing +written +wrong +WWW +Xe +xterm +xx +xxx +year +yet +yield +yielding +YMMV +you +your +yourself +YouTube +zero +ZIP +zoom diff --git a/Base/usr/share/man/man6/MasterWord.md b/Base/usr/share/man/man6/MasterWord.md new file mode 100644 index 0000000000..b5d518be79 --- /dev/null +++ b/Base/usr/share/man/man6/MasterWord.md @@ -0,0 +1,15 @@ +## Name + +![Icon](/res/icons/16x16/app-masterword.png) MasterWord + +[Open](file:///bin/MasterWord) + +## Synopsis + +```**sh +$ MasterWord +``` + +## Description + +Try to guess a secret word. As you guess, letters that occur but are in the wrong spot will turn yellow and letters that are in the correct spot will turn green. diff --git a/Userland/Games/CMakeLists.txt b/Userland/Games/CMakeLists.txt index a74f979624..a75ac6c7ff 100644 --- a/Userland/Games/CMakeLists.txt +++ b/Userland/Games/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(Chess) add_subdirectory(FlappyBug) add_subdirectory(GameOfLife) add_subdirectory(Hearts) +add_subdirectory(MasterWord) add_subdirectory(Minesweeper) add_subdirectory(Pong) add_subdirectory(Snake) diff --git a/Userland/Games/MasterWord/CMakeLists.txt b/Userland/Games/MasterWord/CMakeLists.txt new file mode 100644 index 0000000000..db5d90067d --- /dev/null +++ b/Userland/Games/MasterWord/CMakeLists.txt @@ -0,0 +1,13 @@ +serenity_component( + MasterWord + RECOMMENDED + TARGETS MasterWord +) + +set(SOURCES + main.cpp + WordGame.cpp +) + +serenity_app(MasterWord ICON app-masterword) +target_link_libraries(MasterWord LibGUI LibConfig LibMain LibDesktop) diff --git a/Userland/Games/MasterWord/WordGame.cpp b/Userland/Games/MasterWord/WordGame.cpp new file mode 100644 index 0000000000..e916ede9d7 --- /dev/null +++ b/Userland/Games/MasterWord/WordGame.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2022, Joe Petrus + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "WordGame.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: Add stats + +WordGame::WordGame() +{ + read_words(); + m_num_letters = Config::read_i32("MasterWord", "", "word_length", 5); + m_max_guesses = Config::read_i32("MasterWord", "", "max_guesses", 6); + reset(); + pick_font(); +} + +void WordGame::reset() +{ + m_current_guess = {}; + m_guesses.clear(); + auto maybe_word = WordGame::random_word(m_num_letters); + if (maybe_word.has_value()) + m_current_word = maybe_word.value(); + else { + GUI::MessageBox::show(window(), String::formatted("Could not get a random {} letter word. Defaulting to 5.", m_num_letters), "MasterWord"); + if (m_num_letters != 5) { + m_num_letters = 5; + reset(); + } + } + update(); +} + +void WordGame::pick_font() +{ + String best_font_name; + auto best_font_size = -1; + auto& font_database = Gfx::FontDatabase::the(); + font_database.for_each_font([&](Gfx::Font const& font) { + if (font.family() != "Liza" || font.weight() != 700) + return; + auto size = font.glyph_height(); + if (size * 2 <= m_letter_height && size > best_font_size) { + best_font_name = font.qualified_name(); + best_font_size = size; + } + }); + + auto font = font_database.get_by_name(best_font_name); + set_font(font); +} + +void WordGame::resize_event(GUI::ResizeEvent&) +{ + pick_font(); + update(); +} + +void WordGame::keydown_event(GUI::KeyEvent& event) +{ + // If we can still add a letter and the key was alpha + if (m_current_guess.length() < m_num_letters && is_ascii_alpha(event.code_point())) { + m_current_guess = String::formatted("{}{}", m_current_guess, event.text().to_uppercase()); + } + // If backspace pressed and already have some letters entered + else if (event.key() == KeyCode::Key_Backspace && m_current_guess.length() > 0) { + m_current_guess = m_current_guess.substring(0, m_current_guess.length() - 1); + } + // If enough letters and return pressed + else if (m_current_guess.length() == m_num_letters && event.key() == KeyCode::Key_Return) { + add_guess(m_current_guess); + auto won = m_current_guess == m_current_word; + m_current_guess = {}; + if (won) { + GUI::MessageBox::show(window(), "You win!", "MasterWord"); + reset(); + } else if (m_guesses.size() == m_max_guesses) { + GUI::MessageBox::show(window(), String::formatted("You lose!\nThe word was {}", m_current_word), "MasterWord"); + reset(); + } + } + + update(); +} + +void WordGame::paint_event(GUI::PaintEvent& event) +{ + GUI::Frame::paint_event(event); + GUI::Painter painter(*this); + painter.add_clip_rect(frame_inner_rect()); + painter.add_clip_rect(event.rect()); + painter.fill_rect(event.rect(), m_background_color); + + for (size_t guess_index = 0; guess_index < m_max_guesses; ++guess_index) { + for (size_t letter_index = 0; letter_index < m_num_letters; ++letter_index) { + auto this_rect = letter_rect(guess_index, letter_index); + + if (guess_index < m_guesses.size()) { + + switch (m_guesses[guess_index].letter_states.at(letter_index)) { + case Correct: + painter.fill_rect(this_rect, m_right_letter_right_spot_color); + break; + case WrongSpot: + painter.fill_rect(this_rect, m_right_letter_wrong_spot_color); + break; + case Incorrect: + painter.fill_rect(this_rect, m_wrong_letter_color); + break; + } + + painter.draw_text(this_rect, m_guesses[guess_index].text.substring_view(letter_index, 1), font(), Gfx::TextAlignment::Center, m_text_color); + } else if (guess_index == m_guesses.size()) { + if (letter_index < m_current_guess.length()) + painter.draw_text(this_rect, m_current_guess.substring_view(letter_index, 1), font(), Gfx::TextAlignment::Center, m_text_color); + } + + painter.draw_rect(this_rect, m_border_color); + } + } +} + +Gfx::IntRect WordGame::letter_rect(size_t guess_number, size_t letter_number) const +{ + auto letter_left = m_outer_margin + letter_number * m_letter_width + m_letter_spacing * letter_number; + auto letter_top = m_outer_margin + guess_number * m_letter_height + m_letter_spacing * guess_number; + return Gfx::IntRect(int(letter_left), int(letter_top), m_letter_width, m_letter_height); +} + +void WordGame::read_words() +{ + m_words.clear(); + auto response = Core::File::open("/res/words.txt", Core::OpenMode::ReadOnly); + if (response.is_error()) { + GUI::MessageBox::show(nullptr, "Could not read /res/words.txt.\nPlease ensure this file exists and restart MasterWord.", "MasterWord"); + exit(0); + } + auto words_file = response.value(); + + while (!words_file->eof()) { + auto current_word = words_file->read_line(); + if (!current_word.starts_with('#') and current_word.length() > 0) + m_words.ensure(current_word.length()).append(current_word); + } +} + +Optional WordGame::random_word(size_t length) +{ + auto words_for_length = m_words.get(length); + if (words_for_length.has_value()) { + auto i = get_random_uniform(words_for_length->size()); + return words_for_length->at(i).to_uppercase(); + } + + return {}; +} + +size_t WordGame::shortest_word() +{ + auto available_lengths = m_words.keys(); + AK::quick_sort(available_lengths); + return available_lengths.first(); +} + +size_t WordGame::longest_word() +{ + auto available_lengths = m_words.keys(); + AK::quick_sort(available_lengths); + return available_lengths.last(); +} + +void WordGame::set_use_system_theme(bool b) +{ + if (b) { + auto theme = palette(); + m_right_letter_wrong_spot_color = Color::from_rgb(0xb59f3b); + m_right_letter_right_spot_color = Color::from_rgb(0x538d4e); + m_border_color = Color::Black; + m_wrong_letter_color = theme.window(); + m_background_color = theme.window(); + m_text_color = theme.accent(); + } else { + m_right_letter_wrong_spot_color = Color::from_rgb(0xb59f3b); + m_right_letter_right_spot_color = Color::from_rgb(0x538d4e); + m_border_color = Color::from_rgb(0x3a3a3c); + m_wrong_letter_color = m_border_color; + m_background_color = Color::from_rgb(0x121213); + m_text_color = Color::White; + } + + update(); +} + +void WordGame::set_word_length(size_t length) +{ + m_num_letters = length; + reset(); +} + +void WordGame::set_max_guesses(size_t max_guesses) +{ + m_max_guesses = max_guesses; + reset(); +} + +Gfx::IntSize WordGame::game_size() const +{ + auto w = 2 * m_outer_margin + m_num_letters * m_letter_width + (m_num_letters - 1) * m_letter_spacing; + auto h = 2 * m_outer_margin + m_max_guesses * m_letter_height + (m_max_guesses - 1) * m_letter_spacing; + return Gfx::IntSize(w, h); +} + +void WordGame::add_guess(AK::StringView guess) +{ + AK::Vector letter_states; + + auto number_correct_for_letter = [this, &guess](StringView letter) -> size_t { + VERIFY(m_current_word.length() == guess.length()); + auto correct_count = 0; + for (size_t i = 0; i < m_current_word.length(); ++i) { + if (m_current_word.substring_view(i, 1) == letter && guess.substring_view(i, 1) == letter) + ++correct_count; + } + return correct_count; + }; + + for (size_t letter_index = 0; letter_index < m_num_letters; ++letter_index) { + auto guess_letter = guess.substring_view(letter_index, 1); + + if (m_current_word[letter_index] == guess_letter[0]) + letter_states.append(Correct); + else if (m_current_word.contains(guess_letter)) { + auto occurrences_in_word = m_current_word.count(guess_letter); + auto occurrences_in_guess_already_counted = guess.substring_view(0, letter_index).count(guess_letter); + auto correct = number_correct_for_letter(guess_letter); + if (occurrences_in_word > correct && occurrences_in_guess_already_counted < occurrences_in_word) + letter_states.append(WrongSpot); + else + letter_states.append(Incorrect); + } else + letter_states.append(Incorrect); + } + + m_guesses.append({ guess, letter_states }); + update(); +} diff --git a/Userland/Games/MasterWord/WordGame.h b/Userland/Games/MasterWord/WordGame.h new file mode 100644 index 0000000000..1c62605fd3 --- /dev/null +++ b/Userland/Games/MasterWord/WordGame.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, Joe Petrus + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +class WordGame : public GUI::Frame { + C_OBJECT(WordGame); + +public: + virtual ~WordGame() override = default; + + void reset(); + void set_use_system_theme(bool b); + void set_word_length(size_t length); + void set_max_guesses(size_t max_guesses); + Gfx::IntSize game_size() const; + + Optional random_word(size_t length); + size_t shortest_word(); + size_t longest_word(); + + void add_guess(AK::StringView guess); + +private: + WordGame(); + void read_words(); + + virtual void paint_event(GUI::PaintEvent&) override; + virtual void keydown_event(GUI::KeyEvent&) override; + virtual void resize_event(GUI::ResizeEvent&) override; + void pick_font(); + + Gfx::IntRect letter_rect(size_t guess_number, size_t letter_number) const; + + size_t m_max_guesses { 6 }; + size_t m_num_letters { 5 }; + static constexpr int m_letter_width { 40 }; + static constexpr int m_letter_spacing { 5 }; + static constexpr int m_outer_margin { 20 }; + static constexpr int m_letter_height { 60 }; + + Color m_right_letter_wrong_spot_color { Color::from_rgb(0xb59f3b) }; + Color m_right_letter_right_spot_color { Color::from_rgb(0x538d4e) }; + Color m_border_color { Color::from_rgb(0x3a3a3c) }; + Color m_wrong_letter_color { m_border_color }; + Color m_background_color { Color::from_rgb(0x121213) }; + Color m_text_color { Color::White }; + + enum LetterState { + Correct, + WrongSpot, + Incorrect + }; + + struct Guess { + AK::String text; + AK::Vector letter_states; + }; + + AK::Vector m_guesses; + AK::String m_current_guess; + AK::String m_current_word; + + HashMap> m_words; +}; diff --git a/Userland/Games/MasterWord/main.cpp b/Userland/Games/MasterWord/main.cpp new file mode 100644 index 0000000000..2d17318e26 --- /dev/null +++ b/Userland/Games/MasterWord/main.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2022, Joe Petrus + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "WordGame.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments arguments) +{ + TRY(Core::System::pledge("stdio rpath recvfd sendfd unix")); + + auto app = TRY(GUI::Application::try_create(arguments)); + + Config::pledge_domain("MasterWord"); + + TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_protocol("/usr/share/man/man6/MasterWord.md") })); + TRY(Desktop::Launcher::seal_allowlist()); + + TRY(Core::System::pledge("stdio rpath recvfd sendfd")); + + TRY(Core::System::unveil("/res", "r")); + TRY(Core::System::unveil("/tmp/portal/launch", "rw")); + TRY(Core::System::unveil(nullptr, nullptr)); + + auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-masterword")); + + auto window = TRY(GUI::Window::try_create()); + + window->set_double_buffering_enabled(false); + window->set_title("MasterWord"); + window->set_resizable(false); + + auto game = TRY(window->try_set_main_widget()); + + auto use_system_theme = Config::read_bool("MasterWord", "", "use_system_theme", false); + game->set_use_system_theme(use_system_theme); + + auto shortest_word = game->shortest_word(); + auto longest_word = game->longest_word(); + + window->resize(game->game_size()); + + auto game_menu = TRY(window->try_add_menu("&Game")); + + TRY(game_menu->try_add_action(GUI::Action::create("&New Game", { Mod_None, Key_F2 }, [&](auto&) { + game->reset(); + }))); + TRY(game_menu->try_add_action(GUI::Action::create("Set &Word Length", [&](auto&) { + auto word_length = Config::read_i32("MasterWord", "", "word_length", 5); + auto word_length_string = String::number(word_length); + if (GUI::InputBox::show(window, word_length_string, "Word length:", "MasterWord") == GUI::InputBox::ExecOK && !word_length_string.is_empty()) { + auto maybe_word_length = word_length_string.template to_uint(); + if (!maybe_word_length.has_value() || maybe_word_length.value() < shortest_word || maybe_word_length.value() > longest_word) { + GUI::MessageBox::show(window, String::formatted("Please enter a number between {} and {}.", shortest_word, longest_word), "MasterWord"); + return; + } + + word_length = maybe_word_length.value(); + Config::write_i32("MasterWord", "", "word_length", word_length); + game->set_word_length(word_length); + window->resize(game->game_size()); + } + }))); + TRY(game_menu->try_add_action(GUI::Action::create("Set &Number Of Guesses", [&](auto&) { + auto max_guesses = Config::read_i32("MasterWord", "", "max_guesses", 5); + auto max_guesses_string = String::number(max_guesses); + if (GUI::InputBox::show(window, max_guesses_string, "Maximum number of guesses:", "MasterWord") == GUI::InputBox::ExecOK && !max_guesses_string.is_empty()) { + auto maybe_max_guesses = max_guesses_string.template to_uint(); + if (!maybe_max_guesses.has_value() || maybe_max_guesses.value() < 1 || maybe_max_guesses.value() > 20) { + GUI::MessageBox::show(window, "Please enter a number between 1 and 20.", "MasterWord"); + return; + } + + max_guesses = maybe_max_guesses.value(); + Config::write_i32("MasterWord", "", "max_guesses", max_guesses); + game->set_max_guesses(max_guesses); + window->resize(game->game_size()); + } + }))); + + TRY(game_menu->try_add_separator()); + TRY(game_menu->try_add_action(GUI::CommonActions::make_quit_action([](auto&) { + GUI::Application::the()->quit(); + }))); + + auto theme_menu = TRY(window->try_add_menu("&Theme")); + auto system_theme_action = GUI::Action::create("&System", [&](auto&) { + game->set_use_system_theme(true); + Config::write_bool("MasterWord", "", "use_system_theme", true); + }); + system_theme_action->set_checkable(true); + system_theme_action->set_checked(use_system_theme); + TRY(theme_menu->try_add_action(system_theme_action)); + + auto wordle_theme_action = GUI::Action::create("&Wordle", [&](auto&) { + game->set_use_system_theme(false); + Config::write_bool("MasterWord", "", "use_system_theme", false); + }); + wordle_theme_action->set_checkable(true); + wordle_theme_action->set_checked(!use_system_theme); + TRY(theme_menu->try_add_action(wordle_theme_action)); + + GUI::ActionGroup theme_actions; + theme_actions.set_exclusive(true); + theme_actions.set_unchecking_allowed(false); + theme_actions.add_action(system_theme_action); + theme_actions.add_action(wordle_theme_action); + + auto help_menu = TRY(window->try_add_menu("&Help")); + TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) { + Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man6/MasterWord.md"), "/bin/Help"); + }))); + TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("MasterWord", app_icon, window))); + + window->show(); + + window->set_icon(app_icon.bitmap_for_size(16)); + + return app->exec(); +}