3

To preface, I am incredibly new to COBOL, and have only been writing it for about a week.

I wrote a program that plays a Rock-Paper-Scissors (RPS) game and the computer randomly picks an option each turn.

Originally, I had the section that generates a random value in the same program, but I wanted to make a module out of it, since I didn't want to have to write that random number generation for each program I write.

The way I found on the internet to do this was to use CALL on the module I compiled, and feed it the necessary variables. However, while randomly generating a number, it also changed two variables that weren't related to the CALL: USER-WIN, COMP-WIN.

Here is the code for the RPS program:

IDENTIFICATION DIVISION.
PROGRAM-ID. ADVENT-RPS.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 CHOICE.
    03 USER-CHOICE PIC A(1).
    03 COMP-CHOICE PIC A(1).
01 WIN.
    03 COMP-WIN PIC 9 VALUE 0.
    03 USER-WIN PIC 9 VALUE 0.
01 RAND.
    03 UPPER-BOUND PIC 9 VALUE 3.
    03 RAND-VAL PIC 9.
PROCEDURE DIVISION.
    DISPLAY "Choose R, P, or S"
    PERFORM UNTIL 1 = 0
*>      ACCEPT USER INPUT FROM TERMINAL AND CHECK FOR CORRECTNESS
        PERFORM USER-PIC
*>      GENERATES RANDOM COMPUTER CHOICE  
        PERFORM COMP-PICK
*>      CHECKS WHO WON THE ROUND AND IF SOMEONE WON THE GAME 
        PERFORM CHECK-WIN
    END-PERFORM.
    STOP RUN.
USER-PIC.
    ACCEPT USER-CHOICE FROM CONSOLE.
    IF (USER-CHOICE NOT = "R") AND
       (USER-CHOICE NOT = "P") AND
       (USER-CHOICE NOT = "S")
       DISPLAY "Please choose a valid input (R, P, S)."
       PERFORM USER-PIC
    END-IF.
COMP-PICK.
*>  CAUSING USER-WIN AND COMP-WIN TO CHANGE UNCONTROLLABLY 
    CALL "RAND" USING UPPER-BOUND, RAND-VAL.
    IF RAND-VAL = 0
       MOVE "R" TO COMP-CHOICE
    ELSE
       IF RAND-VAL = 1
          MOVE "P" TO COMP-CHOICE
       ELSE
          MOVE "S" TO COMP-CHOICE
       END-IF
    END-IF.
    DISPLAY "Computer chose: " COMP-CHOICE.
CHECK-WIN.
    IF USER-CHOICE = COMP-CHOICE
        DISPLAY "It's a tie!"
        EXIT PARAGRAPH
    ELSE
        IF (USER-CHOICE = "R" AND COMP-CHOICE = "P") OR
           (USER-CHOICE = "P" AND COMP-CHOICE = "S") OR
           (USER-CHOICE = "S" AND COMP-CHOICE = "R")
            DISPLAY "Computer beat you this round!"
            COMPUTE COMP-WIN = COMP-WIN + 1
        ELSE
            DISPLAY "You beat the computer this round!"
            COMPUTE USER-WIN = USER-WIN + 1
        END-IF
    END-IF.

    IF USER-WIN = 2
        DISPLAY "You won!"
        STOP RUN.
    IF COMP-WIN = 2
        DISPLAY "Computer won!"
        STOP RUN.
END PROGRAM ADVENT-RPS.

This is the random number generation program:

IDENTIFICATION DIVISION.
PROGRAM-ID. RAND.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-SEED-STRING PIC X(16).
01 WS-SEED-INT PIC 9(16).
LINKAGE SECTION.
01 UPPER-BOUND PIC 9(38).
01 RAND-VAL PIC 9(38).
PROCEDURE DIVISION USING UPPER-BOUND, RAND-VAL.
    MOVE FUNCTION CURRENT-DATE TO WS-SEED-STRING.
    MOVE FUNCTION NUMVAL(WS-SEED-STRING) TO WS-SEED-INT.
    COMPUTE RAND-VAL = FUNCTION RANDOM(WS-SEED-INT) * UPPER-BOUND.
EXIT PROGRAM.

