From a4178b89e70765c1d49a4c5cab559525b0f75e4b Mon Sep 17 00:00:00 2001 From: myrmidex Date: Tue, 1 Sep 2026 00:52:25 +0200 Subject: [PATCH] Merge letters and numbers into a single countdown command --- bin/countdown | 16 ++++ bin/letters | 12 --- bin/numbers | 12 --- src/Commands/CountdownCommand.php | 41 +++++++++++ src/Commands/FindAnagramWordsCommand.php | 56 -------------- src/Commands/FindNumbersCommand.php | 62 ---------------- src/Commands/LettersCommand.php | 93 ++++++++++++++++++++++++ src/Commands/NumbersCommand.php | 81 +++++++++++++++++++++ 8 files changed, 231 insertions(+), 142 deletions(-) create mode 100755 bin/countdown delete mode 100755 bin/letters delete mode 100755 bin/numbers create mode 100644 src/Commands/CountdownCommand.php delete mode 100644 src/Commands/FindAnagramWordsCommand.php delete mode 100644 src/Commands/FindNumbersCommand.php create mode 100644 src/Commands/LettersCommand.php create mode 100644 src/Commands/NumbersCommand.php diff --git a/bin/countdown b/bin/countdown new file mode 100755 index 0000000..01de784 --- /dev/null +++ b/bin/countdown @@ -0,0 +1,16 @@ +#!/usr/bin/env php +addCommand(new CountdownCommand()); +$application->addCommand(new LettersCommand()); +$application->addCommand(new NumbersCommand()); +$application->setDefaultCommand('countdown'); +$application->run(); diff --git a/bin/letters b/bin/letters deleted file mode 100755 index fe608fd..0000000 --- a/bin/letters +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env php -addCommand(new FindAnagramWordsCommand()); -$application->setDefaultCommand('countdown:letters'); -$application->run(); diff --git a/bin/numbers b/bin/numbers deleted file mode 100755 index 34ddc4a..0000000 --- a/bin/numbers +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env php -addCommand(new FindNumbersCommand()); -$application->setDefaultCommand('countdown:numbers'); -$application->run(); diff --git a/src/Commands/CountdownCommand.php b/src/Commands/CountdownCommand.php new file mode 100644 index 0000000..469802a --- /dev/null +++ b/src/Commands/CountdownCommand.php @@ -0,0 +1,41 @@ + 'letters', + 'numbers' => 'numbers', + 'quit' => 'Quit', + ]); + + if ($game === 'quit') { + return Command::SUCCESS; + } + + match ($game) { + 'letters' => new LettersCommand()(null, false, false), + 'numbers' => new NumbersCommand()(), + }; + + pause('Press enter to return to the menu...'); + } + } +} diff --git a/src/Commands/FindAnagramWordsCommand.php b/src/Commands/FindAnagramWordsCommand.php deleted file mode 100644 index 289a082..0000000 --- a/src/Commands/FindAnagramWordsCommand.php +++ /dev/null @@ -1,56 +0,0 @@ - Matcher::findWords($letters), - message: 'Checking dictionary...', - ); - - if ($all) { - $matches = $matches->slice(0, 10); - } - - if ($single) { - $matches = $matches->first(); - } - - table( - headers: ["word", "length"], - rows: $matches->map(fn(string $word) => ['word' => $word, 'length' => strlen($word)])->toArray(), - ); - - return Command::SUCCESS; - } -} diff --git a/src/Commands/FindNumbersCommand.php b/src/Commands/FindNumbersCommand.php deleted file mode 100644 index ebdbbe7..0000000 --- a/src/Commands/FindNumbersCommand.php +++ /dev/null @@ -1,62 +0,0 @@ -filter(fn (string $number) => ctype_digit($number)) - ->map(fn (string $number) => intval($number)) - ); - - $target = text(label: "Enter the target number", required: true); - - if (! ctype_digit($target)) { - error("Invalid target number"); - - return self::FAILURE; - } - - $target = intval($target); - - $result = spin( - callback: fn() => new NumbersService($numbers)->solve($target), - message: 'Calculating...', - ); - - $distance = (abs($target - $result->getResult())); - - callout( - label: $distance === 0 ? 'Found!' : "Off by {$distance}", - content: $result->getSteps()->map(fn(Step $s) => $s->toString())->all(), - type: $distance === 0 ? 'success' : 'warning', - info: 'Steps needed to get to the solution', - ); - - return self::SUCCESS; - } -} diff --git a/src/Commands/LettersCommand.php b/src/Commands/LettersCommand.php new file mode 100644 index 0000000..7f03b62 --- /dev/null +++ b/src/Commands/LettersCommand.php @@ -0,0 +1,93 @@ +text( + label: 'Your nine letters', + placeholder: 'raisedon', + required: 'Enter the letters you were given.', + validate: fn(string $value) => match (true) { + ! ctype_alpha($this->normalise($value)) => 'Letters only, no digits or symbols', + strlen($this->normalise($value)) < 3 => 'Enter at least three letters', + default => null, + }, + hint: 'With or without spaces', + name: 'letters', + ) + ->submit(); + + $letters = $this->normalise($responses['letters']); + + note(sprintf('Solving %s', strtoupper($letters))); + + $matches = spin( + callback: fn() => Matcher::findWords($letters), + message: 'Checking dictionary...', + ); + + if ($matches->isEmpty()) { + callout( + label: 'No words found', + content: 'Nothing in the dictionary matches those letters.', + type: 'warning', + ); + + return self::SUCCESS; + } + + $top = $this->rank($matches)->take(self::TOP); + $best = strlen($top->first()); + + callout( + label: "Best: {$top->first()} ({$best} letters)", + content: sprintf('%d words found', $matches->count()), + type: $best >= 8 ? 'success' : 'info', + ); + + table( + headers: ['#', 'Word', 'Length'], + rows: $top->values()->map(fn(string $word, int $i) => [ + $i + 1, + $word, + strlen($word), + ])->all(), + ); + + return self::SUCCESS; + } + + private function rank(Collection $matches): Collection + { + return $matches->sortBy([ + fn(string $a, string $b) => strlen($b) <=> strlen($a), + fn(string $a, string $b) => $a <=> $b, + ]); + } + + private function normalise(string $input): string + { + return strtolower(preg_replace('/\s+/', '', trim($input))); + } +} diff --git a/src/Commands/NumbersCommand.php b/src/Commands/NumbersCommand.php new file mode 100644 index 0000000..f04e93d --- /dev/null +++ b/src/Commands/NumbersCommand.php @@ -0,0 +1,81 @@ +text( + label: 'Your six numbers', + placeholder: '25 50 75 100 3 6', + required: 'Enter the numbers you were given.', + validate: fn(string $value) => $this->parse($value)->count() < 2 + ? 'Enter at least two numbers, separated by spaces' + : null, + hint: 'Separated by spaces', + name: 'numbers', + ) + ->add( + fn(array $responses, $previous) => number( + label: 'Target', + placeholder: '952', + required: 'Enter the target number.', + hint: 'The number to reach', + min: 1, + max: 999, + ), + name: 'target', + ) + ->submit(); + + $numbers = $this->parse($responses['numbers']); + $target = (int) $responses['target']; + + note(sprintf('Solving %s -> %d', $numbers->implode(' '), $target)); + + $result = spin( + callback: fn() => new NumbersService(new NumbersBag($numbers))->solve($target), + message: 'Searching for a solution...', + ); + + $distance = (abs($target - $result->getResult())); + + callout( + label: $distance === 0 + ? "Exactly {$target}" + : "{$result->getResult()} - off by {$distance}", + content: $result->getSteps()->map(fn(Step $s) => $s->toString())->all(), + type: $distance === 0 ? 'success' : 'warning', + info: $result->getSteps()->count() . ' steps', + ); + + return self::SUCCESS; + } + + private function parse(string $input): Collection + { + return new Collection(preg_split('/\s+/', trim($input), -1, PREG_SPLIT_NO_EMPTY)) + ->filter(fn(string $number) => ctype_digit($number)) + ->map(fn(string $number) => (int) $number); + } +}