Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Wednesday, July 1, 2015

Game #3 - "ANIMAL" - Fool the programmer?

For our third foray into converting the games of "101 BASIC Computer Games" to Javascript, we take quite a departure from last week's AMAZING maze generator into the world of artificial intelligence. Be sure to track the original BASIC source at 101 BASIC Computer Games on Atari Archives and grab a ready-to-run set of source files including the host console window at our project at our Sourceforge archive.

So this is a halcyon effort at AI?

Kinda.

"ANIMAL" is a simple guessing game wherein the user thinks of an animal, and the *computer* tries to guess it. When the computer misses, it asks questions that differentiate its best guess from the right answer, and in so doing tries to build an "intelligence database" of sorts about the animals in that session. The result is a mixed bag.

Granted, BASIC is no panacea for advanced artificial intelligence, and ANIMAL is about 40 years old, so the effort is certainly admirable. But the reality is that ANIMAL is fairly rudimentary, and was in all honesty not my favorite game to convert. In effect, ANIMAL's AI amounts to creating chains of questions and answers that refine its ability to guess the animal the player has in mind. The problem with the chain approach is that chain quickly resembles a tree, and the same animal could be stored at the end of more than one "guess chain."

But, for what it is, ANIMAL is an intriguing effort at making intelligent guesses in an interactive format, so let's plow into the Javascript conversion.

The Conversion


The original ANIMAL game stores its intelligence in a simple string array, A$, that we rename 'questions' in our version. We strike the typical references to the "this" object and a host for the game console. Unlike last week's AMAZING, ANIMAL is very input-intensive, meaning we'll resort to our state-tracking mechanism for input callbacks - with LOTS of states! As noted, the intelligence is built by storing a chain of yes/no questions hopefully leading to an answer for each new animal.

ANIMAL's data structure is unique. Each string in the array is a different *kind* of data; it can be a question ("\Q" prefix), an answer ("\A" prefix , or a Y/N response ("\\Y" or "\\N" prefix) to a previous question. In the initial array, the first item contains the number of strings in the array, which we preserve even though javascript supports a direct length parameter for its arrays. The next item is a question, "DOES IT SWIM", followed by two possible answers and the array indices to follow based on the user's answer. The intelligence is a matter of building more and more strings with this structure, creating a moderately complicated chain of information used to create the illusion of ANIMAL's intelligence.

ANIMAL takes advantage of a unique feature of BASIC - the ability to encode arbitrary numeric or string data directly within a program. The "DATA" statement allows for a comma-delimited list of information that can be read at run time into program variables via the READ statement. Javascript has no such analog; but fortunately, the basic string array should serve as a great substitute. The questions array gets initialized with the number of elements, plus the first question it can ask, along with two possible answers.

function Animal(gameConsole){

     var ref= this;
  var console=gameConsole;
  var questions = [ "4","\\QDOES IT SWIM\\Y2\\N3\\","\\AFISH","\\ABIRD" ];
  var k=1;
  var state=0;
  var display = [];
  var state=0;
  var newAnimal;
  var newDifferentiatingQuestion;
  var newDifferentiatingAnswer;
  
  this.states = { GETTING_QUESTION_RESPONSE: 1,
                  GETTING_GUESS_RESPONSE: 2,
    GETTING_DIFFERENTIATING_QUESTION: 3,
    GETTING_DIFFERENTIATING_ANSWER: 4,
    GETTING_NEW_ANIMAL: 5,
    RESTARTING: 6};
      
  this.Intro = function(){
   console.writeLine("ANIMAL");
   console.writeLine("CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY");
   console.writeLine("Original program by Arthur Luehrmann, Nathan Teichholtz, and Steve North");
   console.writeLine("Javascript conversion by David Whitney");
   console.writeLine("");
   console.writeLine("PLAY 'GUESS THE ANIMAL'");
   console.writeLine("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.");
   console.writeLine("");
   };

}
Our Javascript version identifies six different states - getting a list, getting a new animal, getting a differentiating question and its answer against the current animal, and getting the response for the computer's guess, That state table then gets mapped into the CommandDispatcher:

  function CommandDispatcher(command){

  switch(state){
   case ref.states.RESTARTING: 
    setTimeout(ref.ListOrResponse(command),250);
    break;
   case ref.states.GETTING_QUESTION_RESPONSE: 
    setTimeout(ref.GetQuestionResponse(command),250);
       break;
   case ref.states.GETTING_GUESS_RESPONSE: 
    setTimeout(ref.GetGuessResponse(command),250);
       break;
   case ref.states.GETTING_DIFFERENTIATING_QUESTION: 
       newDifferentiatingQuestion = command;
    setTimeout(ref.GetGuessDifferentiator(command),250);
    break;
   case ref.states.GETTING_DIFFERENTIATING_ANSWER:
    newDifferentiatingAnswer = command;
    setTimeout(ref.GetGuessDifferentiatingAnswer(command),250);
    break;
   case ref.states.GETTING_NEW_ANIMAL:
       newAnimal = command;
    setTimeout(ref.GetNewAnimal(command),250);
    break;
  }
   
  };

