App-Greple-wordle

 view release on metacpan or  search on metacpan

lib/App/Greple/wordle.pm  view on Meta::CPAN

    eval "use $pkg";
    if ($@) {
	die "$app->{data}: no such data set\n" if $@ =~ /Can't locate/;
	die $@;
    } else {
	no strict 'refs';
	@word_all = @{"$pkg\::WORDS"};
	@word_hidden = @{"$pkg\::HIDDEN"};
    }
    $word_all{$_} = 1 for @word_all;
    for ($app->index) {
	$_   = int rand @word_hidden if $app->random;
	$_ //= _days;
	$_  += _days if /^[-+]/;
    }
    if (my $answer = $app->answer) {
	$app->index = undef;
	$word_all{$answer} or die "$answer: wrong word\n";
    } else {
	if ($app->series > 0) {
	    srand($app->series);
	    @word_hidden = shuffle @word_hidden;
	}
	if ($app->index > $#word_hidden) {
	    warn sprintf "no data for %d, so pick a random answer from past data\n", $app->index;
	    srand($app->series);
	    $app->index = int rand @word_hidden;
	}
	$app->answer = $word_hidden[ $app->index ];
    }
}

sub patterns {
    my $app = shift;
    my $answer = $app->answer;
    my @re = map
	    { sprintf "(?<=^.{%d})%s", $_, substr($answer, $_, 1) }
	    0 .. length($answer) - 1;
    my $green  = join '|', @re;
    my $yellow = "[$answer]";
    my $black  = "(?=[a-z])[^$answer]";
    map { ( '--re' => $_ ) } $green, $yellow, $black;
}

sub title {
    my $app = shift;
    my $label = 'Greple::wordle';
    return $label if not defined $app->index;
    sprintf('%s %s%s',
	    $label,
	    $app->series == 0 ? '' : sprintf("%d-", $app->series),
	    $app->index);
}

######################################################################

my $app = __PACKAGE__->new or die;
my $game;
my $interactive;

sub prompt {
    sprintf '%d: ', $game->attempt + 1;
}

sub initialize {
    my($mod, $argv) = @_;
    $app->parseopt($argv)->setup;
    $game = App::Greple::wordle::game->new(answer => $app->answer);
    push @$argv, $app->patterns;
    if ($interactive = -t STDIN) {
	push @$argv, '--interactive', ('/dev/stdin') x $app->total;
	select->autoflush;
	say $app->title;
	print prompt();
    }
}

sub respond {
    local $_ = $_;
    my $chomped = chomp;
    print ansi_code("{CHA}{CUU}") if $chomped;
    print ansi_code(sprintf("{CHA(%d)}",
			    max(11, vwidth($_) + length(prompt()) + 2)));
    print s/(?<=.)\z/\n/r for @_;
}

sub show_answer {
    say colorize('#6aaa64', uc $game->answer);
}

sub show_result {
    printf "\n%s %d/%d\n\n", $app->title, $game->attempt, $app->trial;
    say $game->result;
}

sub check {
    my $word = lc s/\n//r;
    if (not $word_all{$word}) {
	command($word) or respond $app->wrong;
	$_ = '';
    } else {
	$game->try($word);
    }
}

sub command {
    my $word = shift;
    my @cmd = split ' ', $word or return;
    my @word = @word_all;
    state @remember;
    $cmd[0] =~ /^u(niq)?$/ and unshift @cmd, 'hint';

    while (@cmd) {
	local $_ = shift @cmd;
	try {
	    if    ($_ eq '|')   {}
	    elsif (/^d$/)       {
		$app->debug ^= 1;
		printf 'Debug %s', $app->debug ? 'on' : 'off';
		return;
	    }
	    elsif (/^\?$/)      { help(); return }
	    elsif (/^!!$/)      { @word = @remember }
	    elsif (/^h(int)?$/) { @word = choose($game->hint, @word) }
	    elsif (/^u(niq)?$/) { @word = grep { !/(.).*\1/i } @word }
	    elsif (/^=(.+)/)    { @word = choose(includes($1), @word) }
	    elsif (/^!(.+)/)    { @word = choose("^(?!.*[$1])", @word) }
	    elsif (/\W/)        { @word = choose($_, @word); }
	    else  { return }
	    1;
	} or do {
	    warn "ERROR: $_" if $app->debug;
	    return /^[a-z]+$/i ? 0 : 1;
	};
    }
    if (@word == 0) {
	warn "No match\n";
	return 1;
    }
    @remember = @word;
    do {
	local $, = ' ';
	say $game->hint_color(@word);
    };
    1;
}

sub help {
    my $message = << "    END";
#   d      debug
    ?      help
    h      show hint
    u      uniq
    !!     repeat last result
    =<str> include characters
    !<str> exclude characters
    END
    print $message =~ s/^\s*(#.*)\n//gr;
}

sub includes {
    '^' . join '', map { "(?=.*$_)" } $_[0] =~ /./g;
}

sub choose {
    my $p = shift;
    $p =~ s/([A-Z])/[^$1]/g;
    warn "> $p\n" if $app->debug;
    grep /$p/, @_;
}

sub inspect {
    if ($game->solved) {
	respond $app->correct x ($app->trial - $game->attempt + 1);
	show_result if $app->result;
	exit 0;
    }
    if (length) {
	if ($game->attempt >= $app->trial) {
	    show_answer;
	    exit 1;
	}
	$app->keymap and respond $game->keymap;
    }
    print prompt();
}

1;

__DATA__

mode function

define GREEN  #6aaa64
define YELLOW #c9b458
define BLACK  #787c7e

option default \
	-i --need 1 --no-filename \
	--cm 555/GREEN  \
	--cm 555/YELLOW \
	--cm 555/BLACK

# --interactive is set in initialize() when stdin is a tty

option --interactive \
       --if 'head -1' \
       --begin    __PACKAGE__::check   \
       --end      __PACKAGE__::inspect \
       --epilogue __PACKAGE__::show_answer



( run in 1.697 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )