Merge letters and numbers into a single countdown command
This commit is contained in:
parent
8400c52ab6
commit
a4178b89e7
8 changed files with 231 additions and 142 deletions
16
bin/countdown
Executable file
16
bin/countdown
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../autoload.php';
|
||||
|
||||
use Lvl0\Countdown\TUI\Commands\CountdownCommand;
|
||||
use Lvl0\Countdown\TUI\Commands\LettersCommand;
|
||||
use Lvl0\Countdown\TUI\Commands\NumbersCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
$application = new Application('Countdown Solver', '0.1.3');
|
||||
$application->addCommand(new CountdownCommand());
|
||||
$application->addCommand(new LettersCommand());
|
||||
$application->addCommand(new NumbersCommand());
|
||||
$application->setDefaultCommand('countdown');
|
||||
$application->run();
|
||||
12
bin/letters
12
bin/letters
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../autoload.php';
|
||||
|
||||
use Lvl0\Countdown\TUI\Commands\FindAnagramWordsCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
$application = new Application('Countdown Letters Solver', '0.1.3');
|
||||
$application->addCommand(new FindAnagramWordsCommand());
|
||||
$application->setDefaultCommand('countdown:letters');
|
||||
$application->run();
|
||||
12
bin/numbers
12
bin/numbers
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../autoload.php';
|
||||
|
||||
use Lvl0\Countdown\TUI\Commands\FindNumbersCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
$application = new Application('Countdown Numbers Solver', '0.1.3');
|
||||
$application->addCommand(new FindNumbersCommand());
|
||||
$application->setDefaultCommand('countdown:numbers');
|
||||
$application->run();
|
||||
41
src/Commands/CountdownCommand.php
Normal file
41
src/Commands/CountdownCommand.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Lvl0\Countdown\TUI\Commands;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function Laravel\Prompts\clear;
|
||||
use function Laravel\Prompts\info;
|
||||
use function Laravel\Prompts\pause;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
#[AsCommand(name: 'countdown')]
|
||||
class CountdownCommand extends Command
|
||||
{
|
||||
public function __invoke(): int
|
||||
{
|
||||
clear();
|
||||
|
||||
while (true) {
|
||||
info("COUNTDOWN SOLVER");
|
||||
|
||||
$game = select('Which round?', [
|
||||
'letters' => '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...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Lvl0\Countdown\TUI\Commands;
|
||||
|
||||
use Lvl0\Countdown\Letters\Matcher;
|
||||
use Symfony\Component\Console\Attribute\Argument;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Attribute\Option;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function Laravel\Prompts\clear;
|
||||
use function Laravel\Prompts\info;
|
||||
use function Laravel\Prompts\spin;
|
||||
use function Laravel\Prompts\table;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
#[AsCommand(name: 'countdown:letters')]
|
||||
class FindAnagramWordsCommand extends Command
|
||||
{
|
||||
public function __invoke(
|
||||
#[Argument]
|
||||
?string $letters,
|
||||
#[Argument]
|
||||
bool $all = false,
|
||||
#[Option]
|
||||
bool $single = false,
|
||||
): int {
|
||||
clear();
|
||||
|
||||
info("ANAGRAM FINDER");
|
||||
|
||||
$letters = $letters ??= text("Enter the letters", required: true);
|
||||
|
||||
info("Results for your letters: " . $letters);
|
||||
|
||||
$matches = spin(
|
||||
callback: fn() => 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Lvl0\Countdown\TUI\Commands;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Lvl0\Countdown\Numbers\Dtos\NumbersBag;
|
||||
use Lvl0\Countdown\Numbers\Dtos\Step;
|
||||
use Lvl0\Countdown\Numbers\Services\NumbersService;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function Laravel\Prompts\callout;
|
||||
use function Laravel\Prompts\clear;
|
||||
use function Laravel\Prompts\error;
|
||||
use function Laravel\Prompts\intro;
|
||||
use function Laravel\Prompts\spin;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
#[AsCommand(name: 'countdown:numbers')]
|
||||
class FindNumbersCommand extends Command
|
||||
{
|
||||
public function __invoke(): int
|
||||
{
|
||||
clear();
|
||||
|
||||
intro("NUMBERS FINDER");
|
||||
|
||||
$numbersInput = text("Enter the numbers", required: true);
|
||||
|
||||
$numbers = new NumbersBag(
|
||||
new Collection(preg_split('/\s+/', trim($numbersInput), -1, PREG_SPLIT_NO_EMPTY))
|
||||
->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;
|
||||
}
|
||||
}
|
||||
93
src/Commands/LettersCommand.php
Normal file
93
src/Commands/LettersCommand.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace Lvl0\Countdown\TUI\Commands;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Lvl0\Countdown\Letters\Matcher;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function Laravel\Prompts\callout;
|
||||
use function Laravel\Prompts\form;
|
||||
use function Laravel\Prompts\note;
|
||||
use function Laravel\Prompts\spin;
|
||||
use function Laravel\Prompts\table;
|
||||
use function Laravel\Prompts\title;
|
||||
|
||||
#[AsCommand(name: 'countdown:letters')]
|
||||
class LettersCommand extends Command
|
||||
{
|
||||
private const TOP = 10;
|
||||
|
||||
public function __invoke(): int
|
||||
{
|
||||
title("Letters Round");
|
||||
|
||||
$responses = form()
|
||||
->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)));
|
||||
}
|
||||
}
|
||||
81
src/Commands/NumbersCommand.php
Normal file
81
src/Commands/NumbersCommand.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Lvl0\Countdown\TUI\Commands;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Lvl0\Countdown\Numbers\Dtos\NumbersBag;
|
||||
use Lvl0\Countdown\Numbers\Dtos\Step;
|
||||
use Lvl0\Countdown\Numbers\Services\NumbersService;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function Laravel\Prompts\callout;
|
||||
use function Laravel\Prompts\form;
|
||||
use function Laravel\Prompts\note;
|
||||
use function Laravel\Prompts\number;
|
||||
use function Laravel\Prompts\pause;
|
||||
use function Laravel\Prompts\spin;
|
||||
use function Laravel\Prompts\title;
|
||||
|
||||
#[AsCommand(name: 'countdown:numbers')]
|
||||
class NumbersCommand extends Command
|
||||
{
|
||||
public function __invoke(): int
|
||||
{
|
||||
title('Numbers Round');
|
||||
|
||||
$responses = form()
|
||||
->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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue