Monday, June 22, 2015

Watch and be Amazed!!! - Game 2

Okay, okay, cornball title. But you could hardly blame me for taking such an approach to this week's "101 BASIC Computer Games" conversion of Game 2: "Amazing." The fact that I decoded this particular chunk of BASIC is, well, a little amazing all by itself (groan). :). Be sure to follow the original BASIC source for AMAZING at Atari Archives. As always, I invite you to join the blog, leave a comment, and have a great time as we look at these great old games!

Download the full set of files for this week's port at our SourceForge repository!!

Perhaps more than anything else, "Amazing" demonstrates for us the truly unpleasant evil of abused GOTO statements in our old BASIC language source. Decoding the design intent of the original author from the source is manifestly difficult because so many switches in logic are reflected only in jumps to line numbers, that lead to more jumps to more line numbers, and then jump back near the beginning, to still more line numbers - and in many cases, to execute blocks of freqently repeated code.

The unfortuante aspect of this "death by 1,000 GOTO's" is that a rather elegant algorithm for constructing this electronic maze is buried amid the GOTO's. I remember studying this program extensively when I first bought this book, finding myself fascinated at the way the program was described as one *guaranteeing* only one path through - yet never understanding why. These undescriptive variables and chained GOTO's were part of the reason why I couldn't quite keep it straight - to say nothing of the single-letter variable names that didn't seem to remotely reflect their purpose.

Amazingly simple


"Amazing" models a maze as a simple grid of "cells" in the height and width specified by the user (see Figure 1). Building the maze amounts to knocking out walls by carving paths through the maze.
A simple 5x5 maze grid

Carving out paths


To start, each cell in the grid has four walls - north, east, south, and west. To carve out a path through the maze, it follows a simple algorithm:
1. Place a "visitor" in the maze - initially this is the "start" location. Mark the cell as having been "visited."
2. The "visitor" moves randomly by one cell in any direction - up, down, left or right - subject to the following rules
     a. The visitor cannot go beyond the natural boundaries of the maze.
     b. The visitor cannot go to a cell that's already been visited.
3. Remove the wall between where the visitor was and where he now stands.
4. If all cells have been visited, the maze is complete.
5. Repeat the process at step 2 unless the visitor has hit a dead end per the movement rules.
6. Retart the process, re-starting the visitor at a cell already visited on a previous path.

Amazing tracks "visited" cells separately from the maze's "walls." While each cell has four physical walls, those walls become two logical walls in the program - east and south (Figure 2).
Figure 2 - Cell walls
Each cell's north wall is actually modeled as the south wall of the cell directly to its north; each west wall is modeled as the east wall of the cell directly west.  Amazing holds the wall information in a two-dimensional array named "V", and the visitation list in an an array named "W" - try decoding those two letters from four-decade-old dot-matrix print with 50-year-old eyes through a bifocal!!

Amazing - Javascript style


Let's start the source for our Javascript version. The visitor tracking variables R and S become currentColumn and currentRow, respectively; H and V become width and height. "C," holding the cell number, becomes cellCount, and a curious ping-pong of "Z" and "Q" used to control when and if the exit has been created are replaced with a simple boolen "exitPlaced" variable.

