Living in the past

I wrote a calculator program in C++ in Borland’s 3.1 old MS-DOS based IDE. The interesting part for me was writing making the mouse handler class. Mouse functions weren’t included with the compiler. I had to write my own code to handle the int 33h calls to the mouse hardware. I spent enough time with it to remind myself how nice modern IDEs are.

The program is pretty simple but made a few test programs to build up to it anyway. Also these days getting something like this installed on a modern computer is quite the process. It involves virtual machines and virtual floppies.

If you want to replicate this you will first have to install some sort of virtual machine software. I have a personal copy of VMWare Workstation 12, so that’s what I used. If you don’t have that you I would recommend downloading and installing Virtualbox. It is free and will handle MS-DOS 6.22 just fine.

It is fairly easy to find copies of MS-DOS 6.22 and Borland C++3.1 floppies online. I didn’t have to do that because I still have my original disks from the early 90s. Virtually inserting floppy disks to virtual machines works just fine for this. You will have to make your own grinding noises though.

For simple programs you won’t need to do any special MS-DOS configuration. That is a relief because that can be quite tricky. Installing Borland C++ is also pretty eay to do just the defaults and reboot and you are ready to go. Unless you want a mouse then you will have to find and install a MS-DOS mouse driver.

I created a folder called “HELLO” and entered the directory by using the following commands:

md hello
cd hello

I then then launched the the Borland C++ IDE from the “C:\HELLO\” directory by typing “bc”. At first you will be able to move around the pull-down menus at the top with the arrow keys. To select something press enter. Go under “FILE” and select “New”. This will open a window that you can type into. if you want to get back up into the menus hit F10 or alt key and one of the red highlighted letters in the menus.

I typed in the following C++ code and saved it as “HELLO.CPP”

#include <iostream.h>
#include <conio.h>

int main()
{
	clrscr();
	cout << "Hello World!";
	getch();\
	return 0;
}

I selected “Run” from the menus and this was the glorious result.

I closed HELLO.CPP and opened a new file to try something a little more ambitious. The next program will be one that displays an interactive ASCII table. This is a simple program but it highlights a some stuff that has changed in making C++ programs. I’d imagine it could stump some programmers who learned C++ recently.

//ASCIITAB.CPP
//	by CrookedFingerGuy 6/2/2011
//This is a program that displays an ascii table and information on one
//selected character value.
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>

void drawMenu()
{
	gotoxy(5,1);
	cout << "C)hange Value";
	gotoxy(5,2);
	cout << "U)p";
	gotoxy(5,3);
	cout << "D)own";
	gotoxy(5,4);
	cout << "Q)uit";
	gotoxy(20,1);
	cout << "+)Increment";
	gotoxy(20,2);
	cout << "-)Decrement";
}

void drawCharInfo(unsigned char c)
{
	//draw the formatting box
	gotoxy(35,1);
	cout << "ÉÍÍÍÍÍÍÍÑÍÍÍÍÑÍÍÍÑÍÍÍÍÍÍÍÍ»";
	gotoxy(35,2);
	cout    << "º" << "Decimal"
			<< "³" << "Char"
			<< "³" << "Hex"
			<< "³" << "Binary  "
			<< "º";
	gotoxy(35,3);
	//This next section is used to change characters that
	//affect the formating and are undisplayable.
	//They are changed to the 'space' character.
	unsigned char t=c;
	if((t>=7)&&(t<=10))
		t=' ';
	else if(t==13)
		t=' ';
	else if(t==26)
		t=' ';
	else if(t==255)
		t=' ';
	//Display character values in various formats.
	cout    << "º" << setw(7) << dec << (int)c
			<< "³" << setw(4) << t
			<< "³" << hex << setw(3) << (int)c
			<< "³" << dec;
	for(int i=0;i<8;i++) //This loop outputs the binary digits
	{
		gotoxy(53,3);
		cout << setw(8-i) << (int)(c%2);
		c=c>>1;
	}
	gotoxy(61,3);
	cout << dec << "º";
	gotoxy(35,4);
	cout << "ÈÍÍÍÍÍÍÍÏÍÍÍÍÏÍÍÍÏÍÍÍÍÍÍÍͼ";
}

