From e5eaa45a96c7559e47d98f406d3ae16d5343923d Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Tue, 6 Feb 2018 23:31:10 -0800 Subject: [PATCH] Add files via upload --- ipynb/BASIC.ipynb | 439 +++++++++++++++++++++++++++++++--------------- 1 file changed, 295 insertions(+), 144 deletions(-) diff --git a/ipynb/BASIC.ipynb b/ipynb/BASIC.ipynb index c33033f..272ef3a 100644 --- a/ipynb/BASIC.ipynb +++ b/ipynb/BASIC.ipynb @@ -14,7 +14,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "program = '''\n", @@ -59,18 +61,20 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "import re \n", "\n", - "tokenize = re.compile(\n", - " r'''\\d* \\.? \\d+ (?: E -? \\d+)? | # number \n", - " SIN|COS|TAN|ATN|EXP|ABS|LOG|SQR|RND|INT|FN[A-Z]| # functions (including user-defined FNA-FNZ)\n", - " LET|READ|DATA|PRINT|GOTO|IF|FOR|NEXT|END|STOP | # keywords\n", - " DEF|GOSUB|RETURN|DIM|REM|TO|THEN|STEP | # more keywords\n", - " [A-Z]\\d? | # variable names (letter optionally followed by a digit)\n", - " \".*? \" | # labels (strings in double quotes)\n", + "tokenize = re.compile(r'''\n", + " \\d* \\.? \\d+ (?: E -? \\d+)? | # number \n", + " SIN|COS|TAN|ATN|EXP|ABS|LOG|SQR|RND|INT|FN[A-Z]| # functions\n", + " LET|READ|DATA|PRINT|GOTO|IF|FOR|NEXT|END | # keywords\n", + " DEF|GOSUB|RETURN|DIM|REM|TO|THEN|STEP|STOP | # keywords\n", + " [A-Z]\\d? | # variable names (letter + optional digit)\n", + " \".*?\" | # labels (strings in double quotes)\n", " <>|>=|<= | # multi-character relational operators\n", " \\S # any non-space single character ''', \n", " re.VERBOSE).findall" @@ -175,14 +179,17 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "tokens = [] # Global variable to hold a list of tokens\n", "\n", "def tokenizer(line):\n", " \"Return a list of the tokens on this line, handling spaces properly, and upper-casing.\"\n", - " return tokenize(remove_spaces(line).upper())\n", + " line = ''.join(tokenize(line)) # Remove whitespace\n", + " return tokenize(line.upper())\n", "\n", "def peek(): \n", " \"Return the first token in the global `tokens`, or None if we are at the end of the line.\"\n", @@ -197,7 +204,7 @@ " \n", "def remove_spaces(line): \n", " \"Remove white space from line, except space inside double quotes.\"\n", - " return ''.join(tokenize(line))\n", + " return \n", "\n", "def lines(text): \n", " \"A list of the non-empty lines in a text.\"\n", @@ -240,9 +247,6 @@ " assert (tokenizer('100IFX1+123.4+E1-12.3E4 <> 1.2E-34*-12E34+1+\"HI\" THEN99') ==\n", " ['100', 'IF', 'X1', '+', '123.4', '+', 'E1', '-', '12.3E4', '<>', \n", " '1.2E-34', '*', '-', '12E34', '+', '1', '+', '\"HI\"', 'THEN', '99'])\n", - " assert remove_spaces('10 GO TO 99') == '10GOTO99'\n", - " assert (remove_spaces('100 PRINT \"HELLO WORLD\", SIN(X) ^ 2')\n", - " == '100PRINT\"HELLO WORLD\",SIN(X)^2')\n", " assert lines('one line') == ['one line']\n", " assert lines(program) == [\n", " '10 REM POWER TABLE',\n", @@ -364,7 +368,7 @@ "- **`DIM`** ``\n", "- **`REM`** ``\n", " \n", - "The notation `` means any variable and `` means zero or more variables, separated by commas. `[STEP ]` means that the literal string `\"STEP\"`, followed by an expression, is optional. \n", + "The notation `` means any variable and `` means zero or more variables, separated by commas. `[`**`STEP`** `]` means that the literal string `\"STEP\"` followed by an expression is optional. \n", "\n", "Rather than use one of the many [language parsing frameworks](https://wiki.python.org/moin/LanguageParsing), I will show how to build a parser from scratch. First I'll translate the grammar above into Python. Not character-for-character (because it would take a lot of work to get Python to understand how to handle those characters), but almost word-for-word (because I can envision a straightforward way to get Python to handle the following format):" ] @@ -391,7 +395,8 @@ " 'GOSUB': [linenumber],\n", " 'RETURN': [],\n", " 'DIM': [list_of(variable)], \n", - " 'REM': [anycharacters]\n", + " 'REM': [anycharacters],\n", + " 'A': []\n", " }" ] }, @@ -462,9 +467,9 @@ "metadata": {}, "outputs": [], "source": [ - "def linenumber(): return (int(pop()) if peek().isnumeric() else fail('missing line number'))\n", "def number(): return (-1 if pop('-') else +1) * float(pop()) # Optional minus sign\n", "def step(): return (expression() if pop('STEP') else 1) # 1 is the default step\n", + "def linenumber(): return (int(pop()) if peek().isnumeric() else fail('missing line number'))\n", "def relational(): return pop(is_relational) or fail('expected a relational operator')\n", "def varname(): return pop(is_varname) or fail('expected a variable name')\n", "def funcname(): return pop(is_funcname) or fail('expected a function name')\n", @@ -506,7 +511,9 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def variable(): \n", @@ -562,7 +569,7 @@ }, "outputs": [], "source": [ - "def parse(program): return sorted(map(parse_line, lines(program)))\n", + "def parse(program): return sorted(parse_line(line) for line in lines(program))\n", "\n", "def parse_line(line): global tokens; tokens = tokenizer(line); return statement()" ] @@ -577,7 +584,9 @@ { "cell_type": "code", "execution_count": 16, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def parse_line(line):\n", @@ -648,7 +657,7 @@ "* Optionally, `\";\"` can be used instead of `\",\"`.\n", "* Optionally, the `\",\"` or `\";\"` can be omitted—we can have a label immediately followed by an expression.\n", "\n", - "The effect of a comma is to advance the output to the next column that is a multiple of 15 (and to a new line if this goes past column 75). The effect of a semicolon is similar, but works in multiples of 3, not 15. (Note that column numbering starts at 0, not 1.) Normally, at the end of a `PRINT` statement we advance to a new line, but this is not done if the statement ends in `\",\"` or `\";\"`. Here are some examples:\n", + "The effect of a comma is to advance the output to the next column that is a multiple of 15 (and to a new line if this goes past column 100). The effect of a semicolon is similar, but works in multiples of 3, not 15. (Note that column numbering starts at 0, not 1.) Normally, at the end of a `PRINT` statement we advance to a new line, but this is not done if the statement ends in `\",\"` or `\";\"`. Here are some examples:\n", "\n", "* `10 PRINT X, Y`\n", "
Prints the value of `X` in column 0 and `Y` in column 15. Advances to new line.\n", @@ -711,7 +720,9 @@ { "cell_type": "code", "execution_count": 19, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def expression(prec=1): \n", @@ -733,7 +744,7 @@ " return Funcall(pop(), primary())\n", " elif pop('-'): # '-X' => Funcall('NEG', 'X')\n", " return Funcall('NEG', primary())\n", - " elif pop('('): # '(X + 1)' => Opcall('X', '+', 1)\n", + " elif pop('('): # '(X)' => 'X'\n", " exp = expression()\n", " pop(')') or fail('expected \")\" to end expression')\n", " return exp\n", @@ -741,7 +752,7 @@ " return fail('unknown expression')\n", "\n", "def precedence(op): \n", - " return (3 if op == '^' else 2 if op in ('*', '/') else 1 if op in ('+', '-') else 0)\n", + " return (3 if op == '^' else 2 if op in ('*', '/', '%') else 1 if op in ('+', '-') else 0)\n", "\n", "def associativity(op): \n", " return (0 if op == '^' else 1)" @@ -897,7 +908,9 @@ { "cell_type": "code", "execution_count": 23, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def execute(stmts): \n", @@ -958,7 +971,9 @@ { "cell_type": "code", "execution_count": 24, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "import math\n", @@ -973,7 +988,7 @@ " 'SIN': math.sin, 'COS': math.cos, 'TAN': math.tan, 'ATN': math.atan, \n", " 'ABS': abs, 'EXP': math.exp, 'LOG': math.log, 'SQR': math.sqrt, 'INT': int,\n", " '>': op.gt, '<': op.lt, '=': op.eq, '>=': op.ge, '<=': op.le, '<>': op.ne, \n", - " '^': pow, '+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv, \n", + " '^': pow, '+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv, '%': op.mod,\n", " 'RND': lambda _: random.random(), 'NEG': op.neg}\n", " data = deque() # A queue of numbers that READ can read from\n", " for (_, typ, args) in stmts:\n", @@ -1052,7 +1067,9 @@ { "cell_type": "code", "execution_count": 25, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "def basic_print(items): \n", @@ -1066,11 +1083,11 @@ " newline()\n", " \n", "def print_string(s): \n", - " \"Print a string, keeping track of column, and advancing to newline if at or beyond column 75.\"\n", + " \"Print a string, keeping track of column, and advancing to newline if at or beyond column 100.\"\n", " global column\n", " print(s, end='')\n", " column += len(s)\n", - " if column >= 75: newline()\n", + " if column >= 100: newline()\n", " \n", "def pad(width): \n", " \"Pad out to the column that is the next multiple of width.\"\n", @@ -1284,9 +1301,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "1 2 3 4 5 \n", - "6 7 8 9 10 \n", - "11 12 " + "1 2 3 4 5 6 7 \n", + "8 9 10 11 12 " ] } ], @@ -1330,14 +1346,12 @@ "N = 5 I^N:\n", "1 32 243 1024 3125 \n", "\n", - "\n", "N = 6 I^N:\n", - "1 64 729 4096 15625 \n", - "46656 \n", + "1 64 729 4096 15625 46656 \n", "\n", "N = 7 I^N:\n", - "1 128 2187 16384 78125 \n", - "279936 823543 \n", + "1 128 2187 16384 78125 279936 823543 \n", + "\n", "\n" ] } @@ -1372,10 +1386,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "1e+06 941192 884736 830584 778688 729000 681472 636056 592704 \n", - "551368 512000 474552 438976 405224 373248 343000 314432 287496 \n", - "262144 238328 216000 195112 175616 157464 140608 125000 110592 \n", - "97336 85184 74088 64000 54872 46656 39304 32768 27000 21952 17576 13824 10648 \n", + "1e+06 941192 884736 830584 778688 729000 681472 636056 592704 551368 512000 474552 \n", + "438976 405224 373248 343000 314432 287496 262144 238328 216000 195112 175616 157464 \n", + "140608 125000 110592 97336 85184 74088 64000 54872 46656 39304 32768 27000 21952 17576 13824 10648 \n", "8000 5832 4096 2744 1728 1000 512 216 64 8 0 " ] } @@ -1454,27 +1467,27 @@ "data": { "text/plain": [ "defaultdict(float,\n", - " {('P', (1.0,)): 1.25,\n", - " ('S', (3.0, 5.0)): 33.0,\n", - " ('S', (3.0, 4.0)): 16.0,\n", - " ('S', (1.0, 5.0)): 42.0,\n", - " ('S', (2.0, 1.0)): 10.0,\n", - " ('P', (3.0,)): 2.5,\n", - " ('S', (1.0, 4.0)): 29.0,\n", - " ('S', (1.0, 2.0)): 20.0,\n", - " ('S', (3.0, 1.0)): 35.0,\n", - " 'I': 3.0,\n", - " ('S', (2.0, 4.0)): 21.0,\n", - " ('P', (2.0,)): 4.3,\n", - " ('S', (2.0, 5.0)): 8.0,\n", - " ('S', (1.0, 1.0)): 40.0,\n", - " ('S', (3.0, 3.0)): 29.0,\n", + " {('S', (2.0, 1.0)): 10.0,\n", " 'J': 5.0,\n", + " ('S', (1.0, 1.0)): 40.0,\n", + " ('S', (3.0, 5.0)): 33.0,\n", + " ('P', (2.0,)): 4.3,\n", " ('S', (3.0, 2.0)): 47.0,\n", + " ('S', (2.0, 5.0)): 8.0,\n", + " 'I': 3.0,\n", + " ('P', (1.0,)): 1.25,\n", + " ('S', (1.0, 4.0)): 29.0,\n", + " ('S', (2.0, 4.0)): 21.0,\n", + " ('S', (3.0, 1.0)): 35.0,\n", + " ('S', (3.0, 4.0)): 16.0,\n", " 'S': 169.4,\n", - " ('S', (2.0, 2.0)): 16.0,\n", + " ('S', (1.0, 2.0)): 20.0,\n", + " ('S', (1.0, 3.0)): 37.0,\n", " ('S', (2.0, 3.0)): 3.0,\n", - " ('S', (1.0, 3.0)): 37.0})" + " ('S', (2.0, 2.0)): 16.0,\n", + " ('S', (1.0, 5.0)): 42.0,\n", + " ('P', (3.0,)): 2.5,\n", + " ('S', (3.0, 3.0)): 29.0})" ] }, "execution_count": 35, @@ -1495,10 +1508,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "7 2 8 4 9 1 9 8 0 8 3 0 3 4 5 6 4 2 1 6 9 9 9 7 5 \n", - "8 4 1 5 9 9 7 5 5 7 2 0 3 2 1 2 1 8 3 9 9 3 0 0 2 \n", - "2 8 9 7 5 8 9 0 2 7 8 6 7 4 9 9 4 2 5 4 0 9 6 5 3 \n", - "3 3 5 1 7 1 6 1 0 7 3 5 8 1 9 0 4 7 1 7 4 5 1 7 6 \n" + "3 1 1 0 2 4 4 5 1 1 8 5 5 0 1 0 4 9 4 6 2 7 7 9 4 4 1 5 6 7 3 6 4 1 \n", + "1 2 0 8 7 2 5 4 1 2 1 5 1 0 2 5 7 8 2 9 3 4 7 5 9 6 8 6 9 4 9 1 6 6 \n", + "1 6 9 2 8 1 4 8 4 7 2 2 6 0 0 5 6 4 9 5 8 7 1 3 8 8 1 9 4 2 8 3 " ] } ], @@ -1655,54 +1667,61 @@ "name": "stdout", "output_type": "stream", "text": [ - "Error in line '10 X = 1' at 'X = 1': unknown statement type\n", - "Error in line '20 GO TO JAIL' at 'J A I L': missing line number\n", - "Error in line '30 FOR I = 1 ' at '': expected 'TO'\n", - "Error in line '40 IF X > 0 & X < 10 GOTO 999' at '& X < 10 GOTO 999': expected 'THEN'\n", - "Error in line '50 LET Z = (Z + 1' at '': expected \")\" to end expression\n", - "Error in line '60 PRINT \"OH CANADA\", EH?' at '?': unknown expression\n", - "Error in line '70 LET Z = +3' at '+ 3': unknown expression\n", - "Error in line '80 LET X = Y ** 2' at '* 2': unknown expression\n", - "Error in line '90 LET A(I = 1' at '= 1': expected \")\" to close subscript\n", - "Error in line '100 IF A = 0 THEN 900 + 99' at '+ 99': extra tokens at end of line\n", - "Error in line '110 NEXT A(I)' at '( I )': extra tokens at end of line\n", - "Error in line '120 DEF F(X) = X ^ 2 + 1' at 'F ( X ) = X ^ 2 + 1': expected a function name\n", - "Error in line '130 IF X != 0 THEN 999' at '! = 0 THEN 999': expected a relational operator\n", - "Error in line '140 DEF FNS(X + 2*P1) = SIN(X)' at '+ 2 * P1 ) = SIN ( X )': expected ')'\n", - "Error in line '150 DEF FNY(M, B) = M * X + B' at ', B ) = M * X + B': expected ')'\n", - "Error in line '160 LET 3 = X' at '3 = X': expected a variable name\n", - "Error in line '170 LET SIN = 7 * DEADLY' at 'SIN = 7 * D E A D L Y': expected a variable name\n", - "Error in line '180 LET X = A-1(I)' at '( I )': extra tokens at end of line\n", - "Error in line 'STOP' at 'STOP': missing line number\n", - "Error in line '200 STOP IT, ALREADY' at 'I T , A L READ Y': extra tokens at end of line\n", - "PROGRAM STILL EXECUTES: 2 + 2 = 4 \n" + "Error in line '1 X = 1' at 'X = 1': unknown statement type\n", + "Error in line '2 GO TO JAIL' at 'J A I L': missing line number\n", + "Error in line '3 FOR I = 1 ' at '': expected 'TO'\n", + "Error in line '4 IF X > 0 & X < 10 GOTO 999' at '& X < 10 GOTO 999': expected 'THEN'\n", + "Error in line '5 LET Z = (Z + 1' at '': expected \")\" to end expression\n", + "Error in line '6 PRINT \"OH CANADA\", EH?' at '?': unknown expression\n", + "Error in line '7 LET Z = +3' at '+ 3': unknown expression\n", + "Error in line '8 LET X = Y ** 2' at '* 2': unknown expression\n", + "Error in line '9 LET A(I = 1' at '= 1': expected \")\" to close subscript\n", + "Error in line '10 IF A = 0 THEN 900 + 99' at '+ 99': extra tokens at end of line\n", + "Error in line '11 NEXT A(I)' at '( I )': extra tokens at end of line\n", + "Error in line '12 DEF F(X) = X ^ 2 + 1' at 'F ( X ) = X ^ 2 + 1': expected a function name\n", + "Error in line '13 IF X != 0 THEN 999' at '! = 0 THEN 999': expected a relational operator\n", + "Error in line '14 DEF FNS(X + 2*P1) = SIN(X)' at '+ 2 * P1 ) = SIN ( X )': expected ')'\n", + "Error in line '15 DEF FNY(M, B) = M * X + B' at ', B ) = M * X + B': expected ')'\n", + "Error in line '16 LET 3 = X' at '3 = X': expected a variable name\n", + "Error in line '17 LET SIN = 7 * DEADLY' at 'SIN = 7 * D E A D L Y': expected a variable name\n", + "Error in line '18 LET X = A-1(I)' at '( I )': extra tokens at end of line\n", + "Error in line '19 FOR SCORE + 7' at 'C O R E + 7': expected '='\n", + "Error in line '20 STOP IN NAME(LOVE)' at 'I N N A M E ( L O V E )': extra tokens at end of line\n", + "Error in line '85 ENDURANCE.' at 'U R A N C E .': extra tokens at end of line\n", + "ADD 2 + 2 = 4 \n" ] } ], "source": [ "run('''\n", - "10 X = 1\n", - "20 GO TO JAIL\n", - "30 FOR I = 1 \n", - "40 IF X > 0 & X < 10 GOTO 999\n", - "50 LET Z = (Z + 1\n", - "60 PRINT \"OH CANADA\", EH?\n", - "70 LET Z = +3\n", - "80 LET X = Y ** 2\n", - "90 LET A(I = 1\n", - "100 IF A = 0 THEN 900 + 99\n", - "110 NEXT A(I)\n", - "120 DEF F(X) = X ^ 2 + 1\n", - "130 IF X != 0 THEN 999\n", - "140 DEF FNS(X + 2*P1) = SIN(X)\n", - "150 DEF FNY(M, B) = M * X + B\n", - "160 LET 3 = X\n", - "170 LET SIN = 7 * DEADLY\n", - "180 LET X = A-1(I)\n", - "STOP\n", - "200 STOP IT, ALREADY\n", - "998 PRINT \"PROGRAM STILL EXECUTES: 2 + 2 = \" 2 + 2\n", - "999 END\n", + "1 X = 1\n", + "2 GO TO JAIL\n", + "3 FOR I = 1 \n", + "4 IF X > 0 & X < 10 GOTO 999\n", + "5 LET Z = (Z + 1\n", + "6 PRINT \"OH CANADA\", EH?\n", + "7 LET Z = +3\n", + "8 LET X = Y ** 2\n", + "9 LET A(I = 1\n", + "10 IF A = 0 THEN 900 + 99\n", + "11 NEXT A(I)\n", + "12 DEF F(X) = X ^ 2 + 1\n", + "13 IF X != 0 THEN 999\n", + "14 DEF FNS(X + 2*P1) = SIN(X)\n", + "15 DEF FNY(M, B) = M * X + B\n", + "16 LET 3 = X\n", + "17 LET SIN = 7 * DEADLY\n", + "18 LET X = A-1(I)\n", + "19 FOR SCORE + 7\n", + "20 STOP IN NAME(LOVE)\n", + "80 REMARKABLY, THE INTERPRETER\n", + "81 REMEDIES THE ERRORS, AND THE PROPGRAM\n", + "82 REMAINS AN EXECUTABLE ENTITY, UN-\n", + "83 REMITTENTLY RUNNING, WITH NO\n", + "84 REMORSE OR REGRETS, AND WITH GREAT\n", + "85 ENDURANCE.\n", + "98 PRINT \"ADD 2 + 2 = \" 2 + 2\n", + "99 END\n", "''')" ] }, @@ -1710,10 +1729,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Final Program\n", + "# Longer Program: Life\n", "\n", - "Now for a final, longer example, Conway's Game of [Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life),\n", - "which shows that BASIC is capable of handling a non-trivial problem—but I wouldn't want to rely on it for anything much bigger. This should give us some added confidence in the validity of the interpreter, but I would say the interpreter needs more work before I would trust it. I hope you found working through the interpreter infortative, and maybe you have ideas for how to improve it, or to develop an interpreter for another language." + "Now for a final, slightly more complicated example: Conway's Game of [Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life). " ] }, { @@ -1725,7 +1743,7 @@ "name": "stdout", "output_type": "stream", "text": [ - " GENERATION 0 \n", + "GEN 0 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", @@ -1736,7 +1754,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 1 \n", + "GEN 1 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", @@ -1747,7 +1765,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 2 \n", + "GEN 2 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", @@ -1758,7 +1776,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 3 \n", + "GEN 3 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", @@ -1769,7 +1787,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 4 \n", + "GEN 4 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", @@ -1780,7 +1798,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 5 \n", + "GEN 5 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . O O . . . . . \n", @@ -1791,7 +1809,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 6 \n", + "GEN 6 \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . O O O . . . . \n", @@ -1802,7 +1820,7 @@ ". . . . . . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 7 \n", + "GEN 7 \n", ". . . . . . . . . . \n", ". . . . O . . . . . \n", ". . . O . O . . . . \n", @@ -1813,7 +1831,7 @@ ". . . . O . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 8 \n", + "GEN 8 \n", ". . . . . . . . . . \n", ". . . . O . . . . . \n", ". . . O O O . . . . \n", @@ -1824,7 +1842,7 @@ ". . . . O . . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 9 \n", + "GEN 9 \n", ". . . . . . . . . . \n", ". . . O O O . . . . \n", ". . O . . . O . . . \n", @@ -1835,7 +1853,7 @@ ". . . O O O . . . . \n", ". . . . . . . . . . \n", ". . . . . . . . . . \n", - " GENERATION 10 \n", + "GEN 10 \n", ". . . . O . . . . . \n", ". . . O O O . . . . \n", ". . O O O O O . . . \n", @@ -1852,15 +1870,17 @@ "source": [ "run('''\n", "100 REM CONWAY'S GAME OF LIFE\n", - "102 REM G IS NUMBER OF GENERATIONS, M IS MATRIX SIZE (M X M)\n", - "104 REM A(X, Y) IS 1 IFF CELL AT (X, Y) IS LIVE\n", - "106 REM L(SELF_ALIVE, NEIGHBORS_ALIVE) IS 1 IFF CELL WITH THOSE COUNTS SHOULD LIVE ON\n", - "110 LET G = 10\n", - "120 LET M = 10\n", - "125 READ A(3,4), A(3,5), A(3,6), A(6,5), A(6,6), A(7,5), A(7,6)\n", - "130 DATA 1, 1, 1, 1, 1, 1, 1\n", - "140 READ L(0, 3), L(1, 3), L(1, 2)\n", - "145 DATA 1, 1, 1\n", + "\n", + "102 REM G IS NUMBER OF GENERATIONS, \n", + "104 REM M IS MATRIX SIZE (M X M)\n", + "106 REM L(SELF, NEIGHBORS_ALIVE) IS 1 IFF CELL WITH THOSE COUNTS LIVES\n", + "108 REM A(X, Y) IS 1 IFF CELL AT (X, Y) IS LIVE\n", + "110 REM B(X, Y) GETS THE NEXT GENERATION\n", + "\n", + "120 READ G, M, L(0,3), L(1,3), L(1,2)\n", + "121 DATA 10, 10, 1, 1, 1\n", + "130 READ A(3,4), A(3,5), A(3,6), A(6,5), A(6,6), A(7,5), A(7,6)\n", + "131 DATA 1, 1, 1, 1, 1, 1, 1\n", "\n", "150 REM MAIN LOOP: PRINT, THEN REPEAT G TIMES: UPDATE / COPY / PRINT\n", "155 LET I = 0\n", @@ -1872,7 +1892,7 @@ "210 NEXT I\n", "220 STOP\n", "\n", - "300 REM SUBROUTINE: UPDATE B = NEXT_GENERATION(A)\n", + "300 REM ========== UPDATE B = NEXT_GEN(A)\n", "310 FOR Y = 1 TO M\n", "320 FOR X = 1 TO M\n", "325 LET N = A(X-1,Y)+A(X+1,Y)+A(X,Y-1)+A(X,Y+1)+A(X-1,Y-1)+A(X+1,Y+1)+A(X-1,Y+1)+A(X+1,Y-1)\n", @@ -1881,7 +1901,7 @@ "350 NEXT Y\n", "360 RETURN\n", "\n", - "500 REM SUBROUTINE: COPY A = B\n", + "500 REM ========== COPY A = B\n", "510 FOR Y = 1 TO M\n", "520 FOR X = 1 TO M\n", "530 LET A(X, Y) = B(X, Y)\n", @@ -1889,14 +1909,14 @@ "550 NEXT Y\n", "560 RETURN\n", "\n", - "700 REM SUBROUTINE: PRINT A\n", - "705 PRINT \" GENERATION \" I\n", + "700 REM ========== PRINT A\n", + "705 PRINT \"GEN \" I\n", "710 FOR Y = 1 TO M\n", "720 FOR X = 1 TO M\n", - "730 IF A(X, Y) = 0 THEN 750\n", - "740 PRINT \"O\";\n", - "750 IF A(X, Y) = 1 THEN 770\n", - "760 PRINT \".\";\n", + "730 IF A(X, Y) = 1 THEN 760\n", + "740 PRINT \".\";\n", + "750 GOTO 770\n", + "760 PRINT \"O\";\n", "770 NEXT X\n", "780 PRINT\n", "790 NEXT Y\n", @@ -1906,14 +1926,145 @@ "''')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Actually, this was an assignment in my high school BASIC class. (We used a [slightly different](https://www.grc.com/pdp-8/docs/os8_basic_reference.pdf) version of BASIC.) Back then, output was on rolls of paper, and I thought it was wasteful to print only one generation per line. So I arranged to print multiple generations on the same line, storing them until it was time to print them out. But BASIC doesn't have three-dimensional arrays, so I needed to store several generations worth of data in one `A(X, Y)` value. Today, I know that that could be done by allocating one bit for each generation, but back then I don't think I knew about binary representation, so I stored one generation in each decimal digit. That means I no longer need two matrixes, `A` and `B`; instead, the current generation will always be the value in the one's place, the previous generation in the ten's place, and the one before that in the hundred's place. (Also, I admit I cheated: I added the mod operatoir, `%`, which did not appear in early versions of BASIC, just because it was useful for this program.)" + ] + }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GEN 0 GEN 1 GEN 2 \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . O . . . . . . . | . . . . . . . . . . | . . O . . . . . . . | \n", + ". . O . . O O . . . | . O O O . O O . . . | . . O . O O O . . . | \n", + ". . O . . O O . . . | . . . . . O O . . . | . . O . O O O . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + "GEN 3 GEN 4 GEN 5 \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . O O . . . . . | \n", + ". . . O . O . . . . | . . O O O O . . . . | . . O . O O . . . . | \n", + ". O O . O . O . . . | . . O . O . O . . . | . . O . . . O . . . | \n", + ". . . . O . O . . . | . . . O O . O . . . | . . . O O . O . . . | \n", + ". . . . . O . . . . | . . . . . O . . . . | . . . . O O . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + "GEN 6 GEN 7 GEN 8 \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . O . . . . . | . . . . O . . . . . | \n", + ". . . O O O . . . . | . . . O . O . . . . | . . . O O O . . . . | \n", + ". . O . O O . . . . | . . O . . . O . . . | . . O O . O O . . . | \n", + ". . O . . . O . . . | . . O . . . O . . . | . O O O . O O O . . | \n", + ". . . O O . O . . . | . . O . . . O . . . | . . O O . O O . . . | \n", + ". . . O O O . . . . | . . . O . O . . . . | . . . O O O . . . . | \n", + ". . . . . . . . . . | . . . . O . . . . . | . . . . O . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n", + "GEN 9 GEN 10 GEN 11 \n", + ". . . . . . . . . . | . . . . O . . . . . | . . . O O O . . . . | \n", + ". . . O O O . . . . | . . . O O O . . . . | . . O . . . O . . . | \n", + ". . O . . . O . . . | . . O O O O O . . . | . O . . . . . O . . | \n", + ". O . . . . . O . . | . O O . . . O O . . | O . . . O . . . O . | \n", + ". O . . . . . O . . | O O O . . . O O O . | O . . O . O . . O . | \n", + ". O . . . . . O . . | . O O . . . O O . . | O . . . O . . . O . | \n", + ". . O . . . O . . . | . . O O O O O . . . | . O . . . . . O . . | \n", + ". . . O O O . . . . | . . . O O O . . . . | . . O . . . O . . . | \n", + ". . . . . . . . . . | . . . . O . . . . . | . . . O O O . . . . | \n", + ". . . . . . . . . . | . . . . . . . . . . | . . . . . . . . . . | \n" + ] + } + ], + "source": [ + "run('''\n", + "100 REM CONWAY'S GAME OF LIFE\n", + "\n", + "102 REM G IS NUMBER OF GENERATIONS, \n", + "104 REM M IS MATRIX SIZE (M X M)\n", + "106 REM L(SELF, NEIGHBORS_ALIVE) IS 1 IFF CELL WITH THOSE COUNTS LIVES\n", + "108 REM A(X, Y) STORES THE HISTORY OF THE CELL AT (X, Y); 1 MEANS LIVE,\n", + "110 REM BUT WE STORE SEVERAL GENERATIONS: A(X, Y) = 100 MEANS THE CELL\n", + "112 REM IS DEAD IN THE CURRENT AND PREVIOUS GENERATION (00), BUT LIVE IN THE\n", + "114 REM GENERATION BEFORE THAT (1). WE STORE MULTIPLE GENERATIONS SO THAT\n", + "116 REM WE CAN PRINT THEM OUT ON ONE LINE, SAVING SPACE/PAPER.\n", + "\n", + "120 READ G, M, L(0,3), L(1,3), L(1,2)\n", + "122 DATA 11, 10, 1, 1, 1\n", + "124 READ A(3,4), A(3,5), A(3,6), A(6,5), A(6,6), A(7,5), A(7,6)\n", + "126 DATA 1, 1, 1, 1, 1, 1, 1\n", + "\n", + "130 REM FNA(N) = THE PREVIOUS GENERATION'S VALUE\n", + "132 DEF FNA(N) = INT(N / 10) % 10\n", + "\n", + "134 REM FNC(N) = THE GENERATION IN COLUMN C; FNC(123) = C FOR EACH C IN 1..3\n", + "136 DEF FNC(N) = FNA(N / (10 ^ (2 - C)))\n", + "\n", + "150 REM MAIN LOOP: DO 3 UPDATES (2 FIRST TIME), THEN PRINT AND SHIFT\n", + "160 FOR I = 1 TO G\n", + "170 GOSUB 300\n", + "175 IF I % 3 <> 2 THEN 200\n", + "180 GOSUB 700\n", + "190 GOSUB 800\n", + "200 NEXT I\n", + "210 STOP\n", + "\n", + "300 REM ========== UPDATE A: SHIFT OLD GENS LEFT; ADD IN NEW GEN\n", + "310 FOR Y = 1 TO M\n", + "320 FOR X = 1 TO M \n", + "330 LET A(X, Y) = 10 * A(X, Y)\n", + "340 NEXT X\n", + "350 NEXT Y\n", + "360 FOR Y = 1 TO M\n", + "370 FOR X = 1 TO M \n", + "380 LET N1 = FNA(A(X+1,Y-1)) + FNA(A(X+1,Y)) + FNA(A(X+1,Y+1)) + FNA(A(X,Y-1))\n", + "390 LET N2 = FNA(A(X-1,Y-1)) + FNA(A(X-1,Y)) + FNA(A(X-1,Y+1)) + FNA(A(X,Y+1))\n", + "400 LET S = FNA(A(X, Y))\n", + "410 LET A(X, Y) = A(X, Y) + L(S, N1 + N2)\n", + "420 NEXT X\n", + "430 NEXT Y\n", + "440 RETURN\n", + "\n", + "700 REM ========== PRINT A (3 GENERATIONS ACROSS THE PAGE)\n", + "705 PRINT \"GEN \" I-2, \" \", \" GEN \" I-1, \" \", \" GEN \" I\n", + "710 FOR Y = 1 TO M\n", + "715 FOR C = 1 TO 3\n", + "720 FOR X = 1 TO M\n", + "730 IF FNC(A(X, Y)) = 1 THEN 760\n", + "740 PRINT \".\";\n", + "750 GOTO 770\n", + "760 PRINT \"O\";\n", + "770 NEXT X\n", + "775 PRINT \"|\";\n", + "777 NEXT C\n", + "780 PRINT\n", + "790 NEXT Y\n", + "795 RETURN\n", + "\n", + "800 REM ========== FORGET ALL BUT THE MOST RECENT GENERATION IN A\n", + "810 FOR Y = 1 TO M\n", + "820 FOR X = 1 TO M\n", + "830 LET A(X, Y) = A(X, Y) % 10\n", + "840 NEXT X\n", + "850 NEXT Y\n", + "860 RETURN\n", + "\n", + "999 END \n", + "''')" + ] } ], "metadata": {