Nimbly Fuscous with Raku

by Arne Sommer

Nimbly Fuscous with Raku

[120] Published 21. March 2021.

This is my response to the Perl Weekly Challenge #104.

Challenge #104.1: FUSC Sequence

Write a script to generate first 50 members of FUSC Sequence. Please refer to OEIS for more information.

The sequence defined as below:

fusc(0) = 0
fusc(1) = 1
for n > 1:
when n is even: fusc(n) = fusc(n / 2),
when n is odd: fusc(n) = fusc((n-1)/2) + fusc((n+1)/2)

This is yet another sequence that would have fitted right in in my Centenary Sequences with Raku article series.

We can set up this as a sequence with gather/take:

File: fusc-seq
#! /usr/bin/env raku

unit sub MAIN ($limit = 50);     # [1]

my $fusc := gather               # [2]
{
  take 0;                        # [3]
  take 1;                        # [4]
  my $index = 1;                 # [5]
  loop                           # [6]
  {
    $index++;                    # [7]
    take $fusc[$index / 2];      # [8]
    $index++;                    # [7]
    take $fusc[($index - 1)/2] + $fusc[($index +1)/2];  # [9]
  }
}

say $fusc[^$limit].join(", ");  # [10]

[1] Default to the first 50 values.

[2] A gather block to gather the following values.

[3] The first value, with take.

[4] The second value.

[5] The sequence relies on previous values, hence the need for the index.

[6] En eternal loop. Note that we used binding (:= in [2]), so the values are not computed until we need them.

[7] Update the index.

[8] This is the value, when the index is even.

[9] And this is the value, when the index is odd.

[10] Print the requested number of values.

See my Raku Gather, I Take article or docs.raku.org/syntax/gather take for more information about gather/take.

Running it:

$ ./fusc-seq 10
0, 1, 1, 2, 1, 3, 2, 3, 1, 4

$ ./fusc-seq 
0, 1, 1, 2, 1, 3, 2, 3, 1, 4, 3, 5, 2, 5, 3, 4, 1, 5, 4, 7, 3, 8, 5, 7, 2, \
7, 5, 8, 3, 7, 4, 5, 1, 6, 5, 9, 4, 11, 7, 10, 3, 11, 8, 13, 5, 12, 7, 9, 2, 9

Note that this program (as opposed to a recursive approach) only calculates each value once. The next time we need a prior value (in [8] or [9]), it looks it up in the the $fusc variable. We got built in caching, for free.

Challenge #104.2: NIM Game

Write a script to simulate the NIM Game.

It is played between 2 players. For the purpose of this task, let assume you play against the machine.

There are 3 simple rules to follow:

a) You have 12 tokens
b) Each player can pick 1, 2 or 3 tokens at a time
c) The player who picks the last token wins the game

The program is not very smart. It picks the number of tokens (1, 2 or 3) by random. Except when there is 3 or less tokens left, when it takes them all.

File: nim-game
#! /usr/bin/env raku

unit sub MAIN;

my $tokens = 12;                                               # [1]

loop                                                           # [2]
{
  say "Remaining tokens: $tokens";
  
  my $count = prompt "How many tokens do you want (1,2,3): ";  # [3]

  die unless $count == any(1,2,3);                             # [4]

  $tokens -= $count;                                           # [5]

  if $tokens <= 0                                              # [6]
  {
    say "You won!";
    last;                                                      # [6a]
  }

  say "Remaining tokens: $tokens";

  $count = $tokens <= 3 ?? $tokens !! (1,2,3).pick;            # [7]
  say "Machine picked $count tokens";
  $tokens -= $count;                                           # [5]

  if $tokens <= 0                                              # [8]
  {
    say "The Machine won!";
    last;                                                      # [8a]
  }
}

[1] The game starts with 12 tokens.

[2] An eternal loop. Note the two (explicit) exits with last, in [6a] and [8a].

[3] Use prompt to get user input, after printing the text (if any).

[4] Using an any junction to check that the input values is one of 1, 2 or 3. Terminate the program if not. (This is not very user friendly, but it can be considered a suitable punishment for typing in something illegal.)

[5] Update the number of tokens left.

[6] It is ok to specify more tokens than we have left. You win if you take the last one(s).

[7] The machine player takes all the remaining tokens, if there afre 3 or less of them. if not, it picks a random number between 1 and 3 (with pick).

[8] Has the machine player won?

See docs.raku.org/routine/prompt for more information about prompt.

Running it:

$ ./nim-game 
Remaining tokens: 12
How many tokens do you want (1,2,3): 3
Remaining tokens: 9
Machine picked 3 tokens
Remaining tokens: 6
How many tokens do you want (1,2,3): 2
Remaining tokens: 4
Machine picked 3 tokens
Remaining tokens: 1
How many tokens do you want (1,2,3): 1
You won!

$ ./nim-game 
Remaining tokens: 12
How many tokens do you want (1,2,3): 3
Remaining tokens: 9
Machine picked 1 tokens
Remaining tokens: 8
How many tokens do you want (1,2,3): 3
Remaining tokens: 5
Machine picked 2 tokens
Remaining tokens: 3
How many tokens do you want (1,2,3): 2
Remaining tokens: 1
Machine picked 1 tokens
The Machine won!

You win if you can arrange a situation where there are 4 tokens left when it is the other user's turn.

And that's it.