void drawTable(unsigned char start)
{
	unsigned char x=start;
	int row=0;
	int col=0;

	gotoxy(1,5);
	cout << "ÉÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍËÍÍÍÑÍ»";
	for(row=0;row<20;row++)
	{
		gotoxy(1,row+6);
		cout << 'º';
		for(col=0;col<10;col++)
		{
			if((x>=7)&&(x<=10)) //These ifs check for undisplayable chars
				cout << setw(3) << (int)x << '³' << ' ' << 'º';
			else if(x==13)
				cout << setw(3) << (int)x << '³' << ' ' << 'º';
			else if(x==26)
				cout << setw(3) << (int)x << '³' << ' ' << 'º';
			else if(x==255)
				cout << setw(3) << (int)x << '³' << ' ' << 'º';
			else
				cout << setw(3) << (int)x << '³' << x << 'º';
			x++;
		}
	}
	gotoxy(1,25);
	cout << "ÈÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏÍÊÍÍÍÏͼ";
}

int main()
{
	//Initialize screen
	_setcursortype(_NOCURSOR);
	clrscr();
	drawMenu();
	//Draw inital Character Info box.
	int charInfo=0;
	drawCharInfo(charInfo);
	//Draw Initial ASCII table starting at 0.
	int tableStart=0;
	drawTable(tableStart);

	//Menu Input Loop
	char choice=' ';
	while((choice!='q')&&(choice!='Q'))
	{
		choice=getch();
		switch(choice)
		{
		case '+':
			charInfo++;
			drawCharInfo(charInfo);
			break;
		case '-':
			charInfo--;
			drawCharInfo(charInfo);
			break;
		case 'c':
		case 'C':
			_setcursortype(_NORMALCURSOR);
			gotoxy(36,3);
			cout << "       ";
			gotoxy(36,3);
			cin >> charInfo;
			drawCharInfo(charInfo);
			_setcursortype(_NOCURSOR);
			break;
		case 'u':
		case 'U':
			tableStart+=10;
			drawTable(tableStart);
			break;
		case 'd':
		case 'D':
			tableStart-=10;
			drawTable(tableStart);
			break;
		};
	}
	clrscr();
	_setcursortype(_NORMALCURSOR);
	return 0;
}

This is what this program looks like when it runs. (Note: Some weird stuff may have happened above due mismatched character codes between the modern web and MS-DOS.)

This program is also slightly useful if you want to look up old ASCII codes see the characters.

Simple Tests Are Done

Next up is a little more advanced. Borland C++ 3.1 has projects that allow you to organize all of the various files associated with even slightly complex programs. I don’t think it is that intuitive to create a new project. There is no “New Project” menu item. You have to go to Project>Open Project.

Now you just have to know to type in your project file name in place of the asterisks.

Hitting OK lets you start adding files to a C++ project.

From here on I think the IDE is easy to learn. There are not so many menu options that you could never look through them all like on a modern IDE. You can find most of the information about compiler specific functions in the Help area. The help file is easy to search and often includes example code.

I made a program that makes interrupt calls to operate the mouse. I use them to work a simple calculator program. Here is all the code.

#include <iostream.h>
#include <iomanip.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <math.h>
#include "mouse.h"
#include "calcdisp.h"

#define MAXLEN 12 //maximum number of digits in display
#define OFFX 0 //Not used
#define OFFY 0 //Not used

//int main() is at the bottom


//This is a function for debugging/explaing the mouse stuff
void showMouseState(MouseState mTest,int clicks)
{
	gotoxy(1,15);
	cout << setw(3) << mTest.x << "\n"
		<< setw(3)  << mTest.y << "\n"
		<< mTest.button << "\n" << clicks << endl;
	if(mTest.processed)
		cout << "Processed";
	else
		cout << "Not Processed";
}


//This function takes in the mouse state and converts the
//click into the character associated with the spot on the screen
//It also increments the number of left clicks
char getMouseInput(Mouse &MyM,MouseState &m,int &clicks)
{
	int c=' ';

	//Check if the left button has been newly clicked
	//In text mode the mouse moves in increments of 8
	if((m.button==1)&&(m.processed==NOT_PROCESSED))
	{
		if((m.x>=8)&&(m.x<=24))// Left column
		{
			switch(m.y)
			{
			case 24:    //Top row
				c='c';
				break;
			case 40:    //Second row
				c='7';
				break;
			case 56:    //You guessed it third row
				c='4';
				break;
			case 72:    //There is a pattern here
				c='1';
				break;
			case 88:    //Five rows total
				break;
			}
		}
		else if((m.x>=40)&&(m.x<=56))//Second column
		{
			switch(m.y)
			{
			case 24:
				c='@';
				break;
			case 40:
				c='8';
				break;
			case 56:
				c='5';
				break;
			case 72:
				c='2';
				break;
			case 88:
				c='0';
				break;
			}
		}
		else if((m.x>=72)&&(m.x<=88))
		{
			switch(m.y)
			{
			case 24:
				c=' ';
				break;
			case 40:
				c='9';
				break;
			case 56:
				c='6';
				break;
			case 72:
				c='3';
				break;
			case 88:
				c='.';
				break;
			}
		}
		else if((m.x>=104)&&(m.x<=120))//Four columns
		{
			switch(m.y)
			{
			case 24:
				c='+';
				break;
			case 40:
				c='-';
				break;
			case 56:
				c='*';
				break;
			case 72:
				c='/';
				break;
			case 88:
				c='=';
				break;
			}
		}
		clicks++;
		MyM.setPState(PROCESSED);//Click and hold only 1 "key" press
	}
	return c;
}

