01 - Programming (EN)01a - RPG (EN)

RPG-5250 tribute to John Conway – Game of Life

Another effect of this damned virus: two days ago, April 11, 2020, John Horton Conway passed away: another victim of Covid-19, mathematician and author of mathematical games, professor at Princeton University (New Jersey), known to most as the inventor of the game “The Game of Life” … even if he himself did not like to be recognized as “that of The Game of Life” ( https://www.youtube.com/watch?v=E8kUJL04ELA);

Covid-19, this little bastard, is taking away an entire generation who has built, for better or for worse, the current world in which we are living: whether he is a famous mathematician with his games for “nerd” and “eggheads”, or a Doctor who has returned from retirement to go to the ward again against Covid-19 or our dear grandmother / grandfather / father / mother is always a loss for us and for all humanity and those who say that only the elderly and sick leave are making a big mistake: a generation of people, men, women, who have been professors, doctors, workers, and housewives but, above all, fathers, mothers, husbands, wives, lovers and friends of a time gone by go away.

For this reason I wanted to pay tribute to John Conway and his famous “The Game of Life”, doing it in RPG (The Game of Life – RPG and 5250 Version – GAMEOFLF.SQLRPGLE), with the limits of 5250 and with questionable techniques, which certainly can also be improved in RPG,

I practically spent the whole day of Easter, between my garden and my computer, and I put together this small program which simulates the game “The Game Of Life” by reading the initial configuration (initial pattern) from an IFS text file written with the RLE Run Length Encoded rules (see below).

The SQL-RPGLE source, DSPF, Sansoterra’s UDTFs, and the RLE examples are available in this Github Gist: The Game Of Life – SQLRPGLE and 5250 John Conway’s Tribute

The result I put in an animated GIF and in a video that you find towards the end of the post: it has its limits, in addition to the 27 x 132 characters of 5250 are added the limits of an RPG programmer on Easter day after the classic lunch with an excellent accompanying Prosecco. Yes, it could have been done better, it’s true … but I could also stay on the couch with Netflix and Spotify!


Let’s see it in detail … beyond the “graphics” and 5250 problems, the project also saw interesting “challenges”: reading a text file from IFS, splitting text strings with SQL, interpreting a new language (RLE) … all with RPG and SQL: tips and improvements are still welcome … I wait for you below in the comments!

(1) “The Game Of Life” Game Rules

As we read from Wikipedia, the game “The Game Of Life” is based on a few well-defined rules:

  • It is a “Cellular Automaton”
  • It behaves a bit like cells in life … probably our current nightmare Corona Virus Covid-19 also works quite similarly!
  • We can think of it as an “MTU Universal Turing Machine”
  • It’s a game without players … we can’t even think of spending time once we get back from the Covid-19 quarantine in our offices.

But let’s get to the rules … considering that the game rests on a grid-matrix of cells, called “World”, with a theoretically infinite dimension:

  • A cell has 8 neighboring cells, all around it
  • A cell can be found in two states: LIVE or DEAD (on or off)
  • The state of the grid evolves in discrete time intervals … all together … in one shot … moving on to the “next generation”
  • The state of the cell depends on the state in which it is and on the state of its 8 “neighboring” cells
  • 1) Any live cell with less than two adjacent live cells dies, as if by isolation effect;
  • 2) Any live cell with two or three adjacent live cells survives the next generation;
  • 3) Any live cell with more than three adjacent live cells dies, as an effect of overpopulation;
  • 4) Any dead cell with exactly three adjacent live cells becomes a live cell, as if by reproduction effect.

(1) How to represent a matrix in 5250 and RPG

Here is the first problem … a graphic matrix in RPG, with a Display File … 24×80 or 27×132 … it’s not a big matrix … but let’s be satisfied: I could do a single multi-line video field … that took up as much space as possible. Instead, I decided to make a matrix of 24 lines by 130 char. The choice, as we see in the code, is needed because I want to work on a larger matrix anyway … allowing the calculations to go “off screen” and therefore also to “go back” for some objects that “move”.

(2) In RPG I manage a matrix larger than 130 x 24 reaching 300 x 100