The handlers for most of these dispatchers is fairly simple, amounting to displaying a message, changing state, and then invoking another read routine. Play simply displays the intro and starts the game process; StartMain() is the top of the loop the program will traverse. ListOrResponse simple handles the user's response to the "Are you thinking of an animal" query, affording the user a chance to see the animals the computer "knows" about. AskQuestion displays the question at the current question index, held in the "k" variable, and then getting the user's response:

  this.Play = function(){
   ref.Intro();
   ref.StartMain();
       };
 
  this.StartMain = function(){
    state = ref.states.RESTARTING;
    console.writeLine("ARE YOU THINKING OF AN ANIMAL?");
    console.readLine(CommandDispatcher);
   };
     
  this.ListOrResponse= function(response){
    if (response=="Y"){
        ref.AskQuestion();
    } else if (response=="LIST") {
         ref.ListKnownAnimals();
        ref.StartMain();
    }
        };
  
          
  this.AskQuestion = function(){
    var string = questions[k];
    display = string.split("\\");
    console.write(display[1].substring(1,display[1].length)+"? ");
    state = ref.states.GETTING_QUESTION_RESPONSE;
    console.readLine(CommandDispatcher);
                       };
        
        
  this.GetQuestionResponse = function(response){
            if (response!="Y" && response!="N"){
           ref.AskQuestion();
     } else {
         if (response==display[2].substring(0,1)){
         k = parseInt(display[2].substring(1,display[2].length));
     } else if (response==display[3].substring(0,1)){
         k = parseInt(display[3].substring(1,display[3].length));
               }
        }
            ref.ShowGuess();
    };

"GetQuestionResponse" deserves some attention. "k" holds the current index into the question "database." The answers to the questions, which are always either "Y" or "N", also hold a "pointer" to the *next* index in the question list that should be traversed based on the user's input. That means we must parse out the integer following the Y or the N in each possible answer.

"ShowGuess" displays the computer's current guess. If "ANIMAL" gets to a point in its chain that it is ready to display an answer, indicated by the current question being prefixed with a "\A", it displays the answer and asks the user if the computer is correct. The program changes state to receive a new animal from the user, and a question that differentiates the new animal from it's guess:

  this.ShowGuess = function() {
    if (questions[k].length==0){
        console.writeLine("I have no more questions. I give up.");
    } else if (questions[k].substring(0,2)=="\\Q"){
        ref.AskQuestion();
    } else {
        console.write("IS IT A " + questions[k].substring(2,questions[k].length) + "? ");
        state = ref.states.GETTING_GUESS_RESPONSE;
           console.readLine(CommandDispatcher);
    }
    };
 
  this.GetGuessResponse = function(response){
     if (response=="Y" || response=="y"){
         console.writeLine("Great! Try another!");
         ref.StartMain(); //placeholder to restart
     } else {
         console.writeLine("WHAT ANIMAL WERE YOU THINKING OF? ");
              state = ref.states.GETTING_NEW_ANIMAL;
         console.readLine(CommandDispatcher);
     }
     };
 
  this.GetGuessDifferentiator= function(){
           console.writeLine("FOR A/AN " + newAnimal +", THE ANSWER TO THIS QUESTION WOULD BE WHAT?");
      state = ref.states.GETTING_DIFFERENTIATING_ANSWER;
       console.readLine(CommandDispatcher);
      };

When the user supplies a question that differentiates the animal from the computer's guess, the computer also has to track how that question applies to the animal guessed; that's captured by GetGuessDifferentiatingAnswer. GetNewAnimal simply displays a prompt, changes state, and calls the input handler. AddNewAnimal provides the biggest part of the program's logic, representing the core of how the question[] array is structured with its pointer-oriented list structure, sometimes moving answers within the list, and adding the "intelligence" about the newest animal near the end. ONce done, it merely restarts the game via a call to StartMain(). Lastly, ListKNownAnimals simply lists the animals the program already knows about by traversing the question array, looking for strings prefixed with "\A" indicating an answer.

  this.AddNewAnimal= function(){
    var oppositeAnswer="Y";
    if (newDifferentiatingAnswer=="Y")
     oppositeAnswer="N";
       
    var z1=parseInt(questions[0]);
    questions[0]=new String(z1+2);
    questions[z1]=questions[k];
    questions[z1+1]="\\A" + newAnimal;
    questions[k] = "\\Q" + newDifferentiatingQuestion + 
                          "\\" +  newDifferentiatingAnswer + new String(z1+1) + 
            "\\" +  oppositeAnswer +  new String(z1) + "\\";
    ref.StartMain(); 
   };
               
  this.ListKnownAnimals = function(){
         var x=0;
         console.writeLine("ANIMALS I ALREADY KNOW:");
            for (var i=1; i < questions.length;i++){
      if (questions[i].substring(0,2)=="\\A"){
                  var current  = questions[i].split("\\");
                   console.write(current[1].substring(1,current[1].length) + " ");
      }
         }
         console.writeLine("");
    };

