Matrix digital rain

From Rosetta Code
Revision as of 18:27, 20 December 2018 by PureFox (talk | contribs) (Added Go)
Matrix digital rain is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Implement the Matrix Digital Rain visual effect from the movie "The Matrix" as described in Wikipedia.

Provided is a reference implementation in Common Lisp to be run in a terminal.

Common Lisp

Works with: SBCL

Runs in the terminal (using the Ncurses C library and the croatoan Lisp wrapper).

<lang lisp> (defun matrix-digital-rain ()

 (with-screen (scr :input-echoing nil :input-blocking nil :cursor-visibility nil)
   (let* ((width (.width scr))
          (height (.height scr))
          ;; start at a random height in each column.
          (positions (loop repeat width collect (random height)))
          ;; run each column at a random speed.
          (speeds (loop repeat width collect (random 4))))
     ;; hit the q key to exit the main loop.
     (add-event-handler (scr #\q) 'exit-event-loop)
     (add-event-handler (scr nil)
       (lambda (win event)
         ;; generate a random ascii char
         (flet ((randch () (+ 64 (random 58))))
           (loop for col from 0 to (1- width) do
             (loop repeat (nth col speeds) do
               ;; position of the first point in the current column
               (let ((pos (nth col positions)))
                 (setf (.attributes win) '(:bold))
                 (setf (.color-pair win) '(:white :black))
                 (add win (randch) :y (mod pos height) :x col)
                 (setf (.color-pair win) '(:green :black))
                 (add win (randch) :y (mod (- pos 1) height) :x col)
                 (add win (randch) :y (mod (- pos 2) height) :x col)
                 (setf (.attributes win) '())
                 (add win (randch) :y (mod (- pos 3) height) :x col)
                 ;; overwrite the last char half the height from the first char.
                 (add win #\space  :y (mod (- pos (floor height 2)) height) :x col)
                 (refresh win)
                 ;; advance the current column
                 (setf (nth col positions) (mod (+ pos 1) height))))))))
       (setf (.frame-rate scr) 20)
       (run-event-loop scr))))

</lang>

Sample output:

https://i.imgur.com/17b36O3.png

Go

Library: goncurses

This is a translation of the C code here which uses the ncurses library.

Rather than pressing Ctrl+C to stop the program, I've added code so that it stops automatically after 1 minute and restores the terminal to its original state. <lang go>package main

import (

   gc "github.com/rthornton128/goncurses"
   "log"
   "math/rand"
   "time"

)

// Time between row updates in microseconds. // Controls the speed of the digital rain effect. const rowDelay = 40000

func main() {

   start := time.Now()
   rand.Seed(time.Now().UnixNano())
   // Characters to randomly appear in the rain sequence.
   chars := []byte("0123456789")
   totalChars := len(chars)
   // Set up ncurses screen and colors.
   stdscr, err := gc.Init()
   if err != nil {
       log.Fatal("init", err)
   }
   defer gc.End()
   gc.Echo(false)
   gc.Cursor(0)
   if !gc.HasColors() {
       log.Fatal("Program requires a colour capable terminal")
   }
   if err := gc.StartColor(); err != nil {
       log.Fatal(err)
   }
   if err := gc.InitPair(1, gc.C_GREEN, gc.C_BLACK); err != nil {
       log.Fatal("InitPair failed: ", err)
   }
   stdscr.ColorOn(1)
   maxY, maxX := stdscr.MaxYX()
   /* Create slices of columns based on screen width. */
   // Slice containing the current row of each column.
   columnsRow := make([]int, maxX)
   // Slice containing the active status of each column.
   // A column draws characters on a row when active.
   columnsActive := make([]int, maxX)
   // Set top row as current row for all columns.
   for i := 0; i < maxX; i++ {
       columnsRow[i] = -1
       columnsActive[i] = 0
   }
   for {
       for i := 0; i < maxX; i++ {
           if columnsRow[i] == -1 {
               // If a column is at the top row, pick a
               // random starting row and active status.
               columnsRow[i] = rand.Intn(maxY + 1)
               columnsActive[i] = rand.Intn(2)
           }
       }
       // Loop through columns and draw characters on rows.
       for i := 0; i < maxX; i++ {
           if columnsActive[i] == 1 {
               // Draw a random character at this column's current row.
               charIndex := rand.Intn(totalChars)
               stdscr.MovePrintf(columnsRow[i], i, "%c", chars[charIndex])
           } else {
               // Draw an empty character if the column is inactive.
               stdscr.MovePrintf(columnsRow[i], i, "%c", ' ')
           }
           columnsRow[i]++
           // When a column reaches the bottom row, reset to top.
           if columnsRow[i] >= maxY {
               columnsRow[i] = -1
           }
           // Randomly alternate the column's active status.
           if rand.Intn(1001) == 0 {
               if columnsActive[i] == 0 {
                   columnsActive[i] = 1
               } else {
                   columnsActive[i] = 0
               }
           }
       }
       time.Sleep(rowDelay * time.Microsecond)
       stdscr.Refresh()
       elapsed := time.Since(start)
       // Stop after 1 minute.
       if elapsed.Minutes() >= 1 {
           break
       }
   }

}</lang>

Perl 6

Works with: Rakudo version 2018.11

Kind-of cheap and cheesy, but what the heck... Probably will only work in a POSIX compatible terminal. Runs until you hit ^C to exit.

The "lightning" effect is actually a bug, but I liked it so I kept it.

<lang perl6>signal(SIGINT).tap: { print "\e[H\e[J\e[?25h"; exit(0) }

my @codes = flat 'Α' .. 'Π', 'Ѐ' .. 'ѵ', 'Ҋ' .. 'ԯ', 'Ϣ' .. 'ϯ', 'ヲ'.. 'ン',

                'Ⲁ' .. '⳩', '∀' .. '∗', '℀' .. '℺', '⨀' .. '⫿';

my ($rows,$cols) = qx/stty size/.words;

my @c = flat "\e[38;2;255;255;255m", (255,245 … 30).map({"\e[38;2;0;$_;0m"}),

             "\e[38;2;0;25;0m" xx 75;

my $sz = +@c; my (@o, @s, @a); print "\e[?25l\e[48;5;232m"; init($rows, $cols);

my $delay; my ($r, $c);

loop {

    if ++$delay %% 20 {
        ($r, $c) = qx/stty size/.words;
        init($r, $c) if $r != $rows or $c != $cols;
        $delay = 0
    }
    print "\e[1;1H";
    print join , (^@s).map: {
        @a[$_] = (@a[$_] + 1) % $sz;
        flat @c[@a[$_]], @s[$_]
    }
    @s[(^@s).pick] = @codes.roll for ^30;

} sub init ($r, $c) {

   @s = @codes.roll($r * $c);
   ($rows, $cols) = $r, $c;
   my @o = (^@c).pick xx $cols;
   for ^$rows -> $row {
       @a[$row * $cols ..^ $row * $cols + $cols] = @o;
       @o = (^@o).map: {(@o[$_] - ($_ % 3)) % $sz};
   }

}</lang>

Sample output:

See matrix-digital-rain-perl6.png (offsite png image)

zkl

Translation of: Perl6

<lang zkl>var [const] codes=Walker.chain( // a bunch of UTF non ascii chars

        [0x0391..0x03a0], [0x03a3..0x0475], [0x0400..0x0475],
        [0x048a..0x052f], [0x03e2..0x03ef], [0x2c80..0x2ce9],
        [0x2200..0x2217], [0x2100..0x213a], [0x2a00..0x2aff])

.apply(fcn(utf){ utf.toString(-8) }), // jeez this is lame

   codeSz=codes.len(),	// 970
   c=L("\e[38;2;255;255;255m",[255..30,-15].apply("\e[38;2;0;%d;0m".fmt),
       (250).pump(List,T(Void,"\e[38;2;0;25;0m"))).flatten(),
   csz=c.len(); // 267, c is ANSI escape code fg colors: 38;2;<r;g;b>m

// query the ANSI terminal rows,cols := System.popen("stty size","r").readln().split().apply("toInt");

o,s,fg := buildScreen(rows,cols); ssz:=s.len();

print("\e[?25l\e[48;5;232m"); // hide the cursor, set background color to dark while(1){ // ignore screen resizes

  print("\e[1;1H");	       // move cursor to 1,1
  foreach n in (ssz){	       // print a screen full
     print( c[fg[n]], s[n] ); // forground color, character
     fg[n]=(fg[n] + 1)%csz;   // fade to black
  }
  do(100){ s[(0).random(ssz)]=codes[(0).random(codeSz)] }  // some new chars
  Atomic.sleep(0.1);	       // frame rate for my system, up to 200x41 terminal

}

fcn buildScreen(rows,cols){ // build a row major array as list

  // s --> screen full of characters
  s:=(rows*cols).pump(List(), fcn{ codes[(0).random(codeSz)]});
  // array fb-->( fg color, fg ..) where fg is an ANSI term 48;5;<n>m color
  fg:=List.createLong(s.len(),0);
  o:=csz.pump(List()).shuffle()[0,cols];  // cols random #s
  foreach row in (rows){		   // set fg indices
     foreach col in (cols){ fg[row*cols + col] = o[col] }
     o=o.apply(fcn(n){ n-=1; if(n<0) n=csz-1; n%csz });  // fade out
  }
  return(o,s,fg);

}</lang> Offsite Image: Matrix rain dance