aboutsummaryrefslogtreecommitdiff
path: root/challenge-336/jo-37/perl/ch-2.pl
blob: 136d2bad9344339ebaa99df1c801635eefa27f9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/perl

use v5.26;
use Test2::V0 -no_srand;
use Test2::Tools::Subtest 'subtest_streamed';
use Getopt::Long;
use experimental 'signatures';


### Options and Arguments

my ($tests, $examples, $verbose);
GetOptions(
    'examples!' => \$examples,
    'tests!'    => \$tests,
    'verbose!'  => \$verbose,
) or usage();

run_tests($examples, $tests);	# tests do not return

usage() unless @ARGV;

sub usage {
    die <<~EOS;
    $0 - final score

    usage: $0 [-examples] [-tests] [-- S...]

    -examples
        run the examples from the challenge
     
    -tests
        run some tests

    S...
        list of scores

    EOS
}


### Input and Output

say final_score(@ARGV);


### Implementation
#
# For details see:
# https://github.sommrey.de/the-bears-den/2025/08/29/ch-336.html#task-1

package Score;

sub new ($class) {
    bless [], $class;
}

sub total ($self) {
    List::Util::sum0 @$self;
}

sub apply ($self, @scores) {
    for (@scores) {
        push(@$self, $_), next if /^[-+]?\d+$/;
        pop(@$self), next if /^C$/;
        push(@$self, 2 * ($self->[-1] // 0)), next if /^D$/;
        push(@$self, ($self->[-2] // 0) + ($self->[-1] // 0)), next if /^\+$/;
        die "score invalid: $_";
    }
    $self;
}


package main;

sub final_score {
    Score->new->apply(@_)->total;
}


### Examples and Tests

sub run_tests ($examples, $tests) {
    return unless $examples || $tests;

    state sub run_example ($args, $expected, $name) {
        my $result = final_score(@$args);
        is $result, $expected,
            "$name: (@$args) -> $expected";
    }

    plan 2;

    $examples ? subtest_streamed(examples => sub {
        my @examples = (
            [["5","2","C","D","+"], 30, 'example 1'],
            [["5","-2","4","C","D","9","+","+"], 27, 'example 2'],
            [["7","D","D","C","+","3"], 45, 'example 3'],
            [["-5","-10","+","D","C","+"], -55, 'example 4'],
            [["3","6","+","D","C","8","+","D","-2","C","+"], 128, 'example 5'],
        );
        plan scalar @examples;
        for (@examples) {
            run_example @$_;
        }
    }) : pass 'skip examples';

    $tests ? subtest_streamed(tests => sub {
        plan 9;
        
        like dies {Score->new->apply('X')}, qr/score invalid/,
            'score invalid';
        is(Score->new->total, 0, 'empty stack');
        is(Score->new->apply('+')->total, 0, 'zero summands');
        is(Score->new->apply('1')->apply('+')->total, 2, 'one summand');
        is(Score->new->apply('D')->total, 0, 'double nothing');
        is(Score->new->apply('C')->total, 0, 'cancel on empty stack');
        {
            my $x = Score->new;
            $x->apply(3);
            $x->apply(2);
            $x->apply('+');
            is $x->total, 10, 'single scores';
        }
        is(Score->new->apply(3)->apply(2)->apply('+')->total, 10, 'chained');
        is(Score->new->apply(qw(3 2 +))->total, 10, 'multiple scores');
    }) : pass 'skip tests';

    exit;
}