And so we've finished our THIRD game conversion from "101 BASIC COMPUTER GAMES." I hope you enjoy these games, and will keep coming back each week. Blessings, David

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 :)

Sunday, June 7, 2009

New designs, bad habits?

Here's a poser for the Object-Oriented development crowd.

OO tells us to encapsulate; to keep our object methods small (or, in the word of the jargon, atomic). Build objects that have simple methods, and that helps make the objects reusable. By the same token, objects also have state that is resposed in one or more member variables that may or may not be exposed by public properties. State, however, is tyically expensive, because persistence implies the memory and similar resources necessary to implement it.

Atomicity and state indirectly tend to work against each other. If my methods are too small, it necessarily suggests I'm going to push out elements to the class level. But if I push too much to the class level, I run the risk of creating classes that may need increasingly complex persistence mechanisms which, in turn, suggests a class that may be too broadly scoped. Yet if I decompose (or factor) an object too much, the fragmented design becomes a nightmare to maintain. It's not clearly a vicious circle, but it's a cautionary cliff to avoid.

Here's a shadowy example.

Suppose you have a class:

public class Something
{
    public void InterestingMethod1()
    {
    int ImportantVariable;
   ....do something interesting...
    }
}


And, without typing them here, suppose you have several similar methods in this class, each with a similar "ImportantVariable" declaration. Now, the casual observer would probably suggest that the repetition of that variable could indicate that it should be declared at the class level, as such:


public class Something
{
    int ImportantVariable;

    public void InterestingMethod1()
    {
    }
}


If, however, we start referencing "ImportantVariable" in our atomic methods, don't we reintroduce an old villain in our nice, object-oriented code? It seems to me that in a class of any appreciable size that does any appreciable work, factoring out common variables to the class level starts to look a lot like our old nemesis - global variables. We all know they're bad, don't we? That is, a substantive module wherein we declare a variable once, then it has scoping across all methods, allowing a single change to wreak all manner of unintended consequences. But isn't that precisely what member variables are allowing us to do in even moderately complex classes?

I won't pretend that I have the answer here, nor that this microexample is anything but a strawman example of the point. So I'll throw out the question- what is the "right" answer? When do our atomic methods have elements like local variables factored to the class scope, risking global behavior; when do our classes have members pushed down to methods for the sake of atomicity?

When, indeed? The floor is open for debate...

Friday, May 29, 2009

Hello...and an introductory musing

Greetings!!

Here begins a blog devoted to things computing and development oriented, ranging from the whimsical to the technical and everything in between. As topics evolve, feel free to contribute and comment for the benefit of everyone.

I've been a developer in the Windows environment for over 20 years, back in the halcyon days of Visual Basic, and later into the object-oriented world of Java, and more recently C# in ASP.NET. Most of my focus in recent years has been on database development in Microsoft SQL Server. I run a small Samba-based network at home, and have contributed to various technical publications over the years.

This brings us to today's inaugural post: a question of simplicity.

I'm a pretty simple person. I like plain mashed potatoes with a little butter and salt. I like hamburgers without a lot of gourmet trappings. And I prefer my programming code to be simple, too.

Having developed ASP.NET applications in C# for some time, it should come as no surprise to have encountered a situation with a site having multiple pages serving as content in conjunction with a master page. Each subsequent page gets a reference to a master page, and if programmatic access to elements of the master page is needed, you use the page's Master property to access them. Now, this works, and has obviously working for some time now, but it seems an unnecessarily complication.

The situation above, in my own head, screams as a matter of inheritance. In my ideal world, I would declare a base master page, a base content page, and derive all such pages in my projects:

// ideal ASP.NET subclass model
public class MyMasterPage: MasterPage
{
}

public class MyBaseContentPage: Page
{
}

public class AppContentPage: MyBaseContentPage
{
}

In this world, the source files for AppContentPage amount to a single ASPX file that includes the ASP:Content control markers for inclusion into the inherited master page, and the corresponding .cs code-behind file. That's it.

Now, you can "kinda" do this in ASP.NET today, but not quite. Note that these declarations omit the "partial" keyword - that's because they're hard classes. You can declare "hard" classes that reside in a web application folder called "App_Code", but as they're compiled to a separate assembly, they have no knowledge of master pages or other components. You can create page files with partial class declarations that inherit hard classes from App_Code, but you don't inherit the ASPX file that goes with it.

The inheritance model I dream of here eliminates the need for the "Master" keyword to access elements of the inherited master page, which I've always thought of as a bit of an ugly hack. For the notion of "installable" master pages, the inheritance notion also suggests that master page developers could be led more naturally to the implementation of standard interfaces to bridge the gap between the presentation and the content, not to mention the use of events to decouple the master page from its consumer. That "Master" keyword leads to ungainly and knarled code that, further, tends to gloss over design partitioning issues that, in turn, slow productivity.

As I noted, I understand you can "kinda" do the things I've discussed in ASP.NET, but not in a way (so far as I know) that truly embraces the notion of object-based inheritance.

Am I wrong? Have I missed some huge boat here?

Let me know.