In the RPG code I then manage these video fields as an Array of horizontal lines … in order to manage a larger theoretical matrix, the array I am going to create will have a size of 300 x 100 (but you could do more if it were needed!): note the keyword POS (1) to overlay inside the DS!

        dcl-c maxx const (130);
        dcl-c maxy const (24);
        dcl-c m_maxx const (300);
        dcl-c m_maxy const (100);
        dcl-c d_maxx const (170); // 300-130
        dcl-c d_maxy const (88); // 100-22

        dcl-ds matrix;
          row001 char (maxx);
          row001h char (d_maxx);
          row002 char (maxx);
          row002h char (d_maxx);
          row003 char (maxx);
          row003h char (d_maxx);
          row004 char (maxx);
          row004h char (d_maxx);
          row005 char (maxx);
          row005h char (d_maxx);
          row006 char (maxx);
          row006h char (d_maxx);
          row007 char (maxx);
          row007h char (d_maxx);
          row008 char (maxx);
          row008h char (d_maxx);
          row009 char (maxx);
          row009h char (d_maxx);
          row010 char (maxx);
          row010h char (d_maxx);
          row011 char (maxx);
          row011h char (d_maxx);
          row012 char (maxx);
          row012h char (d_maxx);
          row013 char (maxx);
          row013h char (d_maxx);
          row014 char (maxx);
          row014h char (d_maxx);
          row015 char (maxx);
          row015h char (d_maxx);
          row016 char (maxx);
          row016h char (d_maxx);
          row017 char (maxx);
          row017h char (d_maxx);
          row018 char (maxx);
          row018h char (d_maxx);
          row019 char (maxx);
          row019h char (d_maxx);
          row020 char (maxx);
          row020h char (d_maxx);
          row021 char (maxx);
          row021h char (d_maxx);
          row022 char (maxx);
          row022h char (d_maxx);
          row023 char (maxx);
          row023h char (d_maxx);
          row024 char (maxx);
          row024h char (d_maxx);
          rows char (m_maxx) dim (m_maxy) pos (1);
        END-DS;                    

(3) Dead or Alive (on or off)

The alive or dead state, on or off, of the cells is, for simplicity, represented with a character … but we could do better with a “full block” or some other aesthetically more effective character

        dcl-s on char (1) inz ('O'); // Alive
        dcl-s off char (1) inz (''); // Dead  

(4) Evolution of generations (iterations)