I am using the GnuCOBOL compiler and used cobc -m -free RAND.COBOL to compile the random number generator and cobc -x -free RPS.COBOL to compile the RPS program. I am on Linux Mint Xfce if that is also important.

I tried putting variables on different levels, thinking that would change something, and it didn't. I also added the random number generator code back in to the RPS code, and it work perfectly fine then.

I also tried changing the size of UPPER-BOUND and RAND-VAL in the RAND program, and I found that if they are declared as size 9(15) and lower they work perfectly fine.

I think this has something to do with the RAND program overwriting memory that is used for COMP-WIN and USER-WIN, however I don't know if that is the case, and would like to know why this occurs and how to prevent similar thing in the future.

Additionally, I don't understand why changing the sizes of UPPER-BOUND and RAND-VAL in RAND fixes the issue. Does it have something to do with the size of WS-SEED-INT and what the RANDOM function returns?

Ken White
  • 123,280
  • 14
  • 225
  • 444
Ethan Krug
  • 73
  • 6
  • 4
    You are using rather different declarations for `UPPER-BOUND` and `RAND-VAL` in the two files: a single digit in the main program, but 38 digits in the module. I would guess that when the module assigns a new value to `RAND-VAL`, it's overwriting 37 subsequent digits' worth of storage. – jasonharper Jun 03 '23 at 02:14
  • 1
    That comment should be an answer instead. Also : you likely want to use PIC 9 ANY NUMERIC in the LINKAGE, so whatever size and type is passed will just work. – Simon Sobisch Jun 03 '23 at 06:46
  • 1
    Side note: This problem illustrates why it is common to define the structure of linkage sections in separate copybooks, which both, calling and called program, then include to make sure the structures are identical. – not2savvy Jun 03 '23 at 08:52

1 Answers1

3

calling and called programs must use the same communication area length and offsets for fields. You don't need to declare the parameters in the linkage section as group level, as shown bellow, but is a good practice to avoid mistakes and better understanding. As mentioned at comments, it's a good practice using copybooks too, for the same reason (but in that case, do not initialize the value in the copybook; just move the desired values to input fields at calling program).

If you want reuse the random module, I suggest you using a pic definition big enough for your future uses. In my test, I've used 5 bytes for both upper-bound and rand-val (keeping the max digits equal 18 for numeric fields it's a good practice too). Both fields (UPPER-BOUND and RAND-VAL) must be the same length because the RANDOM intrinsic function returns a value between 0 and 1, when multiplied by upper-bound will produce a value between 0 and upper-bound.

For example:


RPS.CBL
...
01 RAND-COMM-AREA.
   03 RAND-INPUT.
      05 UPPER-BOUND   PIC 9(5).
   03 RAND-OUTPUT.
      05 RAND-VAL      PIC 9(5).
...
  MOVE 3   TO UPPER-BOUND.
  CALL "RAND" USING RAND-COMM-AREA.


RAND.CBL
...
LINKAGE SECTION 
01 RAND-COMM-AREA.
   03 RAND-INPUT.
      05 UPPER-BOUND   PIC 9(5).
   03 RAND-OUTPUT.
      05 RAND-VAL      PIC 9(5).
PROCEDURE DIVISION USING RAND-COMM-AREA.

The question about WS-SEED-INT is related with the seed you are using to feed the random generator. You are getting the timestamp with the function CURRENT DATE and converting that string to a numeric value, with the same length. It works fine, but use a huge number.

The code bellow will have the same effect, if you move just the last 4 digits that represent the seconds and hundredths to the feed.


 01 WS-SEED-STRING PIC X(16).
 01 WS-SEED-INT PIC 9(4).
 ...
*> RETURNS YYYYMMDDhhmmsscc
     MOVE FUNCTION CURRENT-DATE TO WS-SEED-STRING.
*> MOVE just seconds and hundredths TO seed
     MOVE FUNCTION NUMVAL(WS-SEED-STRING(13:4)) TO WS-SEED-INT.
 ...

I hope this help you. Have a lot of fun.

Rui Vieira
  • 125
  • 1
  • 4
  • I usually create a copybook to variables being communicated and have the calling program include that in Working Storage and the called program include it in Linkage Section. By using a copybook then you are sure the definitions match. – Scott Nelson Jun 05 '23 at 12:56