function Amazing(gameConsole){

    var ref=this;
    var width, height;  // maps to W, H in original
    var wVisited=[];   // maps to "w" array in original; will become 2d when initialized [][]
    var vWalls=[];     // maps to "V" array in original
    var cellCount=1;
    var currentRow, currentColumn;
    var QControl=0;
    var console = gameConsole;
    var	ZControl=true; // ZControl indicates whether an exit on the bottom row has been defined
	               // This allows us to go "down" on the bottom row *one* time - to define an exit.
    var exitPlaced = false; 
    var visitedCellList= [];	
	this.Play = function(){
		console.writeLine("AMAZING PROGRAM");
		console.writeLine("CREATIVE COMPUTING - MORRISTOWN, NEW JERSEY");
		console.writeLine("Original program credit: Jack Hauber of Windsor, Connecticut");
		console.writeLine("Javascript port: David Whitney, Oklahoma City, OK");
		ref.GetDimensions();
	}

How it works



Figure 3 illustrates the first two theoretical paths through a sample 5x5 maze. The yellow arrows represent a "first" path traversal, entering at the maze starting point (Start #1, picked randomly along the first row), followed by a path carving sequence of right-down-right-up before dead-ending. Each cell visited in the first path is slightly shaded to indicate it having been visited. Following the dead-end of the first path, the algorithm repeats by randomly selecting a new starting position (Start #2) for the visitor somewhere along a non dead-end cell from any previously constructed path, moving again in once-cell steps, stopping when a dead end is reached (End #2).


The process of tracing paths by always starting the visitor in a location previously visited means that we're already expanding a previously defined path. Continuing that strategy eventually will extend, as noted, to the bottom row, and an exit. The code allows such a choice - going "down" from a cell on the bottom row - one time. Once the "exit" has been defined, the perpetual extension of the remaining paths simply fill the rest of the maze until all cells have been visited. That's how the code guarantees a single exit path from start to finish.

What makes this simple algorithm so hard to "see" amid the original BASIC source is, in addition to the GOTO's, the repeated "chunks" of code that are really doing nothing more than bounds checking to determine which "chunk" of options are possible for the visitor at a given point. The original source wants to know ahead of time if it's going to generate a choice, for example, between left, up, or down (line 350), up, right, or down (line 580), and so on. Doing this, however, requires pieces of the bounds checking code to be repeated.

Rather than try to reproduce this kind of logic, I opted to redesign it. I wrapped the bounds checking code into four simply named routines - canMove{Up/Down/Left/Right}(). The visitor is then allowed to make a random choice from among any of the "available" directions. If no directions are available, the visitor is dead-ended, and must restart his journey on a new location (cell) from among those previously visited.

	function canMoveLeft(){
	    if (currentColumn==0)
			return false;
		else
			return (wVisited[currentColumn-1][currentRow]==0);
	};
	
	function canMoveRight(){
		if (currentColumn==width-1)
			return false;
		else
			return (wVisited[currentColumn+1][currentRow]==0);
	};
	
	function canMoveDown(){
		if (currentRow==height-1){
			if (exitPlaced){
			    QControl =1;
				return false;
			} else {
				return true;
			}
		}
		else
			return (wVisited[currentColumn][currentRow+1]==0);
	};
	
	function canMoveUp(){
		if (currentRow==0)
			return false;
		else
			return (wVisited[currentColumn][currentRow-1]==0);
	};
	// Replaces BASIC source lines 820-850
 	function moveUp(){
		wVisited[currentColumn][currentRow-1]=cellCount; 
		vWalls[currentColumn][currentRow-1]=1;
		visitedCellList.push( {column: currentColumn, row: currentRow-1} );
		currentRow--;
		cellCount++;
	}
	
	// Replaces BASIC source  lines 910-955
	function moveDown(){
		if (currentRow!=height-1){
		//if (QControl==0){
			wVisited[currentColumn][currentRow+1]=cellCount;
			visitedCellList.push( {column: currentColumn, row: currentRow+1} );
			cellCount++;
			if (vWalls[currentColumn][currentRow]==0){
				vWalls[currentColumn][currentRow]=1;
			} else {
				vWalls[currentColumn][currentRow]=3;
			}
			currentRow++;
		} else {
			ZControl = false; // exit found, can't go down on bottom row now
			exitPlaced=true;
			//console.write("Exit placed.");
			if (vWalls[currentColumn][currentRow]==0){
				vWalls[currentColumn][currentRow]=1;
				currentRow=0;
				currentColumn=0;
			} else {
				vWalls[currentColumn][currentRow]=3;
			}
			QControl=0;
		}
		
	}
	// BASIC lines 860-905
   	function moveRight(){
		wVisited[currentColumn+1][currentRow]=cellCount;
		visitedCellList.push( {column: currentColumn+1, row: currentRow} );
		cellCount++;
		if (vWalls[currentColumn][currentRow]==0){
			vWalls[currentColumn][currentRow]=2;
		} else {
			vWalls[currentColumn][currentRow]=3;
		}
		currentColumn++;
	}
	// BASIC lines 790-815
	function moveLeft(){
		wVisited[currentColumn-1][currentRow]=cellCount;
		visitedCellList.push( {column: currentColumn-1, row: currentRow} );
		cellCount++;
		vWalls[currentColumn-1][currentRow]=2;
		currentColumn--;
	}
Another change in the program's design I opted to implement improves the efficiency of the maze computation in a couple of ways. The original source, upon dead-ending, always moved linearly in a left-to-right, top-to-bottom fashion looking for an unvisited cell, sometimes restarting in the upper left-hand corner. I think this tended to generate mazes that were more "open" near the top, with longer walls at or near the bottom. To fix this, I added a "visitedCellList" array that gets the coordinates of each cell as it is visited. When the visitor's starting position must be assigned, a unlocked cell from this list is chosen at random, distributing the starting positions more evenly:
	function isLocked(column, row){
	    return ( 
				 (column==0 || (wVisited[column-1][row] > 0)) &&
				 (row==0 || (wVisited[column][row-1] > 0)) &&
				 (column==width-1 || (wVisited[column+1][row] > 0)) &&
				 (row==height-1 || (wVisited[column][row+1] > 0))
			   );
	}
	function findStartingLocation2(){
	
		while ((wVisited[currentColumn][currentRow]==0) || isLocked(currentColumn,currentRow)){
			var newLocationIndex = random(visitedCellList.length-1);
			currentColumn = visitedCellList[newLocationIndex].column;
			currentRow    = visitedCellList[newLocationIndex].row;
		}
	};
        
The redesign of the start position selection and movement logic allowed the generation of the entire maze to be reduced into a single loop, with the outermost loop merely checking the current cell count, and the inner loop moving the visitor so long as he isn't deadlocked along the current path. When the cell count is reached, indicating all cells have been visited, the maze is complete. For each iteration, we create a list of valid possible directions (validDirections[]), then pick a random number from among that list:
	function constructMazePaths(){
	    
		// starting at the currentColumn,currentRow, snake up/down/left/right until we 
		// run out of cells or can't go anywhere else; then find a new starting position
		// and continue
		
		while (cellCount<=width*height){
		
		    var validDirections = [];
			findStartingLocation2();

			var onCurrentPath = true;
			while (onCurrentPath){

				validDirections.length=0;

				//var onCurrentPath = (canMoveUp() || canMoveRight() || canMoveDown() || canMoveLeft());
				// Direction are mapped 1:Up, 2:Right,3:Down, 4:Left
				// Push valid current possible directions into an array, then
				// randomly select from among the valid possible values. 
				if (canMoveUp()){
					validDirections.push(1);
				}
				
				if (canMoveRight()){
					validDirections.push(2);
				}
				
				if (canMoveDown()){
					validDirections.push(3);
				}
				
				if (canMoveLeft()){
					validDirections.push(4);
				}
				
				//if validDirections is length=0, no valid values, we're locked.
				onCurrentPath = (validDirections.length>0);
				
				if (onCurrentPath){
					direction = validDirections[random(validDirections.length)-1]; 
					switch(direction){
						case 1: moveUp();
								break;
						case 2: moveRight();
								break;
						case 3: moveDown();
								break;
						case 4: moveLeft();
								break;
					}
				}
				onCurrentPath = onCurrentPath && (cellCount<=width*height);
			}
		}
	}

Rendering the maze is a simple matter of iterating through the wall matrix one row at a time, interrogating each cell to determine if an east and/or a south wall must be displayed. I preserved the original program logic for this, reserving vertical walls in one physical line, and horizontal walls on the next. This could could be condensed and allow for larger display mazes if it were reworked to use an underscore and a vertical bar (pipe) for the south and east walls.
	function renderMaze(){
		for (var j=0; j< height; j++){
			console.write("I");
			for (var i=0; i< width; i++){
				if (vWalls[i][j]<2){
					console.write("  I");
				} else {
				    console.write("   ");
				}
			}
			console.writeLine("");
			for (var i=0; i< width; i++){
				if ((vWalls[i][j]==0) || (vWalls[i][j]==2)) {
					console.write(":--");
				} else {
					console.write(":  ");
				}
			}
			console.writeLine(".");
		}
	}

The input for Amazing wants two comma-separated integers, and this is where I discovered a bug in our ConsoleWindow class. The keydown handler did not properly map the keycode for a comma, returning 188 and causing a "1/4" symbol to be displayed. A tweak to map this code to 44, for an actual comma, fixed this issue. The maze dimensions are the only input to Amazing, hence our CommandDispatcher is fairly trivial. We also provide a simple command validator to ensure numeric input, and initialize the visitation and cell wall arrays. I also added a simple utility function to provide a little syntactic sugar to random number generation:
    function CommandDispatcher(command){
		setTimeout(ref.BuildMaze(command),250);
	}
	
	this.GetDimensions = function(){
	                            console.writeLine("WHAT ARE YOUR WIDTH AND LENGTH?");
								console.readLine(CommandDispatcher);
						};


	function validArguments(param){
		
		var parms = param.split(",");
		
		if (parms.length != 2){
			return false;
		}
		
		width = parseInt(parms[0]);
		height = parseInt(parms[1]);
		
		return (!isNaN(width) || !isNaN(height))
	}

	function initArrays(){
	    for (i=0; i< width; i++){
			wVisited[i]=[];
			vWalls[i]=[];
		    for (j=0; j< height; j++){
			    wVisited[i][j]=0;
				vWalls[i][j]=0;
			}
		}
	};		
	
    function random(number){
        return Math.floor(Math.random()*number)+1;
    };

All that's left now is to wrap up these helper methods into the core routine that will take the input, carve the paths, and render the maze, and that's in the BuildMaze function off our Play() method:
	this.BuildMaze = function(command){
						if (!validArguments(command)){
						    console.writeLine("MEANINGLESS DIMENSIONS. TRY AGAIN.");
							ref.GetDimensions();
							return;
						}
						
						// valid width, height in (surprise) width, height;
						initArrays();
						
						startOpening = random(width);
						QControl=0;
						ZControl=0;
						
						// maps lines 165-190 for top line of maze
						for (var tc=1; tc<=width; tc++){
							if (tc!=startOpening)
								console.write(".--");
							else
								console.write(".  ");
						}
						console.writeLine(".");
						
						wVisited[startOpening-1][0] = cellCount;
						cellCount++;
						
						currentColumn=startOpening-1;
						currentRow=0;
						
						constructMazePaths();
						renderMaze();
						// now we start snaking through a path
					};


This was a fun and rather challenging port, because it was less about the language differences than it was decoding a design strategy buried in a "maze" of BASIC code. Here's hoping you enjoyed reading through and playing with the project as much as I did!!
Grab the code and the updated ConsoleWindow from the SourceForge repository, and please feel free to leave comments below.
Until next week!

Monday, June 15, 2015

Converting Game #1: AceyDucey

As promised, this is the first project in converting "101 BASIC Computer Games" to Javascript! And please take this as my invitation to join the VirtualDeveloper blog, jump into the conversion party by tossing in some comments below, or check out the code yourself in the SourceForge reposistory. Above all, have fun!

While this conversion will deal with programming and technical topics, the intent here is to have a good time, learn a little, but not necessarily generate the most perfect Javascript code in history. If you're willing to go along, please read on!!!

We start with the first game in the book, "Acey Ducey," with the original BASIC source here. Take note of that archive, because we'll be referencing it in frequently in this project.  And, for those who are just too eager to see the result, feel free to jump to the SourceForge AceyDucey archive for a ready-to-run set of files.

This first effort is a monument to things not turning out as expected. When I first looked at "Acey Ducey," I saw a trivial BASIC program that I thought would roll to Javascript in practically no time.

I was wrong.

AceyDucey is about as simple a card game as they come; pick two cards, then place a bet on whether the next card will fall within the two just dealt. The BASIC source for this game is a little over 100 lines; my Javascript version is 175 lines, not even counting the separate code for the console display window. And the differences in how the programs operate just point out different a language Javascript is from other contemporary languages, like C#. Heck, a console executable C# port of AceyDucey would have been trivial!

The Big Lesson


From a lesson I learned writing the ConsoleWindow, you can't allow Javascript to block awaiting input. That lesson extends to the games themselves. Snagging console input has to be done by a custom event handler, but no handler can fire until Javascript's single-thread-of-control exits any currently executing method. As a result, the simple loops in the original BASIC code to control betting and replay logic, all driven by user inputs, just don't map one-for-one to Javascript. Yes, I could have added an HTML input box and a button on the form to force the issue, but doing so wouldn't have kept the "spirit of the console" I'm trying to retain from these old games.

This decision has an important consequence. Because we can't simply port sequential lines of code, the design of the ported game necessarily changes somewhat. We end up discovering that the best way to conceptualize or model the Javascript version of AceyDucey or any other input-dependent program is as a state machine. A state machine is just a way of modeling a system that moves from different configurations or "states", with the machine "moving" from state-to-state allowing the "edges" to represent the transitions between the states. For our model, our games move to different states, with the goal of identifying states requiring user input. We then use that state information to tie our input handler to a function that knows how to handle each possible input state. Simple, eh? Yeah, it really is - a lot simpler in practice than it is in words :)

This modeling concept allows us to block off "chunks" of program behavior into methods that roughly reflect the "edges" of our game machine, moving to user input states. At those states, the program references the console's input via a callback method that, in turn, routes the input to another method within the program, continuing execution appropriately. This state model allows the "external" input handler to jump "back" into our program and keep running.

The game itself


All this discourse about input handling hasn't even touched on the game itself, which borders on the trivial - and allows us even to visit a bit of object orientation along the way. So let's dive in.

Most of the original BASIC code deals with nothing more than printing out the value of the current card, or one of JACK, QUEEN, or KING for face cards (values greater than 11). In fact, AceyDucey repeats card generation and display logic three times; twice in lines 270-650, and again in lines 730-900. Note, too, that AceyDucey doesn't even draw from a "real" deck of 52 cards; each one chosen is a simple random number each time. The sequence is simple:

  1. Pick a random number from 2-14
  2. If that value is less than 11, print the raw value
  3. For values 11, 12, 13, and 14, print "JACK," "QUEEN," "KING," or "ACE", respectively.
This is *begging* to be consolidated in to a single chunk of code, which I've done in the "Card()" function nested within the AceyDucey() constructor. The Card() "object" needs to define a random value for itself, then provide a method that can return a string to display the card's proper value using the logic above. In Javascript, Math.random() does the job of BASIC's old RND() method, returning a decimal number less than one. We multiply it by 13 to get a range of 0 to 12, and we add one to it to get 1-13. And we'll map our card numbers from there - but in my version, we're making "Ace" low rather than high. We then define a Text() method to get a text rendering of the card's name, and a CardValue() method to return the card's raw numeric value to simplify comparisons in the game.

function AceyDucey(gameConsole)
{
    // other code snipped for now
    function Card(){
        var value = Math.floor((Math.random()*13)+1);
        this.Text = function(){ 
                       if (value==1){
                           return "Ace";
                       } else if( value>10 ){
                           switch (value){
                               case 11: return "Jack";
                                        break;
                               case 12: return "Queen";
                                        break;
                               case 13: return "King";
                                        break;
                           }
                       } else {
                           return value;
                       }
                   };
              }
       this.CardValue = function() { return value;};
    }

States of Indecision


We talked earlier about modeling the game as a series of states, and dividing up code accordingly. The easiest chunk is the instruction display in lines 10-80, which we simply plop in a ShowInstructions() method. The player has a betting stake we initialize to $100. We then lay the foundation for playing the game, which amounts to generating two cards, displaying them (SetupRound), getting the user's bet (GetPlayerBet), the determining a win or a loss (PlayBet), and checking for the user going broke after losing (BASIC lines 900-1040).

Because we must implement a callback to receive a user's input, but also must know how to route that input in that callback, we define states in which the program has to handle user input. When AceyDucey needs a user's bet, we define that to be the WAITING_FOR_BET state; when we are confirming whether the user wants to restart the game, we're in a WAITING_FOR_REPLAY state. We track the game's state in a variable "gameState," and define handlers for both of those states (PlayBet() and ConfirmRestart()), tying them together in the CommandDispatcher() callback:

function CommandDispatcher(command){
     
     if (gameState==ref.States.WAITING_FOR_BET){
  setTimeout(ref.PlayBet(command),250);
  return;
     }
  
     if (gameState==ref.States.WAITING_FOR_REPLAY){
  setTimeout(ref.ConfirmRestart(command),250);
  return;
     }
 };
When the console's Readline method is fired, we send a reference to CommandDispatcher to receive the result. The Dispatcher then checks the program state to know which method should be fired to handle the specific command; because PlayBet or ConfirmRestart may, in turn, need more input, we must ensure they are not fired until CommandDispatcher() terminates; hence, we use Javascript's "setTimeout()" facility to defer execution of the handlers until an arbitrary 250ms after CommandDispatcher ends and freeing up the Javascript execution thread. This neatly ties together the need for input with the handlers needed to interpret it.

Input handling - Looking at GetPlayerBet()


After a round is set up by displaying two cards, we have to get the user's bet. This is handled in the GetPlayerBet() method, which displays a message, sets the WAITING_FOR_BET state, and fires the readLine method with the CommandDispatcher callback:

this.GetPlayerBet = function() {
    console.write("Enter your bet (Q to quit): ");
    gameState = ref.States.WAITING_FOR_BET;
    console.readLine(CommandDispatcher);
   };

The Game is Up


Input handling is the most esoteric part of this port; the rest of AceyDucey is fairly simple. We wrap a Play() method around the SetupInstructions(), SetupRound(), and GetPlayerBet() methods for the initial run. The only remaining logic is to compare the two cards generated in SetupRound(); that comparison is done in the IsBetween() method and represents a bit of logic departure from the BASIC source. In the original AceyDucey, the program logic forces the first card to be the lower-valued card in lines 270-330, storing the card values in variables "A" and "B." The "payoff" card value in "C" is then compared in lines 910-930. I chose not to force the lower-first-card forcing logic, just wrapping the comparison into a single IsBetween() method that takes three values, and determines if the first value is between the other two.

A perfectly reasonable if not preferable alternate design for IsBetween() would be a Card-object specific method, Compare(), accepting a Card object as an argument, and returning -1, 0, or 1 to indicate which card is lower or higher.

Validation


The original AceyDucey performs several input validation checks. One ensures the player doesn't bet more than he has. Another checks whether the user wants to start the game again if they go bust. I added a third validation to allow the user a quit option the original didn't support - the option to "quit while you're ahead" by typing "Q" for a bet amount. All the possible bet values are handled in the IsValidBet() method:

this.IsValidBet= function(text){
     if (text=="Q"){
   quitting=true;
   return true;
  }
     var amount = parseInt(text);
   
  if (isNaN(amount)){
      console.writeLine("You have to enter a number to bet, dude...");
   return false;
  }
  
  if (amount > playerStake){
      console.writeLine("You only have $" + playerStake +" to bet, dude...");
   return false;
  }
  
  if (amount < 0) {
   console.writeLine("Cute. You can't bet less than $0.");
   return false;
  }
  
  return true;
 };
If the user chooses to end the game, betting "Q", we validate that input an return immediately; otherwise, we take the integer value of the string to get the bet value via Javascript's parseInt() method. If the user hasn't typed in a numeric value, parseInt assigns the special "NaN" value as the result; we test for this before any more numeric comparisons are made. Once a valid number has been verified, we compare that value to the current player's stake in the "playerStake" variable and invaliding the bet accordingly. Surviving those checks validates the input and returns true back to the caller.

Playing the Bet


Once the user's bet is validated, we play the game by selecting a new "payoff" card, and comparing its value to the first two dealt and stored in the card1 and card2 variables. We adjust the player's stake by virtue of the win or the loss, and start the process by calling Replay() to repeat the gameplay cycle:

this.PlayBet   = function(bet){
                     if (ref.IsValidBet(bet)){
                         if (quitting){
                             ref.GameEnd("You're quitting this game.");
                             return;
                         }
         
                         bet=parseInt(bet);
                         payoffCard = new Card();
                         console.writeLine("NEXT CARD IS: " + payoffCard.Text());
                         if (ref.IsBetween(payoffCard.CardValue(),card1.CardValue(),card2.CardValue()))
                         {
                             console.writeLine("WINNER!");
                             playerStake += bet;
                         }
                         else
                         {
                             console.writeLine("SORRY, YOU LOSE!");
                             playerStake -= bet;
                         }
       
                         this.Replay();
                 } else {
                     this.GetPlayerBet();
                 }
          };

That's a wrap!

With a few other methods that are self-explanatory, that wraps up this lengthy discussion over this simple BASIC game. This implementation is by no means perfect; a "pure" implementation would probably convert the card generation to occur from an actual deck of 52 cards, and the comparison could, as noted, be moved to a method off the Card object. We leave those as refinements for the reader. Here's a screen shot of Javascript AceyDucey in action:

Until next week!! -David

Wednesday, June 10, 2015

BASIC: A Primer Before Porting

As part of this series on converting "101 BASIC Computer Games" to Javascript, I realized that there are more than a few younger folks out there who might never have seen classic BASIC. So here's a primer that will get you started as we dive into porting these games into Javascript. Mind you, this is no tutorial or deep-dive; just a skim over some basic structures and language features. Keep in mind, too, that everyone's flavor of BASIC in the microcomputer era had slight implementation differences that might make porting any one game just a bit problematic.

BASIC is an acronym for "Beginner's All-Purpose Symbolic Instruction Code," and had it's origins back in the late 60's as a "starting" computer language. It had a limited instruction set, limited control structures, and limited extensibility - but it worked.

Each line of a BASIC program consisted of a line number, followed by one or more statements. Multiple statements could be combined on a single line by separating them with colons, eg

10 PRINT "HELLO":PRINT "GOODBYE"
20 IF A=1 THEN PRINT "NO"

The BASIC interpreter executed statements in increasing line number order; it was convention to space the numbers 10 apart to allow for additional statements between lines as a program evolved.

The nickel tour


BASIC's variables, all of which were global, could be string, integer, and single/double precision floating point; but no notion of custom types or structures were available - only arrays, which could be multidimensional.  For control structures, BASIC offered FOR-NEXT, IF-THEN, WHILE-WEND, GOTO, and GOSUB. BASIC's output was the PRINT statement, followed by a string of text or a combination of text and variables. For data entry, there was the INPUT statement; execution would stop at the INPUT statement and await the user to enter data, which was assigned to the variable noted in the statement. Lastly, comments could be added to the text via the REM statement, or the single-quote (apostrophe):

5  REM THIS IS MY PROGRAM REMARK
10 DIM A$
15 DIM B(5,6) ' DECLARE A 5 by 6 array
20 INPUT "ENTER YOUR NAME: "; A$
30 PRINT "YOUR NAME IS ";A$

Variables were declared by the DIM statement, but not all dialects required variables to be defined. Some if not all dialects honored the "$" as a type declaration character for string variables. The DEFINT, DEFSTR, DEFDBL, and DEFSNG statement defined variables of integer, string, double, and single-precision types, respectively, without any type declaration characters:

10 DIM A$:DEFINT A
20 A$="HELLO":A=26

IF-THEN-ELSE


The IF-THEN-ELSE structure set up a simple test-and-branch mechanism; if the IF test was true, branch to the linenumber specified in the THEN statement; otherwise, control continued with the next highest-numbered line in the source. An ELSE clause could specify a linenumber as well.

10 IF A<>1 THEN 40
20 PRINT "A IS 1"
30 GOTO 50
40 PRINT "A IS NOT 1"
50 END

The "THEN" and "ELSE" portions of the IF could specify a statement rather than a line number, as shown in the PRINT statement above.

WHILE-WEND


This simple WHILE loop structure establishes a test condition at the top of a loop, and executes all subsequent statements thereafter until a WEND statement is found:

10 DIM A:A=1
20 WHILE A<=10
30 PRINT "A STILL LESS THAN 10"
40 A=A+1
50 WEND
60 END

FOR-NEXT


This simple, traditional control structure established a loop that would take a variable from a starting value to an ending value, bumping the variable by 1 unless a different value were provided via the optional STEP keyword. The scope of the loop extended from the base FOR statement to the NEXT keyword:

10 DIM A
20 FOR A = 2 to 50 STEP 2
30 PRINT "EVEN NUMBER: "; A
40 NEXT A

GOTO-GOSUB


GOTO and GOSUB are first cousins. GOTO linenumber transfers immediate program control to the line number specified in the statement. GOSUB linenumber does the same thing, with a difference; it remembers the point at which it was invoked, and will return to that point when a RETURN statement is encountered. This gave BASIC at least the illusion of subroutines. Each also had a "computed" counterpart, which would accept an integer value and branch to the line number corresponding to that value's ordinal position in a list of line numbers:

100 ON X GOTO 200,210,220,230

In this example, X=1 would branch to line 200; X=2 would branch to 210, and so on.

Function Definitions


Some implementations of BASIC allowed for function definitions via the DEF FNx statement. This example declares a function "C" that accepts a single argument, and performs a Farenheit-to-Celsius conversion. The function is called in the next line:

10 DEF FNC(X) = (x-32)*5/9

20 PRINT "32 DEGREES F = " ; FNC(32); " CELSIUS."

DATA statements


One unique feature of BASIC was the ability to store what amounted to raw data within the program text. DATA statements allowed for a comma-delimited set of arbitrary values as program code, which could then be loaded into program variables by READ statements:

10 DIM X,Y(6)
20 FOR X=1 to 6
30 READ Y(X)
40 NEXT X
..
1000 DATA 125,26,159,1000,2,3142


That's It!!


That's the quick-hitter tour of BASIC's most important features. We didn't go into all the math functions and operators, as they're all fairly obvious from other languages, and any special attention needed as the conversions go along will be duly noted. Be ready to go forth and BASIC :)

Saturday, June 6, 2015

Nostalgia, Games, and porting BASIC to Javascript - Part One!

As my daughter finished her junior year in high school, and my son his first in college, I found myself in a nostalgia wave this last month. I thought about how things have changed in my 50 years, how quickly time passes, and amid that flow was a reminder of how much my own profession has changed. The world of computing and programming isn't what it was 30 years ago.

As the venerable Radio Shack chain came to a rather ignominous end this year, I came across more than a few ads that took me back to my teenage years when I dove into "computing" with a klunky but functional TRS-80 computer - a "Level I" version with an adapted black-and-white TV as a monitor, a 64x16 black and white upper-case-only character cell display, 4K of RAM, a minimalist version of the BASIC programming language, and a cassette tape interface for recording programs.

I typed in a few programs, started learning BASIC, and realized almost instantaneously that I was hooked.

Soon realizing that Level I version wasn't good for much in practical terms, a bit of wheedling with my mom persuaded her to "upgrade" me for my birthday to the "Level II" model, with 16K of RAM and a better version of BASIC. I started writing programs that actually did practical things, learning about sorting, organizing my programs, and getting first-hand frustrations with the vagaries of the cassette tape interface. There were few things more frustrating than spending an hour or more typing in a program, and CSAVE-ing it to tape, only to discover the save didn't "take" when you tried to CLOAD the program back later. And that happened more than once.

As I persued the bookshelves at the old B. Dalton bookstore in the mall near my home, I came across a tantalizing book title: "101 BASIC Computer Games" by Dr. David Ahl of "Creative Computing" magazine. I already knew about "Creative Computing." It was one of the first big, important monthly computing magazines of the era. I had saved enough for a year's subscription and found it supremely fascinating. I even sent them an unsolicited text about artificial intelligence (which they politely rejected). And I desperately wished I'd had enough money to buy the Heathkit dot-matrix line printer kit described in an article in one issue - but I couldn't quite wheedle that out of my parents. But this "101 BASIC Computer Games" lit me up like a Christmas tree.

I thumbed through the book and found page after page of BASIC source listings for games from Football, Tennis, Acey-Deucy, to banners and calendars, to the penultimate - a game called "Super Star Trek" - all ported from numerous divers mainframe environments into a relatively neutral version of BASIC similar to the one that had become pervasive on the first pre-PC-clone generation of personal computers. All you needed was good eyesight and the patience to type in the source into whatever personal computer you might have. If your dialect of BASIC was a little different, you were on your own to get the porting right.

I dove in like a kid in the proverbial candy store.

Some 35 years later, I still have that big, yellow book, and fortunately it's still in very good condition. The wave of nostalgia combined with the discovery of that great old book led me to an idea - how much fun it might be to update those programs into a contemporary environment in a contemporary language. Not because there's some great demand for it, but just for the fun and homage of what that book meant to the fledgling home computing industry, and the joyous memories they held for me those years ago.

I first started to port some of the games to C#, but found that some folks had already started an effort to port "Super Star Trek" to C back in 2008, so there was little point in retreading old ground. But some posts I saw on that project host page offered an intriguing idea - "What about porting this to Javascript!?"

What about that idea, indeed!!

In much the same vein as erstwhile blogger Julie Powell undertook to cook her way through Julia Child's "Mastering the Art of French Cooking," I'm going to try and port most - if not all - those great "101 BASIC Computer Games" into Javascript. Now, let's be honest - is there some huge demand for this? Surely not. I doubt there is much practical about such an undertaking aside from the mental exercise and novelty. But I also suspect a few other long-timers such as myself might find that novelty worth sharing, and that sharing starts here. I'll aim to post about a game a week, maybe more, maybe less, depending on how much spare time I have and how long any one game takes to port.

I've startaed up a SourceForge site for these proejcts, just in case someone else with a similar mania finds these efforts worth exploring. Mind you, these will be fairly straightforward conversions - plain Javascript, no jQuery - wherever possible. And I won't promise to write the best Javascript out there - I'm sure there are plenty of sharp Javascript coders who can tell me how I can write stuff "better," and that only helps me. This project is about my own odd kind of geeky fun.

We'll kick off this effort with a bit of a "tease" of what's ahead - a project not even in the "101 BASIC Computer Games" book, but one we can't proceed without.

A Minor Requirement


The first thing I realized when I opted to embark on this minor, strange project was the fact that these great, old Ahl games depended on on simple thing: a character-based console environment. For those too young to know exactly what that means, let's embark on a brief history lesson.

Back in the days before LCD and LED high-definition screens and gigabytes of memory, computers rendered their output on simple video displays that rendered only fixed-width symbols and alphanumeric characters along with a few limited block-style graphics characters within a fixed grid, often 80 columns wide by 24 lines deep. While advanced for their time, these video consoles were little more than next-generation versions of hardcopy paper teletype terminals that did one thing: print lines of text on paper.

Teletype consoles and their "advanced" video console descendants offered little in the way of variety. Line widths could typically be varied from 40 to 132 columns, and video consoles could sometimes be adjusted to display varying numbers of lines. Most consoles were white characters on a black background; later versions rendered text in green as it was deemed easier on the eyes for extended viewing.

Early "personal" computers inherited this character-cell orientation, and the migration of BASIC from the mainframe platforms of that era formed the basis for Ahl's classic compilation. Despite the ubiquity of BASIC, the subtle differences in implementation among the multiple platforms - diverse among the mainframes, then diverse again among personal computers - made the migration of these great games into a generally consumable format all the more amazing.

With the history lesson in hand, we now know that before we can attempt to migrate these games from text-oriented BASIC into Javascript in an HTML world, we need a console output device on which to render them; hence, the first project: A Javascript text ConsoleWindow.

Project Zero - A Javascript Text Console

Attacking the design

A text console needs specific visual properties. It needs to be rendered in a fixed-width (monospaced) font; the particular font face isn't terribly important. We want to declare a console of *exactly* the desired with and height. We'll settle for a default of white-on-black for text, and think of color support perhaps in a second version (hint, hint). When printing to the display, we need to fill from top to bottom, and scroll the top out as the bottom fills up. Virtually all of these issues can be controlled via HTML styling. We control the console's content via the innerHTML property of the element we choose to represent the console.

We'll generate the console as a DIV element, and create an inline style that defines most of these properties. For the style, we specify a "font-family" value of "monospace", and let the browser pick the one it wants. If text is written to the end of a line, it should wrap to the next line; hence, we set a "word-wrap" value of "break-word". We want no scrollbars or resizing on the console, so we'll set "overflow: hidden", and get our white-on-black text with "color: white" and "background-color: black". One thing we can't do statically is establish the size of the DIV, because we won't know that until runtime.

To avoid requiring the user to specify such mundane things as explicit point, font, and pixel sizes, we'll spin up a simple enumeration that defines five possible "canned" console sizes, with fonts ranging from 10 to 18 pixels in two-pixel increments.

Starting the code

We'll make the Console a simple Javascript object, initialized with its required columns, rows, and size. We'll strike an internal reference for the object as we'll implement private and public methods to manage our console, as well as our size enumeration:


function Console(columns, rows, size){

   var ref = this;
   this.columns=columns;
   this.rows=rows;

   this.SizeInfo = {
Tiny  : { value: 1, cellHeight: "10px", cellWidth: "6px", fontSize: "10px"},
Small : { value: 2, cellHeight: "12px", cellWidth: "8px", fontSize: "12px"},
Normal: { value: 3, cellHeight: "14px", cellWidth: "10px", fontSize: "14px"},
Big   : { value: 4, cellHeight: "16px", cellWidth: "12px", fontSize: "16px"},
Large : { value: 5, cellHeight: "18px", cellWidth: "14px", fontSize: "18px"}
};

}


Sizing the console


How do we create a console of exactly the size required? Measuring browser text can be tricky because not every font is available in every system, and most situations involve characters of varying widths. For our purposes, a console with a monospaced font means if we can measure one character, regardless of the *actual* font, we know the size of *every* character, and thus should be able to size our div precisely in both dimensions.

To do this, we'll create a SPAN with a font-family style of "monospace", and the font-size defined by the 'size' parameter to the constructor. We fill the innerHTML property with an aribtrary character, and once the SPAN is added to the document body and rendered, it will have a bounding rectangle we can query for height and width via getBoundingClientRect(), then remove from the document. Moreover,if we do this in the constructor, the "rendering" will be in memory only, never to be seen by the user.

var testSpan = document.createElement("SPAN");
testSpan.style.fontFamily="monospace";
testSpan.style.fontSize=this.size.fontSize;
testSpan.innerHTML='X';
document.body.appendChild(testSpan);
cellRect = testSpan.getBoundingClientRect();
document.body.removeChild(testSpan);
  
// Extrapolate the size of a single monospaced letter to a full console
var consoleHeight = cellRect.height * rows + 'px';
var consoleWidth  = cellRect.width  * columns + 'px'; 

With all parameters for the console at hand, we can now construct the STYLE attribute programmatically, then attach it to the head of the document:

var consoleCSS = document.createElement("STYLE");
consoleCSS.type = "text/css";
consoleCSS.innerHTML = '.console { overflow: hidden; background-color: black; word-wrap: break-word; ' +
' color: white; font-family: monospace; font-size: ' + this.size.fontSize + '; '+ 
    ' display: inline-block; min-height: ' + consoleHeight + '; max-height: ' + consoleHeight + '; ' +
' min-width: ' + consoleWidth +'; max-width: ' + consoleWidth +'; }; ';

// Append the console style sheet.
document.getElementsByTagName("head")[0].appendChild(consoleCSS);

Now, all that matters is adding the console itself, with an appropriate class name to match our style:

var consoleDiv = document.createElement("DIV");
consoleDiv.className='console';
document.body.appendChild(consoleDiv);

The baseline console is now in place, but with no methods to write to it. Our design is focused on simply appending text to the DIV via the innerHTML property, and exposing two 'write' methods; one that appends a "carriage return/line feed," and one that doesn't. We can implement this by simply appending an HTML tag to force a break where we want the CR/LF to appear. Also, we have to ensure we write actual spaces to our console, and to do that, we have to replace any literal spaces in the input with the HTML no-break-space encoding. Lastly, we must make sure that whatever we write forces the top to scroll out if the screen is full, which we can accomplish by setting the DIV's scrollTop property to its scrollHeight after changing the innerHTML. That gives us our two write methods:

// Write with no "CR/LF"
this.write = function(text){
consoleDiv.innerHTML += text.replace(" "," ");
consoleDiv.scrollTop = this.theAltConsole.scrollHeight;
}

// Write with CR/LF as a break tag - br
this.writeLine = function(text){
  this.write(text+ "") ;
}


A 'clear' method simply amounts to erasing the content of the innerHTML property:

this.clear = function(){
consoleDiv.innerHTML =''; //blanks;
consoleDiv.scrollTop = consoleDiv.scrollHeight;
    }

The next requirement is the trickiest: Data input. At various times, all the games will need to receive input from the user. In original BASIC, an "INPUT" statement allowed a program to display a message with a trailing cursor, and then halt until the user typed something and hit [ENTER]. BASIC would transfer what the user entered into a variable specified at the end of the INPUT statement.

Simply put, there's just no way of duplicating this in Javascript. Most fundamentally, the very notion of trying to force Javascript to "block" on input sends shudders down our backsides, because neither browsers nor the Javascript engine itself just were designed to operate that way. Block the Javascript thread, and you'll likely end up locking your browser.

All this means we have to adapt our model to fit Javascript's world, and that means we need to trap keystrokes with an event handler.

For this purpose, our event handler must trap keystrokes, accumulate them into a string, and when the input is complete, return that string to the caller. The best place to capture keystrokes from the console is by placing our event handler with the 'onkeydown' event of the document. We also need to capture only "printable" or "displayable" characters. Lastly, once the user is done, we've got to send that data back to the caller; however, our console won't know anything about it's caller, so the caller will have to send that mechanism to us. That's known as a *callback function*.

We'll keep our keystroke handler simple. With two exeptions, we're going to ignore any non-displayable characters - those having a keyCode lower than 32 - a space. In general, as each displayable character is pressed, we'll add it to an internal string, and continue to do so until the user hits the [ENTER] key, which becomes one of our non-displayable exceptions (keyCode 13). We also must allow for the user to [BACKSPACE] over mistakes (keyCode 8), which is our other exception. Here's the entire input routine, and we'll dissect things as we go along.

this.readLine = function(recipientCallback){
  ref.inputString="";
ref.inputLocked=true;
ref.write("_");
document.body.onkeydown= function(){
      var key = event.keyCode || event.charCode;
if (key < 32){
         if (key==8){ //backspace
             event.preventDefault();
if (ref.inputString.length>0){
                ref.inputString = ref.inputString.substring(0,ref.inputString.length-1);
                inputBackspace();
}
         } else if (key==13) { //enter, terminate
              ref.inputLocked=false;
event.preventDefault();
document.body.onkeydown=null;
consoleDiv.innerHTML = consoleDiv.innerHTML.substring(0,consoleDiv.innerHTML.length-1);
if(recipientCallback){
                 recipientCallback(ref.inputString);
}
ref.writeLine("");
         }
} else {
         event.preventDefault();
         var newChar = String.fromCharCode(key);
         consoleDiv.innerHTML =              consoleDiv.innerHTML.substring(0,consoleDiv.innerHTML.length-1);
         ref.write(newChar + "_");
         ref.inputString += newChar;
}
}
}

We initialize the input string to blank when we start the function, and render the "cursor" to show the user we're awaiting input. We then initialize the key handler as an anonymous function, identifying the character pressed by inspecting the keyCode or charCode properties of the event object for each firing of the onkeydown event.

Appending displayable keystrokes - keyCode >=32 - is simple: We simply call our 'write' method to display it, and concatenate the new character to our input string. The [ENTER] key - keyCode 13 - signals the end of input, where we disconnect the handler by setting onkeydown=null, remove the cursor, and send the string back to the caller via its callback. The backspace key, however, is trickier.

Backspacing requires several steps. We have to truncate the last character of the input from the displayed innerHTML *and* our internal input string. We must be aware that in innerHTML, hard spaces will be stored as no-break spaces - nbsp; - so just deleting the last character on a backspace won't work. We *don't* have to make this check for the internal input string, because we add the uncoded version. With all this in mind, we wrap backspace handling into a private function:

function inputBackspace (){
     // copy innerHTML locally for simplicity
   var txt = consoleDiv.innerHTML;
   txt = txt.substring(0,txt.length-1); // trim the cursor

     // check for backspacing an encoded space
   if (txt.substring(txt.length-6,txt.length)==" "){
       txt = txt.substring(0,txt.length-6);
   } else {
txt = txt.substring(0,txt.length-1);
   }
     // replace the cursor
   txt += "_";
   consoleDiv.innerHTML = txt;
}

Put it all together, and that should represent the entirety of our Javascript ConsoleWindow. We can test it by adding some buttons to a regular page that will instantiate a window, and exercise the methods we've written. I've posted the console as a standalone .js file and as a single HTML file at this SourceForge repository with the code included that also has some test buttons that will let you exercise the console, firing up an 80x24 window in a default "size 3" variety. Here's a sample of the HTML file in action on IE11:



Play with the code, look at it, tweak it, let me know how you've enhanced it. While you do that, here are some obvious limitations:
  1. Long game sessions that never clear the console can result in large content blocks in the console's innerHTML. That might result in memory issues that affect performance.
  2. This console isn't *addressable*. We've mimicked a teletype/green-screen with straight line dumps that scroll up and off the screen. An *addressable* screen would allow the specification of a particular x-y cell coordinate at which to print text; this innerHTML implementation makes that essentially impossible. A later version, which would entail a significant rewrite, could include this important and useful feature (hint, hint? A keen observer might note this was called console_v1.html")
This will be the baseline ConsoleWindow used for all of these BASIC game ports. And, we'll give this one a shot with our very first game...next week.

With all great appreciation to the great work of Dr. Ahl and the old "Creative Computing" team.

Enjoy!

-David