The transition to the new generations takes place on a support matrix (MatrixNextGen) where each individual cell is evaluated according to the 4 rules of the game and then … in the program …. a “check_matrix ()” procedure passes the whole matrix, reads the state of each cell “row.pixel ()”, calculates the number of living cells around “neighbours_count ()” and applies the rules according to whether the same cell is alive “alivePixel_rules ()” or dead “deadPixel_rules ()”. The transition from MatrixNextGen to Matrix is made … to make the “click” of the new generation all in an instant.

        // -----------------------------------
        // check_matrix;
        // -----------------------------------
        dcl-proc check_matrix;
          dcl-pi check_matrix;
          END-PI;
          dcl-ds row qualified;
                 pixel char (1) dim (m_maxx);
          END-DS;
          dcl-s neighbors int (5);
          for y = 1 to maxy;
              row = rows (y);
              for x = 1 to m_maxx;
                  // if pixel is alive
                  if row.pixel (x) = on;
                     neighbors neighbours_count = (x: y: bounce);
                     row.pixel (x) = alivePixel_rules (neighbors);
                  else;
                  // if is death
                     neighbors neighbours_count = (x: y: 'N');
                     row.pixel (x) = deadPixel_rules (neighbors);
                  ENDIF;
              ENDFOR;
              // save row in the matrix
              matrixNextGen.rows (y) = row;
          ENDFOR;

          // Show Next Generation
          matrix = MatrixNextGen;

        END-PROC;


        // -----------------------------------
        // neighbors_count (x: y)
        // -----------------------------------
        dcl-proc neighbors_count;
        dcl-pi neighbors_count int (5);
            x int (5) const;
            y int (5) const;
            ibounce char (1) const;
        END-PI;
            dcl-s count int (5);
            dcl-ds row qualified;
                   pixel char (1) dim (m_maxx);
            END-DS;
            // Same row
            getRow row = (y: ibounce);
            count + = ifalive (row: x-1);
            count + = ifalive (row: x + 1);
            // upper row
            getRow row = (y-1: ibounce);
            count + = ifalive (row: x-1);
            count + = ifalive (row: x);
            count + = ifalive (row: x + 1);
            // lower row
            getRow row = (y + 1: ibounce);
            count + = ifalive (row: x-1);
            count + = ifalive (row: x);
            count + = ifalive (row: x + 1);

            return count;
        end-proc;


        // -----------------------------------
        // getrow (y)
        // -----------------------------------
        dcl-proc getrow;
        dcl-pi getrow char (m_maxx);
            y int (5) const;
            ibounce char (1) const;
        END-PI;
            if y> = 1 and y <= maxy;
               return rows (y);
            else;
               if ibounce = 'Y';
                  return rowon;
               else;
                  return rowoff;
               ENDIF;
            ENDIF;

        end-proc;

        // -----------------------------------
        // ifalive (x: y)
        // -----------------------------------
        dcl-proc ifalive;
        dcl-pi ifalive int (5);
            irow char (m_maxx) const;
            x int (5) const;
        END-PI;
            dcl-ds row qualified;
                   pixel char (1) dim (m_maxx);
            END-DS;
            row = IRow;
            if x> = 1 and x <= m_maxx;
               if row.pixel (x) = on;
                  return 1; // alive
               ENDIF;
            ENDIF;
            return 0; // death
        end-proc;
        // -----------------------------------
        // alivePixel_rules
        // -----------------------------------
        dcl-proc alivePixel_rules;
        dcl-pi alivePixel_rules char (1);
            neighbors int (5) const;
        END-PI;
            select;
               when neighbors <= 1; // die
                    return off;
               when neighbors <= 3; // rest alive
                    return on;
               when neighbors> 3; // die
                    return off;
            ENDSL;
        end-proc;

        // -----------------------------------
        // deathPixel_rules
        // -----------------------------------
        dcl-proc deadPixel_rules;
        dcl-pi deadPixel_rules char (1);
            neighbors int (5) const;
        END-PI;
            select;
               when neighbors = 3; // becomes alive
                    return on;
               other; // rest dead
                    return off;
            ENDSL;
        end-proc;


        // -----------------------------------
        // set_NPixel
        // -----------------------------------
        dcl-proc set_nPixel;
        dcl-pi set_nPixel;
            currentx int (5);
            currenty int (5);
            repetition int (5);
            on_off ind const;
        END-PI;
            dcl-s i int (5);
            if repetition = 0;
               repetition = 1;
            ENDIF;
            for i = 1 to repetition;
              if on_off;
                 setPixel_alive (CurrentX: currenty);
              else;
                 setPixel_dead (CurrentX: currenty);
              ENDIF;
              CurrentX + = 1;
            ENDFOR;
            // Reset repetition;
            repetition = 0;
        end-proc;
        // -----------------------------------
        // setPixel_alive
        // -----------------------------------
        dcl-proc setPixel_alive;
        dcl-pi setPixel_alive;
            x int (5) const;
            y int (5) const;
        END-PI;
            dcl-ds row qualified;
               pixel char (1) dim (m_maxx);
            END-DS;
            if x> = 1 and x <= m_maxx and
               y> = 1 and y <= m_maxy;
             row = rows (y);
             row.pixel (x) = on;
             rows (y) = row;
            endif;
        end-proc;
        // -----------------------------------
        // setPixel_dead
        // -----------------------------------
        dcl-proc setPixel_dead;
        dcl-pi setPixel_dead;
            x int (5) const;
            y int (5) const;
        END-PI;

            dcl-ds row qualified;
                   pixel char (1) dim (m_maxx);
            END-DS;
            if x> = 1 and x <= m_maxx and
               y> = 1 and y <= m_maxy;
             row = rows (y);
             row.pixel (x) = off;
             rows (y) = row;
            endif;
        end-proc;                         

(5) Initial pattern – Configuration – RLE Run Lenght Encoding representation

To test the game “The Game of Life”, thousands of initial patterns, oscillators, still-life, spaceships and infinite other shapes that you feed in the game behave in a different and challenging way. The problem is that all these patterns, live-cell configurations on the matrix, are available in an RLE – Run Lenght Encoding representation, practically a system of “compression” of an image that exploits the repetition of a “color” in the nearby “pixels” (a bit of an ancestor of today’s JPG and PNG compressions).

The RLE representation (with rules B3 / S23 for “The Game of Life”) is very simple: starting from the initial coordinates, a string will define the sequence of live (‘o’) or dead (‘b’) cells with a possible number of repetitions and indicating the jump to the next line with a dollar sign ‘$’: in the following example (example07.rle) there is a pattern I invented