int main()
{
	//Turn off the cursor and clear the screen
	_setcursortype(_NOCURSOR);
	clrscr();

	//Initialize the mouse using my custom mouse class
	Mouse MyMouse;
	MyMouse.resetDriver();
	MyMouse.visible(MOUSE_SHOW);
	MouseState ms;
	int clickCount=0;//counting left clicks is for explaination purposes only


	//Initializing the calulator display and functions
	CalcDisplay CalcD(MAXLEN);
	CalcD.showCalc();
	CalcD.showDisplay();

	//Menu Input Loop
	char choice=' ';
	while((choice!='q')&&(choice!='Q')) //press q to quit
	{
		choice=' ';
		if(!kbhit())//kbhit checks if a key has been hit
		{
			//If no key hit check the mouse
			MyMouse.update();
			ms=MyMouse.getState();

			//convert relvant mouse clicks to characters
			choice=getMouseInput(MyMouse,ms,clickCount);
		}
		else
			choice=getch(); //get the key that was hit
		//This shows the mouse state for explaination purposes
		showMouseState(ms,clickCount);

		//This is the function where the program actually adds and stuff
		CalcD.processInput(choice);
	}

	//clear the screen and turn the cursor back on
	clrscr();
	_setcursortype(_NORMALCURSOR);
	return 0;
}
#ifndef CALCDISP_H
#define CALCDISP_H

//These Enums are pretty self explainitory
//NOOP mean No Operation
//SQROOT is for the square root function
enum opType {NOOP,EQUALS,PLUS,MINUS,MULT,DIVIDE,SQROOT};


//This class contains the core of the calculator's functionality
//It also handles all of the output of the calculator
//It takes in characters that are pressed or clicked and
//works like a standard desk calculator would.
class CalcDisplay
{
public:
	CalcDisplay(int maxDisplay);
	~CalcDisplay();

	//Draws the characters that mimic a calculator keyboard
	void showCalc();

	//This handles the calculator "screen" output
	void showDisplay();

	//handles when the 'c' or clear button is pressed
	void clearDisplayBuf();

	//handles when a number or '.' button is pressed
	void addDigit(char c);

	//if there is a math operation to do this does it
	void computeAnswer();

	//Figures out what sort of button has been pressed and calls the
	//appropriate helper function to handle it
	void processInput(char c);

private:
	double displayVal; //holds what is currently showing on the "screen"
	double prevDisplayVal; //the holds the other number to do math with
	int maxDisplayLength; //Some day I might make this thing wider
	char *displayBuf; //c style string to hold what is on the "screen"
	opType curOp; //What the calculator is going to do next
};

#endif
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "calcdisp.h"


CalcDisplay::CalcDisplay(int maxDisplay)
{
	displayBuf=new char[maxDisplay];
	displayVal=0;
	prevDisplayVal=0;
	maxDisplayLength=maxDisplay;
	curOp=NOOP;
}

CalcDisplay::~CalcDisplay()
{
	delete displayBuf;
}

void CalcDisplay::showCalc()
{
	cout << "ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»" << endl;
	cout << "º               º" << endl;
	cout << "ÌÍÍÍÑÍÍÍÑÍÍÍÑÍÍ͹" << endl;
	cout << "º C ³ û ³   ³ + º" << endl;
	cout << "ÇÄÄÄÅÄÄÄÅÄÄÄÅÄÄĶ" << endl;
	cout << "º 7 ³ 8 ³ 9 ³ - º" << endl;
	cout << "ÇÄÄÄÅÄÄÄÅÄÄÄÅÄÄĶ" << endl;
	cout << "º 4 ³ 5 ³ 6 ³ * º" << endl;
	cout << "ÇÄÄÄÅÄÄÄÅÄÄÄÅÄÄĶ" << endl;
	cout << "º 1 ³ 2 ³ 3 ³ / º" << endl;
	cout << "ÇÄÄÄÅÄÄÄÅÄÄÄÅÄÄĶ" << endl;
	cout << "º   ³ 0 ³ . ³ = º" << endl;
	cout << "ÈÍÍÍÏÍÍÍÏÍÍÍÏÍÍͼ" << endl;
}

