UNPKG

termdle

Version:

A fully-featured Wordle clone that runs entirely in your terminal! Built with TypeScript, React, and Ink.

15 lines 22.6 kB
#!/usr/bin/env node import{exit as e}from"process";import{Box as t,Text as n,render as r,useApp as i,useInput as a}from"ink";import o from"ink-gradient";import{jsx as s,jsxs as c}from"react/jsx-runtime";import{useEffect as l,useState as u}from"react";import{atom as d,useAtomValue as f,useSetAtom as p}from"jotai";import{existsSync as m,readFileSync as h,writeFileSync as g}from"fs";import{join as _}from"path";import{homedir as v}from"os";const y=e=>{if(!e)return 0;let t=e.split(` `);return Math.max(...t.map(e=>e.length))},b=({terminalWidth:e,terminalHeight:r})=>{let i=y(` ████████╗███████╗██████╗ ███╗ ███╗██████╗ ██╗ ███████╗ ╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝ ██║ █████╗ ██████╔╝██╔████╔██║██║ ██║██║ █████╗ ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║██║ ██║██║ ██╔══╝ ██║ ███████╗██║ ██║██║ ╚═╝ ██║██████╔╝███████╗███████╗ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝ `),a=e>=i&&r>=36?`████████╗███████╗██████╗ ███╗ ███╗██████╗ ██╗ ███████╗ ╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝ ██║ █████╗ ██████╔╝██╔████╔██║██║ ██║██║ █████╗ ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║██║ ██║██║ ██╔══╝ ██║ ███████╗██║ ██║██║ ╚═╝ ██║██████╔╝███████╗███████╗ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝`:`Termdle`,c=y(a);return s(t,{flexDirection:`column`,width:c,alignItems:`flex-start`,children:s(o,{name:`vice`,children:s(n,{children:a})})})},x=`about.above.abuse.actor.acute.admit.adopt.adult.after.again.agent.agree.ahead.alarm.album.alert.alien.align.alike.alive.allow.alone.along.alter.among.anger.angle.angry.apart.apple.apply.arena.argue.arise.array.aside.asset.avoid.awake.award.aware.badly.baker.bases.basic.beach.began.begin.being.below.bench.billy.birth.black.blame.blind.block.blood.board.boost.booth.bound.brain.brand.bread.break.breed.brief.bring.broad.broke.brown.build.built.buyer.cable.carry.catch.cause.chain.chair.chaos.charm.chart.chase.cheap.check.chest.chief.child.china.chose.civil.claim.class.clean.clear.click.climb.clock.close.cloud.coach.coast.could.count.court.cover.craft.crash.crazy.cream.crime.cross.crowd.crown.crude.curve.cycle.daily.dance.dated.dealt.death.debut.delay.depth.doing.doubt.dozen.draft.drama.drank.dream.dress.drill.drink.drive.drove.dying.eager.early.earth.eight.elite.empty.enemy.enjoy.enter.entry.equal.error.event.every.exact.exist.extra.faith.false.fault.fiber.field.fifth.fifty.fight.final.first.fixed.flash.fleet.floor.fluid.focus.force.forth.forty.forum.found.frame.frank.fraud.fresh.front.fruit.fully.funny.giant.given.glass.globe.going.grace.grade.grand.grant.grass.grave.great.green.gross.group.grown.guard.guess.guest.guide.happy.harry.heart.heavy.hence.henry.horse.hotel.house.human.hurry.image.index.inner.input.issue.japan.jimmy.joint.jones.judge.known.label.large.laser.later.laugh.layer.learn.lease.least.leave.legal.level.lewis.light.limit.links.lives.local.loose.lower.lucky.lunch.lying.magic.major.maker.march.maria.match.maybe.mayor.meant.media.metal.might.minor.minus.mixed.model.money.month.moral.motor.mount.mouse.mouth.moved.movie.music.needs.never.newly.night.noise.north.noted.novel.nurse.occur.ocean.offer.often.order.other.ought.paint.panel.paper.party.peace.peter.phase.phone.photo.piano.piece.pilot.pitch.place.plain.plane.plant.plate.point.pound.power.press.price.pride.prime.print.prior.prize.proof.proud.prove.queen.quick.quiet.quite.radio.raise.range.rapid.ratio.reach.ready.realm.rebel.refer.relax.repay.reply.right.rigid.rival.river.robin.roger.roman.rough.round.route.royal.rural.scale.scene.scope.score.sense.serve.seven.shall.shape.share.sharp.sheet.shelf.shell.shift.shine.shirt.shock.shoot.short.shown.sides.sight.silly.since.sixth.sixty.sized.skill.sleep.slide.small.smart.smile.smith.smoke.snake.solid.solve.sorry.sound.south.space.spare.speak.speed.spend.spent.split.spoke.sport.staff.stage.stake.stand.start.state.steam.steel.stick.still.stock.stone.stood.store.storm.story.strip.stuck.study.stuff.style.sugar.suite.super.sweet.table.taken.taste.taxes.teach.teeth.terry.texas.thank.theft.their.theme.there.these.thick.thing.think.third.those.three.threw.throw.thumb.tiger.tight.timer.title.today.topic.total.touch.tough.tower.track.trade.train.treat.trend.trial.tribe.trick.tried.tries.truck.truly.trunk.trust.truth.twice.uncle.under.union.unity.until.upper.upset.urban.usage.usual.valid.value.video.virus.visit.vital.vocal.voice.waste.watch.water.weary.wheel.where.which.while.white.whole.whose.woman.women.world.worry.worse.worst.worth.would.write.wrong.wrote.yield.young.youth`.split(`.`),S=new Set([...x,`hello`,`world`,`games`,`words`,`plays`,`times`,`years`,`looks`,`makes`,`comes`,`gives`,`takes`,`feels`,`means`,`works`,`keeps`,`seems`,`turns`,`shows`,`moves`,`hands`,`books`,`lines`,`parts`,`helps`,`finds`,`calls`,`tells`,`wants`,`needs`,`opens`,`takes`,`stops`,`talks`,`walks`,`reads`,`lives`,`loves`,`hates`,`likes`,`knows`,`hopes`,`tries`,`falls`,`rises`,`rides`,`flies`,`jumps`,`aahed`,`aalii`,`aargh`,`abaca`,`abaci`,`abaft`,`abaka`,`abamp`,`abash`,`abate`,`abaya`,`abbey`,`abbot`,`abear`,`abele`,`abers`,`abets`,`abhor`,`abide`,`abled`,`abler`,`ables`,`abmho`,`abode`,`abohm`,`aboil`,`aboma`,`aboon`,`abort`,`abray`,`abrim`,`abrin`,`abris`,`abuna`,`abune`,`abuts`,`abuzz`,`abyes`,`abysm`,`abyss`,`acais`,`acari`,`accas`,`accoy`,`acedy`,`acers`,`aceta`,`achar`,`ached`,`aches`,`achoo`,`acids`,`acidy`,`acing`,`acini`,`ackee`,`acker`,`acmes`,`acmic`,`acned`,`acnes`,`acock`,`acold`,`acorn`,`acred`,`acres`,`acrid`,`acron`,`acted`,`actin`,`acton`,`actos`,`acyls`,`adage`,`adapt`,`adaws`,`adays`,`addax`,`added`,`adder`,`addio`,`addle`,`adeem`,`adept`,`adhan`,`adieu`,`adios`,`adits`,`adjag`,`admix`,`adobe`,`adobo`,`adore`,`adorn`,`adown`,`adoze`,`adrad`,`adred`,`adsum`,`adunc`,`adust`,`advew`,`aecia`,`aedes`,`aegis`,`aeons`,`aeros`,`aesir`,`afald`,`afars`,`afear`,`affix`,`afire`,`aflaj`,`afoot`,`afore`,`afoul`,`afrit`,`afros`,`agama`,`agape`,`agars`,`agast`,`agate`,`agave`,`agaze`,`agene`,`agers`,`agger`,`aggie`,`aggro`,`aghas`,`agile`,`aging`,`agios`,`agism`,`agist`,`agita`,`aglet`,`agley`,`agloo`,`aglow`,`agmas`,`agmip`,`agone`,`agons`,`agony`,`agood`,`agora`,`agria`,`agrin`,`agros`,`agued`,`agues`,`aguti`,`ahead`,`aheap`,`ahent`,`ahigh`,`ahind`,`ahing`,`ahint`,`ahold`,`ahull`,`ahuru`,`aider`,`aides`,`aidoi`,`aidos`,`aiery`,`aigas`,`aight`,`ailed`,`aimed`,`aimer`,`ainee`,`ainga`,`aioli`,`aired`,`airer`,`airns`,`airth`,`airts`,`aisle`,`aitch`,`aitus`,`aiver`,`aizle`,`ajapa`,`ajiva`,`ajuga`,`ajwan`,`akees`,`akela`,`akene`,`aking`,`akita`,`akkas`,`alaap`,`alack`,`alamo`,`aland`,`alane`,`alang`,`alans`,`alant`,`alapa`,`alaps`,`alary`,`alate`,`alays`,`albas`,`albee`,`albos`,`alcid`,`alcos`,`aldea`,`alder`,`aldol`,`aleak`,`alear`,`aleck`,`alecs`,`alefs`,`aleft`,`aleme`,`aleye`,`alfas`,`algae`,`algas`,`algid`,`algin`,`algor`,`algum`,`alias`,`alibi`,`alick`,`alifs`,`align`,`alike`,`aline`,`alist`,`alive`,`aliya`,`alkie`,`alkos`,`alkyd`,`alkyl`,`allay`,`allee`,`allel`,`alley`,`allis`,`allod`,`alloy`,`allyl`,`almah`,`almas`,`almeh`,`almes`,`almud`,`almug`,`alods`,`aloed`,`aloes`,`aloft`,`aloha`,`aloin`,`alone`,`along`,`aloof`,`aloud`,`alowe`,`alpha`,`altar`,`alter`,`altho`,`altos`,`alula`,`alums`,`alure`,`alway`,`amahs`,`amain`,`amalg`,`amang`,`amans`,`amant`,`amate`,`amaze`,`amban`,`amber`,`ambit`,`amble`,`ambos`,`ambry`,`ameba`,`ameer`,`amend`,`amens`,`ament`,`amias`,`amice`,`amici`,`amide`,`amido`,`amids`,`amies`,`amiga`,`amigo`,`amine`,`amino`,`amins`,`amirs`,`amiss`,`amity`,`amlas`,`ammil`,`ammon`,`ammos`,`amnia`,`amnic`,`amnio`,`amoks`,`amole`,`among`,`amort`,`amour`,`amove`,`amowt`,`amped`,`ample`,`amply`,`ampul`,`amrit`,`amuck`,`amuse`,`badge`,`bagel`,`balls`,`bands`,`banks`,`barks`,`beard`,`bears`,`beast`,`beats`,`belly`,`bikes`,`bills`,`birds`,`blade`,`blank`,`blast`,`blaze`,`bleed`,`blend`,`bless`,`blink`,`bloom`,`blown`,`blues`,`blunt`,`blush`,`boast`,`boats`,`bobby`,`bones`,`bonus`,`boots`,`boxes`,`brake`,`brass`,`brave`,`brick`,`bride`,`brink`,`brook`,`brush`,`buddy`,`bunch`,`bunny`,`burst`,`buses`,`buzzy`,`cakes`,`calls`,`camel`,`camps`,`canal`,`candy`,`canoe`,`cards`,`cared`,`cargo`,`carve`,`cases`,`caves`,`cease`,`chalk`,`champ`,`cheat`,`cheek`,`cheer`,`chess`,`chick`,`chill`,`chips`,`choir`,`clamp`,`clerk`,`cliff`,`cloth`,`clubs`,`clues`,`coats`,`codes`,`coins`,`coral`,`costs`,`couch`,`cough`,`crack`,`crisp`,`crops`,`cruel`,`crush`,`dairy`,`daisy`,`deals`,`debug`,`decks`,`delta`,`dense`,`desks`,`diced`,`diets`,`digit`,`dirty`,`disco`,`diver`,`dizzy`,`dodge`,`dolls`,`doors`,`doses`,`dough`,`doves`,`drags`,`drake`,`draws`,`dried`,`drops`,`drugs`,`drums`,`drunk`,`ducks`,`dunes`,`dusty`,`eagle`,`eaten`,`eater`,`edges`,`elder`,`elect`,`email`,`essay`,`exams`,`excel`,`exits`,`fable`,`faced`,`facts`,`faded`,`fails`,`fairy`,`falls`,`fancy`,`farms`,`fatal`,`favor`,`fears`,`feast`,`feeds`,`fence`,`fetch`,`fewer`,`fiery`,`files`,`fills`,`films`,`fines`,`fired`,`fires`,`firms`,`flags`,`flame`,`flare`,`flask`,`flesh`,`float`,`flock`,`flood`,`flour`,`flows`,`flute`,`folks`,`fonts`,`foods`,`fools`,`forge`,`forms`,`freed`,`fried`,`frost`,`fuels`,`funds`,`fuzzy`,`gangs`,`gates`,`gears`,`genre`,`ghost`,`gifts`,`girls`,`gives`,`gloom`,`glory`,`glove`,`glows`,`goals`,`goats`,`goods`,`grain`,`grape`,`graph`,`grasp`,`greed`,`greet`,`grief`,`grill`,`grind`,`grips`,`grove`,`grows`,`guilt`,`habit`,`hails`,`hairs`,`halls`,`handy`,`hangs`,`harsh`,`haste`,`hated`,`hates`,`heads`,`heals`,`heard`,`hedge`,`heels`,`helps`,`herbs`,`hides`,`highs`,`hills`,`hinds`,`hints`,`hired`,`hires`,`hoard`,`hobby`,`holds`,`holes`,`homes`,`honey`,`hooks`,`hoped`,`hopes`,`horns`,`hours`,`howls`,`humor`,`hurts`,`icons`,`ideal`,`ideas`,`idiom`,`imply`,`inbox`,`indie`,`items`,`jails`,`james`,`jeans`,`jewel`,`joins`,`jokes`,`juice`,`jumbo`,`keeps`,`kicks`,`kills`,`kinds`,`kings`,`kitty`,`knees`,`knife`,`knock`,`knots`,`knows`,`labor`,`lacks`,`lakes`,`lands`,`lanes`,`lasts`,`ledge`,`lemon`,`lever`,`liked`,`likes`,`lined`,`lines`,`lions`,`lists`,`lived`,`liver`,`loans`,`lobby`,`locks`,`lodge`,`logic`,`loops`,`lords`,`loser`,`loses`,`loved`,`lover`,`loves`,`loyal`,`lungs`,`mails`,`makes`,`males`,`malls`,`mange`,`marks`,`marry`,`masks`,`meals`,`meats`,`medal`,`meets`,`melon`,`melts`,`menus`,`mercy`,`merge`,`merit`,`merry`,`meter`,`miles`,`mills`,`minds`,`mines`,`mixes`,`moats`,`modal`,`modes`,`monks`,`moods`,`moths`,`mowed`,`myths`,`nails`,`naked`,`named`,`names`,`nasty`,`nerve`,`ninja`,`ninth`,`noble`,`nodes`,`nosed`,`noses`,`notes`,`oasis`,`oaths`,`older`,`olive`,`omega`,`onion`,`opens`,`opera`,`opted`,`orbit`,`organ`,`ounce`,`outer`,`owing`,`owned`,`owner`,`oxide`,`paced`,`pages`,`pairs`,`panic`,`pants`,`parks`,`parts`,`pasta`,`paste`,`patch`,`paths`,`pause`,`peaks`,`pears`,`penny`,`perch`,`perks`,`petty`,`picks`,`pills`,`pinch`,`pipes`,`pizza`,`plays`,`plaza`,`plots`,`plugs`,`poems`,`poets`,`poles`,`polls`,`pools`,`porch`,`poses`,`posts`,`probe`,`props`,`proxy`,`psalm`,`pulls`,`pumps`,`punch`,`pupil`,`purse`,`pushy`,`quals`,`query`,`quest`,`queue`,`quote`,`races`,`racks`,`raids`,`rails`,`rains`,`ranks`,`rated`,`rates`,`reads`,`recap`,`reign`,`relay`,`remix`,`rides`,`ridge`,`rifle`,`rings`,`rises`,`risks`,`roads`,`roars`,`roast`,`robes`,`robot`,`rocks`,`rocky`,`roles`,`rolls`,`roofs`,`rooms`,`roots`,`ropes`,`roses`,`ruins`,`ruled`,`ruler`,`rules`,`rumor`,`rusty`,`sails`,`saint`,`salad`,`sales`,`salon`,`sandy`,`sauce`,`saved`,`saves`,`scare`,`scent`,`screw`,`seals`,`seats`,`seeds`,`seems`,`sells`,`sends`,`setup`,`sever`,`shade`,`shake`,`shame`,`shark`,`shave`,`shiny`,`ships`,`shoes`,`shops`,`shore`,`shots`,`shows`,`shuts`,`signs`,`sings`,`sinks`,`sites`,`sizes`,`skate`,`skins`,`skips`,`skull`,`slate`,`slave`,`slips`,`slope`,`slots`,`smell`,`snaps`,`sneak`,`sniff`,`snore`,`snowy`,`socks`,`soils`,`solar`,`songs`,`sorts`,`souls`,`soups`,`spark`,`spell`,`spike`,`spine`,`spoon`,`spots`,`spray`,`squad`,`stack`,`stain`,`stair`,`stale`,`stamp`,`stare`,`stars`,`stays`,`steal`,`steep`,`steer`,`stems`,`steps`,`stern`,`sting`,`stink`,`stomp`,`stool`,`stops`,`surge`,`swaps`,`swear`,`sweat`,`sweep`,`swift`,`swing`,`swiss`,`sword`,`tails`,`takes`,`tales`,`talks`,`tapes`,`tasks`,`teams`,`tears`,`teddy`,`tells`,`tense`,`tenth`,`terms`,`tests`,`texts`,`thief`,`thump`,`tidal`,`tiles`,`timed`,`timer`,`tired`,`toast`,`token`,`tombs`,`tonic`,`tools`,`tooth`,`torch`,`tours`,`towns`,`toxic`,`trail`,`trait`,`tramp`,`trash`,`trees`,`trips`,`tubes`,`tuned`,`turns`,`twins`,`twist`,`typed`,`types`,`unfed`,`urged`,`users`,`using`,`utter`,`vapor`,`vault`,`veins`,`venue`,`verbs`,`verse`,`views`,`viral`,`voids`,`votes`,`wages`,`wagon`,`waist`,`waits`,`wakes`,`walks`,`walls`,`wants`,`warms`,`warns`,`waves`,`wears`,`weeds`,`weeks`,`weird`,`wells`,`welsh`,`wheat`,`widow`,`width`,`winds`,`wines`,`wings`,`winks`,`wipes`,`wired`,`wires`,`witch`,`wives`,`woods`,`worms`,`wound`,`wraps`,`yards`,`yeast`,`zones`]);function C(){let e=Math.floor(Math.random()*x.length),t=x[e];if(typeof t!=`string`)throw Error(`[Words]: Failed to grab a random target word`);return t.toLowerCase()}function w(e){return S.has(e.toLowerCase())}let T=function(e){return e.CORRECT=`correct`,e.PRESENT=`present`,e.ABSENT=`absent`,e}({});var E=class{state;constructor(){this.state={targetWord:C(),guesses:[],results:[],letterStates:new Map,currentGuess:0,gameOver:!1,won:!1,maxGuesses:6}}getState(){return{...this.state}}makeGuess(e){if(this.state.gameOver)return{valid:!1,message:`Game is already over!`};if(e.length!==5)return{valid:!1,message:`Guess must be exactly 5 letters!`};if(e.toLowerCase()!==this.state.targetWord.toLowerCase()&&!w(e.toLowerCase()))return{valid:!1,message:`Not a valid word!`};let t=this.evaluateGuess(e.toLowerCase());return this.updateLetterStates(t),this.state.guesses.push(e.toLowerCase()),this.state.results.push(t),this.state.currentGuess++,e.toLowerCase()===this.state.targetWord?(this.state.won=!0,this.state.gameOver=!0):this.state.currentGuess>=this.state.maxGuesses&&(this.state.gameOver=!0),{valid:!0,result:t}}evaluateGuess(e){let t=this.state.targetWord,n=e.split(``),r=t.split(``),i=[],a=new Map,o=new Set;for(let e of r)a.set(e,(a.get(e)||0)+1);for(let[e,t]of n.entries())if(t===r[e]){i[e]={letter:t,state:T.CORRECT},o.add(e);let n=a.get(t)||0;a.set(t,n-1)}for(let[e,t]of n.entries()){if(o.has(e))continue;let n=a.get(t)||0;n>0?(i[e]={letter:t,state:T.PRESENT},a.set(t,n-1)):i[e]={letter:t,state:T.ABSENT}}return i}updateLetterStates(e){for(let t of e){let e=t.letter.toLowerCase(),n=this.state.letterStates.get(e);n?(t.state===T.CORRECT||t.state===T.PRESENT&&n===T.ABSENT)&&this.state.letterStates.set(e,t.state):this.state.letterStates.set(e,t.state)}}reset(){this.state={targetWord:C(),guesses:[],results:[],letterStates:new Map,currentGuess:0,gameOver:!1,won:!1,maxGuesses:6}}revealAnswer(){return this.state.targetWord}setTargetWord(e){this.state.targetWord=e}};const D={[T.CORRECT]:`green`,[T.PRESENT]:`yellow`,[T.ABSENT]:`#383838`},O=({letter:e,state:r,isCompact:i=!1})=>{let a=D[r];return s(t,{width:i?3:5,height:i?2:3,borderStyle:`round`,borderColor:a,alignItems:`center`,justifyContent:`center`,children:s(n,{color:a,children:e.toUpperCase()})})};var k=class{statsFile;stats;constructor(){this.statsFile=_(v(),`.terminal-wordle-stats.json`),this.stats=this.loadStats()}loadStats(){let e={played:0,won:0,currentStreak:0,maxStreak:0,guessDistribution:[0,0,0,0,0,0]};try{if(m(this.statsFile)){let t=h(this.statsFile,`utf8`),n=JSON.parse(t);return{...e,...n,guessDistribution:n.guessDistribution||e.guessDistribution}}}catch(e){console.error(e),console.warn(`Could not load stats file, using defaults`)}return e}saveStats(){try{g(this.statsFile,JSON.stringify(this.stats,null,2))}catch(e){console.error(e),console.warn(`Could not save stats file`)}}recordGame(e,t){this.stats.played++,e&&t?(this.stats.won++,this.stats.currentStreak++,this.stats.currentStreak>this.stats.maxStreak&&(this.stats.maxStreak=this.stats.currentStreak),t>=1&&t<=6&&this.stats.guessDistribution[t-1]++):this.stats.currentStreak=0,this.saveStats()}getStats(){return{...this.stats}}resetStats(){this.stats={played:0,won:0,currentStreak:0,maxStreak:0,guessDistribution:[0,0,0,0,0,0]},this.saveStats()}getWinPercentage(){return this.stats.played===0?0:Math.round(this.stats.won/this.stats.played*100)}getAverageGuesses(){if(this.stats.won===0)return 0;let e=0;for(let[t,n]of this.stats.guessDistribution.entries())e+=(t+1)*n;return Math.round(e/this.stats.won*100)/100}};const A=new E,j=new k,M=d(A.getState()),N=d(null,(e,t,n)=>{A.makeGuess(n),t(M,A.getState())}),P=d(null,async(e,t)=>{A.reset(),t(M,A.getState())}),F=d(j.getStats()),I=d(null,(e,t,n,r)=>{j.recordGame(n,r),t(F,j.getStats())}),L=d(null,(e,t)=>{j.resetStats(),t(F,j.getStats())}),R=({isCompact:e})=>{let[r,i]=u([]),{results:o,currentGuess:d,maxGuesses:m,gameOver:h,won:g}=f(M),_=p(N),v=p(I);return l(()=>{h&&v(g,d)},[h,g,d,v]),a((e,t)=>{if(!h){if(t.backspace||t.delete){i(e=>e.slice(0,-1));return}/^[a-zA-Z]$/.test(e)&&r.length<5&&i(t=>[...t,e.toUpperCase()]),t.return&&(_(r.join(``)),i([]))}}),c(t,{flexDirection:`column`,marginY:e?0:1,children:[o.map((n,r)=>s(t,{children:n.map((t,n)=>s(O,{letter:t.letter,state:t.state,isCompact:e},`${n}`))},r)),[...Array(m-d)].map((i,a)=>s(t,{flexDirection:`row`,children:[...[,,,,,]].map((i,o)=>s(t,{width:e?3:5,height:e?2:3,borderStyle:`round`,borderColor:`white`,alignItems:`center`,justifyContent:`center`,children:a==0&&s(n,{children:r[o]||``})},`letter-${o}`))},`row-${a}`))]})},z=({isCompact:e})=>{let r=f(F);return c(t,{flexDirection:`column`,alignItems:`center`,marginTop:e?0:1,children:[s(n,{bold:!0,color:`cyan`,children:`📊 Statistics`}),c(t,{flexDirection:`row`,gap:e?1:2,children:[c(t,{flexDirection:`column`,alignItems:`center`,children:[s(n,{children:`Games Played`}),s(n,{bold:!0,children:r.played})]}),c(t,{flexDirection:`column`,alignItems:`center`,children:[s(n,{children:`Games Won`}),s(n,{bold:!0,children:r.won})]}),c(t,{flexDirection:`column`,alignItems:`center`,children:[s(n,{children:`Win Rate`}),c(n,{bold:!0,children:[r.played>0?Math.round(r.won/r.played*100):0,`%`]})]})]}),c(t,{flexDirection:`row`,gap:e?1:2,marginTop:e?0:1,children:[c(t,{flexDirection:`column`,alignItems:`center`,children:[s(n,{children:`Current Streak`}),s(n,{bold:!0,children:r.currentStreak})]}),c(t,{flexDirection:`column`,alignItems:`center`,children:[s(n,{children:`Max Streak`}),s(n,{bold:!0,children:r.maxStreak})]})]}),c(t,{flexDirection:`column`,alignItems:`center`,marginTop:e?0:1,children:[s(n,{bold:!0,children:`Guess Distribution`}),r.guessDistribution.map((e,t)=>c(n,{children:[t+1,`: `,e]},t))]})]})},B=({isCompact:e})=>{let r=[`qwertyuiop`,`asdfghjkl`,`zxcvbnm`],{letterStates:i}=f(M);return s(t,{flexDirection:`column`,alignItems:`center`,marginTop:e?0:1,children:r.map((r,a)=>s(t,{children:r.split(``).map(r=>{let a=i.get(r),o=a==null?`white`:D[a];return s(t,{width:e?3:4,height:2,borderStyle:`round`,borderColor:o,alignItems:`center`,justifyContent:`center`,children:s(n,{color:o,bold:!0,children:r.toUpperCase()})},r)})},a))})},V=({isCompact:e})=>{let{currentGuess:r,maxGuesses:l,gameOver:d,won:m,targetWord:h}=f(M),g=p(P),_=p(L),[v,y]=u(!1),{exit:b}=i();return a(e=>{if(d){let t=e.toLowerCase();t===`p`||t===`play`?(g(),y(!1)):t===`q`||t===`quit`?b():t===`rs`||t===`resetstats`?(_(),y(!1)):(y(!0),setTimeout(()=>y(!1),2e3))}}),s(t,{flexDirection:`column`,alignItems:`center`,children:d?c(t,{flexDirection:`column`,alignItems:`center`,children:[m?s(n,{color:`green`,bold:!0,children:`'🎉 You won! 🎉'`}):s(n,{color:`red`,bold:!0,children:`💀 Game Over! 💀`}),s(n,{children:m?`You solved it in ${r} ${r===1?`guess`:`guesses`}!`:`The word was: ${h.toUpperCase()}`}),s(z,{isCompact:e}),v&&s(n,{color:`red`,italic:!0,children:`Invalid input! Press 'p' to play again, or 'q' to quit`}),c(t,{flexDirection:`column`,alignItems:`center`,marginTop:1,children:[s(n,{bold:!0,children:`What would you like to do?`}),s(n,{color:`gray`,children:`Press 'p' to play again | 'q' to quit`})]})]}):c(t,{flexDirection:`column`,alignItems:`center`,children:[s(o,{name:`vice`,children:c(n,{color:`blue`,children:[`Guesses remaining: `,l-r]})}),s(B,{isCompact:e})]})})};function H(){let[e,t]=u({columns:process.stdout.columns||60,rows:process.stdout.rows||20});return l(()=>{function e(){t({columns:process.stdout.columns||60,rows:process.stdout.rows||20})}return process.stdout.on(`resize`,e),()=>{process.stdout.off(`resize`,e)}},[]),e}const U=()=>{let{rows:e,columns:n}=H(),r=n<60||e<30;return c(t,{flexDirection:`column`,width:n,alignItems:`center`,justifyContent:`center`,children:[s(b,{terminalWidth:n,terminalHeight:e}),s(R,{isCompact:r}),s(V,{isCompact:r})]})},W=()=>{let e=new Date,t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,`0`),r=String(e.getDate()).padStart(2,`0`);return`${t}-${n}-${r}`};async function G(){let e=W(),t=`https://www.nytimes.com/svc/wordle/v2/${e}.json`;try{let e=await fetch(t);if(!e.ok)throw Error(`Failed to fetch daily word: ${e.statusText}`);let n=await e.json();if(typeof n.solution==`string`&&n.solution.length===5)return n.solution.toLowerCase();throw Error(`Invalid data from NYT API`)}catch(e){return console.error(`[API]: Failed to fetch daily word, falling back to random word. ${e}`),C()}}try{let t=await G();A.setTargetWord(t);let{waitUntilExit:n}=r(s(U,{}),{exitOnCtrlC:!0});await n(),e()}catch(t){console.error({status:`app exited with error`,error:t}),e(1)}export{};