#C Faq400 RLE Test         
x = 10, y = 10, rule = B3 / S23         
o3bo3b $ 2o3b2o $ o3bo3b!                                           
  • Initial coordinates 10, 10
  • 1 live cell (‘0’) – 3 dead cells (‘b’) -1 live – 3 death – line jump (‘$’)
  • 2 live – 3 death – 2 live – jump line
  • 1 alive – 3 death – 1 alive – 3 death – end

Ok … undoubtedly a challenge for Easter day … if I can make a program that reads a text file … split the strings using the dollar sign ($), parse and initialize my matrix I can treat myself to a nice slice of my wife’s “Tiramisu“.

(6) Read a text file in IFS from RPG / SQL

There are several ways to read a text file with RPG … using operating system procedures or fopen etc. seen several times in the tutorials of Scott Klement and others.

Since in addition to reading the text file I had to parse it … splitting the string a different lines … I preferred to use SQL directly … I first played with the CLOB_FILE SQL variables to load a table in QTEMP but I had a problem with the LINE-FEED of the rows … in reading from the CLOB-FILE I was created a single record with all the content of the text file …

          dcl-s InFile sqltype (clob_file);
          dcl-s Path varchar (300);     
....
          path = '/ home / faq400 / gameOfLife /' + pattern;
          Infile_Name =% trimr (Path);
          Infile_NL =% len (% trimr (Infile_Name));
          Infile_FO = SQFRD;       
 // Load RLE text file pattern in QTEMP.PATTERN table
          exec sql DROP TABLE QTEMP.PATTERN;
          exec sql CREATE TABLE QTEMP.PATTERN (PROW CHAR (300));
          exec sql INSERT INTO QTEMP.PATTERN VALUES (: InFile);  

Since I failed with the CLOB I went to recycle an old and excellent utility by Micheal Sansoterra, READIFSFR available on MCPress Online, to read a text file in SQL line by line.

- For a CR + LF line divided text
select * from table (faq400.ReadIFSFR ('/ home / faq400 / GameOfLife / gosper.rle', X'0D25 '));
- For a LF line divided text
select * from table (faq400.ReadIFSFR ('/ home / faq400 / GameOfLife / gosper.rle', X'25 '));

(7) SPLIT a string by dividing it by the dollar sign ($)

Once I have read the TXT I have to split the result using the ‘$’ separator for the change of line. Not having a 7.3 with its split SQL function (as Massimo Duca explained to us in this post on our blog “SQL: perfect cuts with SPLIT”), I still resort to the great Micheal Sansoterra with his UDTF Split.

With a JOIN the game is done:

 exec sql
             declare RLER cursor for
              select trim (value) as RLErow
               from table (faq400.ReadIFSFR (trim (: pathfile), X'25 '))
                   , table (faq400.split (linetext, '$')) s
                   where lower (linetext) not like 'x =%'
                     and lower (linetext) not like 'x =%'
                     and lower (linetext) not like '# n%'
                     and lower (linetext) not like '# c%'
                   order by line_no;

Result

In the end I managed … I can take a pattern from the huge Copy.sh collection, upload it to IFS and test it in my fantastic GAMEOFLF.SQLRPLGE program … these are satisfactions!

Here is a video with some working examples … how about?

Conclusion

Yet another proof that with our beloved IBM i and RPG we can do everything!

Of course, if we look at the time to do it and the graphic result, maybe it would be convenient to do a little search on Google and look for something ready in Python or Node.js … install some packages on our IBM i and run everything inside a browser … it would certainly have been more beautiful.

But it was meant to be a tribute to John Conway, done with RPG, SQL and 5250 that I don’t seem to have seen anywhere by searching on Google!

Goodbye John Conway, thanks for what you left us!

And in the end I was able to read a text file with SQL, split the strings, interpret an RLE pattern configuration script… I can finally jump on my wife’s “Tiramisu”… I deserve it!

--- Roberto De Pedrini Faq400.com
About author

Founder of Faq400 Srl, IBM Champion, creator of Faq400.com and blog.faq400.com web sites. RPG developer since I was wearing shorts, strong IBM i supporter, I have always tried to share my knowledge with others through forums, events and courses. Now, with my company Faq400 Srl, I help companies to make the most of this great platform IBM i.

Leave a Reply

%d bloggers like this: