2

I am trying to convert some old qbasic (a old dos basic) code to python. I know python but not much qbasic (other than guessing the meaning of the syntax). This is the qbasic code

1020 DIM XS(499), A(504), V(99)
1560 GOSUB 2600                 'Get coefficients

2600 REM Get coefficients
2660 CODE$ = "A"
2680 M% = 3
2690    FOR I% = 1 TO M%        'Construct CODE$
2700        GOSUB 2800          'Shuffle random numbers
2710        CODE$ = CODE$ + CHR$(65 + INT(25 * RAN))
2720    NEXT I%
2730 FOR I% = 1 TO M%           'Convert CODE$ to coefficient values
2740    A(I%) = (ASC(MID$(CODE$, I% + 1, 1)) - 77) / 10
2750 NEXT I%
2760 RETURN
2800 REM Shuffle random numbers
2810 IF V(0) = 0 THEN FOR J% = 0 TO 99: V(J%) = RND: NEXT J%
2820 J% = INT(100 * RAN)
2830 RAN = V(J%)
2840 V(J%) = RND
2850 RETURN

It appears it is mapping ASCII codes to random numbers but it is not clear to me how as I am not familiar with the syntax J% and V(J%), etc (dont know what the % means)

user35202
  • 389
  • 1
  • 10
  • 1
    `%` just means the variable is an int. So `J%` is a variable `J` where `J` is a integer. `$` means string. – JacobIRR Dec 29 '18 at 05:07
  • ASC, MID$ and CHR$ must be some functions? – user35202 Dec 29 '18 at 05:12
  • 1
    CHR$ is the inverse function of ASC. So PRINT ASC("A") would give you 65 and CHR$(65) would give you "A". MID$ is a substring function. So if a$ = "Where is Paris?" PRINT MID$(a$, 10, 5) would give you the substring starting at position 10 and 5 chars in length i.e. "Paris". – JennyToy Dec 29 '18 at 05:26
  • 1
    Relevant [BASIC-Command](https://www.c64-wiki.com/wiki/Category:BASIC-Command) – stovfl Dec 29 '18 at 06:23
  • 1
    If you haven't figured it out yet, there is some inconsistent default behavior between the start index of a string in functions like `MID$` and the lower bound/index of arrays. Strings in QBASIC have indices starting at 1, so `b$ = "Bar" : PRINT MID$(b$, 1, 2)` will print `Ba` (using 0 instead of 1 is an error). Arrays, however, have indices starting at 0 by default, so you work with them as `V(0)` to `V(99)` inclusive (i.e. there are 100 values in `V()`). This behavior can be changed using `OPTION BASE 1`, but the altered behavior means `DIM V(99)` would result in 99 items, not 100. –  Jan 01 '19 at 20:02

2 Answers2

2

As mentioned, in QBasic the %, $ and # denote the datatype of the variable. In Python you don't have to specify the datatypes of variables, but in QBasic it's like this:

QBasic
I%   ' % = integer variable
str$ ' $ = string variable
f#   ' # = floating point variable

And if you want to convert QBasic functions, see these questions: Python equivalent of mid and Python get ASCII value

QBasic              Python
str$ = "water"      str = "water"
ASC("A")            ord("A")     // result 65
MID$(str$, 3, 2)    str[3,(3+2)] // result "te"
CHR$(65)            chr(65)      // result "A"
BdR
  • 2,770
  • 2
  • 17
  • 36
  • The substring command produces a `TypeError: string indices must be integers, not tuple`, even corrected it is the wrong substring, `te` is produced by `str[2:2+2]`. – Lutz Lehmann Jan 19 '19 at 20:21
1

This looks more like GW-BASIC than QBasic, although it's also valid QBasic. GW-BASIC variable names are all upper case, while QBasic isn't case sensitive but allowed lower case characters in names.

The postfix type characters where already mentioned in another answer and a comment to the question: % denotes integer variables and $ is for strings. What wasn't mentioned is that A, A%, A$ and the arrays A(), A%(), A$() are six different variables. So when translating code very closely to the original, one better encodes the type postfix into the name on the Python side of things. Unless you are really sure there are no names just differing by type. Given that most GW-BASIC programs used names of length one or two — to stay backwards compatible to older Microsoft BASIC dialects — ”collisions” in bigger programs where almost a given.

Python has no GOTO/RETURN, so one either has to inline those subroutines or define a function and declare the local scalar variables as global.

XS() isn't used and RAN isn't initialised explicitly, but it has to be in Python. So a first translation might look like this:

from random import random


def shuffle_random_numbers():
    global J_int, RAN

    if V_array[0] == 0:
        for J_int in range(100):
            V_array[J_int] = random()

    J_int = int(100 * RAN)
    RAN, V_array[J_int] = V_array[J_int], random()


def get_coefficients():
    global CODE_str, M_int, I_int, RAN
    #
    # Generate a four letter string starting with "A" followed by three random
    # characters from the range A..Y (inclusive).
    #
    CODE_str = "A"
    M_int = 3
    for I_int in range(1, M_int + 1):
        shuffle_random_numbers()
        CODE_str += chr(65 + int(25 * RAN))
    #
    # Convert the letters into values between -1.2 and 1.2.
    # 
    # "A" = -1.2, "B" = -1.1, "C" = -1.0, "D" = -0.9, …, "X": 1.1, "Y" = 1.2
    # 
    for I_int in range(1, M_int + 1):
        A_array[I_int] = (ord(CODE_str[M_int]) - 77) / 10


RAN = 0  # Initialised implicitly in the BASIC code.
A_array = [0] * 505
V_array = [0] * 100
get_coefficients()

shuffle_random_numbers() looks bogus. Either it seems really unnecessary as it just stores random()/RND results and uses the last result to pick one of the stored numbers and replaces it by a new random number. Like random()/RND it returns random numbers between 0 and 1; just in a different order. If this is because the original programmer felt the need to make the random numbers ”more random”, then scrap that function and just use random().

If that particular order is relevant to the program, you can't just use Python's random() but need to recreate the original pseudo random number generator of the BASIC dialect used to run the original program. If it's GW-BASIC then there is a faithful open source implementation written in Python: PC-BASIC.

For get_coefficients(), assuming M% and I% are just used locally, and using arguments and a return value we might end up here:

import random
from string import ascii_uppercase

A_TO_Y = ascii_uppercase[:-1]
assert A_TO_Y[-1] == "Y"


def set_coefficients(values):
    code = "A" + "".join(random.choice(A_TO_Y) for _ in range(3))
    #
    # Convert the letters into values between -1.2 and 1.2.
    #
    # "A" = -1.2, "B" = -1.1, "C" = -1.0, "D" = -0.9, …, "X": 1.1, "Y" = 1.2
    #
    offset = ord(A_TO_Y[0]) + len(A_TO_Y) // 2
    for i, letter in enumerate(code, 1):
        values[i] = (ord(letter) - offset) / 10

    return code


def main():
    values = [0] * 505
    code = set_coefficients(values)


if __name__ == "__main__":
    main()

Notice the change in the function name, because no reader would expect a get_*() function to actually set values in the sequence given as argument.

BlackJack
  • 4,476
  • 1
  • 20
  • 25