void CalcDisplay::showDisplay()
{
	gotoxy(2,2); //DOS conio.h function to move cursor

	if(strlen(displayBuf)>0) //is a new number being entered?
	{
		if(curOp==EQUALS)
			cout << setw(15) << displayVal;
		else
			cout << setw(15) << displayBuf;
	}
	else
		cout << setw(15) << displayVal;
}

//A number has finished being entered and an operation has been chosen
void CalcDisplay::clearDisplayBuf()
{
	prevDisplayVal=displayVal;
	displayBuf[0]=0;
}

void CalcDisplay::addDigit(char c)
{
	if((c!='.')||(!strchr(displayBuf, '.'))) //only one decimal point please
	{
		int l=strlen(displayBuf);
		if(l<maxDisplayLength-2)
		{
			displayBuf[l]=c;   //add the new digit to the buffer
			displayBuf[l+1]=0; //c style strings need a 0 at the end
			displayVal=atof(displayBuf); //convert string to float/double
			showDisplay(); //update the display with the ned digit
		}
	}
}


//This is where the calculator does the calculating
void CalcDisplay::computeAnswer()
{
	switch(curOp)
	{
	case PLUS:
		displayVal=displayVal+prevDisplayVal;
		break;
	case MINUS:
		displayVal=prevDisplayVal-displayVal;
		break;
	case MULT:
		displayVal=displayVal*prevDisplayVal;
		break;
	case DIVIDE:
		displayVal=prevDisplayVal/displayVal;
		break;
	case SQROOT:
		displayVal=sqrt(displayVal);
	};
	curOp=EQUALS;
	showDisplay();
	clearDisplayBuf();
}

void CalcDisplay::processInput(char c)
{
	switch(c)
	{
	case 'c':
		curOp=NOOP;
		clearDisplayBuf();
		displayVal=0;
		showDisplay();
		break;
	case '+':
		curOp=PLUS;
		clearDisplayBuf();
		break;
	case '-':
		curOp=MINUS;
		clearDisplayBuf();
		break;
	case '*':
		curOp=MULT;
		clearDisplayBuf();
		break;
	case '/':
		curOp=DIVIDE;
		clearDisplayBuf();
		break;
	case '@': //There isn't a square root symbol on the keyboard so...
		curOp=SQROOT;
		clearDisplayBuf();
		computeAnswer();
		break;
	case '=':
	case 13:  //The enter key will also work for equals for numpad users
		computeAnswer();
		break;
	case '.':
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		addDigit(c);
		break;
	};
}

#ifndef MOUSE_H
#define MOUSE_H

//Storing the Int 33h info in readable variables
#define MOUSE_SHOW		0x0001
#define MOUSE_HIDE	   	0x0002

#define MOUSE_LEFT		0x0001
#define MOUSE_RIGHT     0x0002

#define NOT_PROCESSED	0
#define PROCESSED		1

//A structure to save usefull mouse data
struct MouseState
{
	int x;
	int y;
	int button;
	int processed;
};

class Mouse
{
public:
	//Contrstructor and destructor are not used
	Mouse();
	~Mouse();

	//Essential mouse operations
	void resetDriver();
	void visible(int vis);
	void update();
	void setPState(int p);
	MouseState getState();
private:
	MouseState mState;
};

#endif
#include <dos.h>
#include "mouse.h"

Mouse::Mouse()
{
}

Mouse::~Mouse()
{
}


//Call the function to start using the mouse driver
void Mouse::resetDriver()
{
	union REGS r;
	r.x.ax=0x0000;
	int86(0x33,&r,&r);
}

//You can turn mouse cursor visibility on and off with this
//Options MOUSE_SHOW and MOUSE_HIDE
void Mouse::visible(int vis=MOUSE_SHOW)
{
	union REGS r;
	r.x.ax=vis;
	int86(0x33,&r,&r);
}

//Makea call to Int 33h. That gets you the the coordinates and
//the button state. Next you store the data in the class's MouseState
void Mouse::update()
{
	union REGS r;
	r.x.ax=0x0003;
	int86(0x33,&r,&r);

	if((mState.button==1)&&(r.x.bx==0))
		mState.processed=NOT_PROCESSED;
	mState.x=r.x.cx;
	mState.y=r.x.dx;
	mState.button=r.x.bx;
}

//This is the function that passes mouse info to the rest of the program.
MouseState Mouse::getState()
{
	return mState;
}

//The mState.processed is used to only do something once per click.
void Mouse::setPState(int p=PROCESSED)
{
	mState.processed=p;
}

Here is what the program looks like running.

It feels like a nice little project that gave me a peek at all of the features available at the time for programmers. It was a fun little project.