Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

     Structures

     Bit Manipulation

     Advanced Pointers

     File I/O

     Graduate Review

       Part I

       Part II

       Part III

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

TIGCC Programming Lessons

Review 3: Introduction to Graduate Programming

Step 2a - Program Analysis, Continued

// the main game loop
while (!done) {
	// setup game play
	drawLevel(level);
	initSnake(snakeptr);
	drawSnake(snakeptr);
	drawScore(score);
	currentApple = drawApple(level, snakeptr);
	applePoints = getApplePoints(level, speed, difficulty);
	apples = totalApples;
	drawApples(apples);
	drawLives(snake.lives);

Okay, here is our main game loop. This loop is responsible for taking the player through the levels, responding to key presses, moving the snake, generating the apples, and all the other things that are part of playing a Nibbles clone. The first step is to draw the level using the drawLevel() function. Note that the play function initialized the level variable to be 0.

// drawLevel - draws one of the maze levels
void drawLevel(short int level) {
	short int yLoop,xLoop;
	long int area, mask;

	// clear the screen first
	ClrScr();

	for (yLoop = 0; yLoop < YTILES; yLoop++) {
		area = levels[level][yLoop];
		mask = 0x100000;

		// start the x loop
		for (xLoop = 0; xLoop < XTILES; xLoop++) {
			// shift the mask index
			mask >>= 1;

			// if the level data says to put a block here, then do it
			if (area & mask) {
				drawBlock(xLoop * WIDTH, yLoop * HEIGHT, block, SPRT_OR);
			}
		}
	}

	// share the bottom line of the maze
	clearDisplayLine();
}

If you went through lesson 8, this function should look very familiar. It's nearly identical to the drawMap() function from lesson 8 part 3. In fact, this is where I got the code in the first place. The differences are very subtle. First, we have more than one level, so we need to grab the appropriate level from the level definitions in gfx.h. Basically, we loop through the y lines, and for each x position in the y that we find a 1 in the level map, we draw a block on the screen using the drawBlock() function.

// drawBlock - draws a block on the screen (all nibble graphics are composed of blocks)
inline void drawBlock(short int x, short int y, unsigned char *sprite, short int mode) {
	Sprite8(x,y,HEIGHT,sprite,LCD_MEM,mode);
}

The drawBlock function simply draws a block on the screen. There are three kinds of 'blocks'. The normal bricks, which make up the level boundaries and obstacles. The empty blocks which are used to erase the snake, and the 'apple shaped' blocks which somewhat resemble an apple. I'm not much of an artist, but I did the best I could.

The final part of the drawLevel() function clears the bottom display lines. Since the bottom part of the map is always the bottom border, we can share the bottom pixels with some indicator texts.

// clearDisplayLine - removes the bottom 3 lines of the bottom border
//			for extra indicator display room
void clearDisplayLine(void) {
	short int loop;

	for (loop = 92; loop < 95; loop++) {
		DrawLine(0,loop,160,loop,A_REVERSE);
	}
}

The clearDisplayLine function just erases the bottom 3 lines of the border so we can share that space with the display indicators.

Now we move on to the initSnake() routine.

// initSnake - sets up the snake parameters
void initSnake(SNAKE *snake) {
	short int loop;

	// reset the snake to standard
	snake->dead = FALSE;
	snake->length = 3;
	snake->direction = RIGHT;

	// setup the initial position
	for (loop = 0; loop < 4; loop++) {
		snake->location[loop].x = 5 - loop;
		snake->location[loop].y = 3;
	}

	// clear all the excess positions
	for (loop = 3; loop < 28; loop++) {
		snake->location[loop].x = 0;
		snake->location[loop].y = 0;
	}
}

The initSnake routine simply sets up the snakes initial settings. The snake starts out alive, with a length of 3, moving right. Since we use the same snake structure, we need to make sure all the parts past his length are empty. This is because his location parts are how we will draw him on the screen.

Now that we have a snake, we should draw him on the screen. Call the drawSnake() function for this.

// drawSnake - draws the snake on the LCD
void drawSnake(SNAKE *snake) {
	POSITION pos;
	short int loop;

	// draw the snake segments
	for (loop = 0; loop < snake->length; loop++) {
		pos = snake->location[loop];
		drawBlock(pos.x * WIDTH, pos.y * HEIGHT, block, SPRT_OR);
	}

	// remove the last segment to create the illusion of movement
	pos = snake->location[snake->length];

	if (pos.x != 0 && pos.y != 0) {
		drawBlock(pos.x * WIDTH, pos.y * HEIGHT, empty, SPRT_AND);
	}
}

So, to draw the snake, we need to put a block for each POSITION of the snake tail. Finally, drawSnake is called each time the snake moves, so erase the last block of the snake to simulate movement.

Now we can draw the score indicator. For now, it will be 0, but that will soon change.

// drawScore - draws the current player score at the lower-left
inline void drawScore(unsigned long int score) {
	printf_xy(SCOREX,SCOREY,"%lu",score);
}

The drawScore method simply displays the score in the lower left side of the screen. The printf_xy function is not actually based on printf. It is actually a macro that calls sprintf and DrawStr. The DrawStr is called with the A_REPLACE option, so each score will replace the last one automatically.

Next, we draw an apple on the grid. To do this, we call the drawApple() function.

// drawApple - draws an apple on screen at a randomly selected location
// 	returns - the position of the newly drawn apple
POSITION drawApple(short int level, SNAKE *snake) {
	POSITION pos = {0,0};

	// find a suitable random position for the apple
	while (hasHitWall(pos,level) || hasHitSnake(pos,snake,SNAKE_HIT_APPLE)) {
		pos.x = random(XTILES);
		pos.y = random(YTILES);
	}

	// draw the apple sprite
	drawBlock(pos.x * WIDTH, pos.y * HEIGHT, apple, SPRT_XOR);

	// return the apple's position
	return pos;
}

In this function, we find a random position for the apple to be drawn. The only requirement is that we don't draw on the level walls or the snake. So, we check the hasHitWall and hasHitSnake functions to make sure we haven't done that. Finally, we draw an apple block, and return the POSITION we came up with.

// hasHitWall - checks to see whether the snake hit a wall
// 	returns - TRUE if snake hit wall, FALSE otherwise
short int hasHitWall(POSITION pos, short int level) {
	long int mask = 0x80000, area;
	short int crash = FALSE;

	// find the search area
	area = levels[level][pos.y];
	mask = mask >> pos.x;

	// if we have a match, we crashed
	if (area & mask) {
		crash = TRUE;
	}

	return crash;
}

To check whether a POSITION intersects the wall, we first find the row of the level we are on. Then, we check to see if our position x intersects one of the block locations in the level data. So, to find the level row, area is the levels array of the level we want to search, with row pos.y. Then, our mask, what we check for, is a single bit 0x80000, or binary 10000000000000000000 giving us 20 bits, the number of horizontal blocks in the level. So, shift our mask bit right by the x coordinate, which ranges from 0 - 19. Then, if area & the mask bit returns a true value, we have an intersection (crash). Otherwise, we don't. See how it works? This should be very familiar from lesson 8. If you need some more review, take a look at lesson 8 again.

// hasHitSnake - checks to see if the snake hit itself
// 	returns - TRUE if the snake hit itself, FALSE otherwise
short int hasHitSnake(POSITION pos, SNAKE *snake, short int start) {
	short int loop;

	// loop through the body of the snake
	for (loop = start; loop < snake->length; loop++) {
		// if the position and the snake match, we hit the snake
		if ((snake->location[loop].x == pos.x) && (snake->location[loop].y == pos.y)) {
			return TRUE;
		}
	}

	return FALSE;
}

Checking for snake hits is different though. Instead of needing to check a single row, we have to check every place the snake is. So, if any of the snake positions are the same as our POSITION position, we hit the snake.

Now, for our scoring system, we need to know how many points an apple is worth in this level, based on our speed and difficulty settings. The faster you play and harder level you select, the more points you get, obviously. This is the bonus for playing the hard levels.

// getApplePoints - calculates the number of points the apple is worth
//	returns - points apple is worth
short int getApplePoints(short int level, short int speed, short int difficulty) {
	short int speedBonus;

	// determine speed bonus
	if (speed <= MEDIUM) {
		speedBonus = 0;
	} else if (speed > MEDIUM && speed < QUICK) {
		speedBonus = 25;
	} else if (speed > AVERAGEPLUS && speed < VERYFAST) {
		speedBonus = 50;
	} else { // speed = VERYFAST
		speedBonus = 100;
	}

	// calculate and return bonus
	return (((level * 5) + 10 + (speedBonus)) * (++difficulty));
}

This function is just some basic math operations. It should be easy enough.

Lastly, we just have to draw the apple and live indicators that tell the player how many apples are left to eat in this level, and how many lives he has.

// drawLives - displays the life indicator using life tokens
void drawLives(short int lives) {
	short int loop, x = 76, max = lives;

	if (max > MAX_LIVES) {
		max = MAX_LIVES;
	}

	// draw a life token for each remaining life
	for (loop = 0; loop < max; loop++) {
		x+=4;
		drawToken(x,97,lifeToken);
		x++;
	}
}

// drawApples - displays remaining apples using apple tokens
void drawApples(short int apples) {
	short int loop, x = 76;

	// draw one apple token for each remaining apple
	for (loop = 0; loop < apples; loop++) {
		x+=4;
		drawToken(x,94,appleToken);
		x++;
	}
}

These two functions simply draw the live and apple token indicators. The only thing to note is that it's possible to have more lives than we can display. If that happens, we simply display as many life tokens as possible, then stop. This is often done when tokens are used for indicator in games. Certain scores give us extra lives, so we don't want to draw off-screen, or worse, into memory that isn't part of the screen buffer. This could cause the calc to crash.

Well, all that and we're finally ready to play the game. See how easy game programming is?

begin = FALSE;
while (!begin) {
	while ((key = getKey()) == 0);

	if (key == KESC) {
		snake.dead = TRUE;
		begin = TRUE;
		done = TRUE;
		ending = QUITTER;
	} else if (key == KENTER) {
		begin = TRUE;
	}

	// wait for keypresses to dissipate
	delay(KEYDELAY);
}

Here is our wait loop. We'll wait for the user to press ENTER to start the level. Or, if the user wants, we can press ESC to end the game. Before we start, make sure the key presses are gone, as ENTER is also our pause key.

while (!snake.dead && apples > 0) {
	for (loop = 0; loop < speedDelay; loop++) {
		if ((key = getKey()) != 0) {
			if (speed < MEDIUM) {
				keyDelay = (6 * KEYDELAY) / 7;
			} else if (speed < AVERAGEPLUS) {
				keyDelay = (2 * KEYDELAY) / 3;
			} else if (speed < FAST) {
				keyDelay = (53 * KEYDELAY) / 100;
			} else {
				keyDelay = KEYDELAY / 2;
			}

			delay(keyDelay);

			break;
		}
	}

Here is our level loop. We do this loop until we are either dead, or we have eaten all the apples in the level.

switch (key) {
	case KLEFT:
		if (snake.direction != RIGHT) {
			snake.direction = LEFT;
		}
		break;
	case KUP:
		if (snake.direction != DOWN) {
			snake.direction = UP;
		}
		break;
	case KDOWN:
		if (snake.direction != UP) {
			snake.direction = DOWN;
		}
		break;
	case KRIGHT:
		if (snake.direction != LEFT) {
			snake.direction = RIGHT;
		}
		break;
	case KCHEAT:
		if (cheater == TRUE) {
			if (level < levels) {
				level++;
			}

			apples = -1;
		}

		cheater = TRUE;
		break;
	case KESC:
		done = TRUE;
		snake.dead = TRUE;
		ending = QUITTER;
		break;
	case KENTER:
		// pause game
		delay(KEYDELAY);
		while (getKey() != KENTER);
		delay(KEYDELAY);
		break;
}

Here we respond to any keypress the user made. The arrow keys simply change the direction the snake is moving. However, we can't move directly opposite the direction we are already going, so make sure we aren't doing that before we change directions.

If the user pressed the cheat key, we advance their level. If the user pressed ESC, we exit the game. If we pressed the ENTER key, we pause the game waiting for them to press ENTER again.

// move the snake
moveSnake(snakeptr);

Now, as part of the game loop, the snake needs to be always moving, so we call the moveSnake function.

// moveSnake - moves the snake across the screen
void moveSnake(SNAKE *snake) {
	short int loop;

	// relocate the positions
	for (loop = snake->length; loop >= 0; loop--) {
		snake->location[loop] = snake->location[loop-1];
	}

	// grab the second position
	snake->location[0].x = snake->location[1].x;
	snake->location[0].y = snake->location[1].y;

	// adjust the snake based on the direction
	if (snake->direction == UP) {
		snake->location[0].y--;
	} else if (snake->direction == DOWN) {
		snake->location[0].y++;
	} else if (snake->direction == LEFT) {
		snake->location[0].x--;
	} else if (snake->direction == RIGHT) {
		snake->location[0].x++;
	}

	// re-draw the snake at the new position
	drawSnake(snake);
}

Okay, to move the snake, we simply shift all the snake positions one down. So, the head becomes tail piece 1, tail piece 1 becomes tail piece 2, and so on. When its finished the end part of the tail disappears. So, now we just need to find the new place for the head based on the direction the snake is moving. Then, we redraw the snake.

// check for snake hitting things
if (hasHitWall(snake.location[0], level)) {
	// hit wall
	drawCrash();
	snake.dead = TRUE;
} else if (hasHitSnake(snake.location[0], snakeptr, SNAKE_HIT_SELF)) {
	// hit self
	drawCrash();
	snake.dead = TRUE;

Now we need to check if the snake crashed. Notice the third parameter of hasHitSnake is SNAKE_HIT_SELF. Since we are comparing the snake to itself, the head will appear to be hitting the head, so we do a special check to make sure we don't check for the head of the snake. In our case, the snake head has to hit some part of the tail. Logically, we could have started 3 places down, because the head couldn't hit the first or second tail piece based on the way the snake is able to move, but that's okay. Our program is fast enough.

If we did crash though, we draw the crash logo and set the snake's dead parameter to TRUE.

// drawCrash - draws the crash message across the screen
void drawCrash(void) {
	short int x, offset = 0;

	for (x = 0; x < CRASHWIDTH; x += 32) {
		Sprite32(CRASHX+x,CRASHY,CRASHHEIGHT,crash+offset,LCD_MEM,SPRT_AND);
		Sprite32(CRASHX+x,CRASHY,CRASHHEIGHT,crash+offset,LCD_MEM,SPRT_OR);
		offset+=CRASHHEIGHT;
	}

	// wait for keypress
	delay(KEYDELAY);
	while (!getKey());
	delay(KEYDELAY);
}

Here we draw the crash logo. Instead of using XOR logic though, we use AND and OR logic. Basically, this will let us replace whatever is behind the crash logo. So, it will erase any background, and the snake, and anything else in the way.

Before returning from the crash, wait for the user to press a key.

} else if (hasHitApple(currentApple,snake.location)) {
	// ate apple
	growSnake(snakeptr);
	currentApple = drawApple(level,snakeptr);
	score += applePoints;
	drawScore(score);
	apples--;
	eraseApple(apples);

	if (apples == 0) {
		// next level, add point and life bonuses
		level++;
		score+=500*difficulty+10*snake.lives;
		snake.lives++;

		if (level > levels) {
			ending = WINNER;
			done = TRUE;
			score += difficulty*10000;
		}
	}
}

Finally, if we haven't hit a wall or ourselves, the only other even of note is that we hit the apple. So, check for that.

// hasHitApple - checks to see whether the snake ate an apple
// 	returns - TRUE if they hit the apple, FALSE otherwise
short int hasHitApple(POSITION apple, POSITION *snake) {
	short int snagged = FALSE;

	// if the position and the apple position match, we snagged it
	if ((apple.x == snake[0].x) && (apple.y == snake[0].y)) {
		snagged = TRUE;
	}

	// otherwise, we didn't
	return snagged;
}

Basically, all we need to do is see if the apple's position intersects the snake's head, which is at snake position 0.

Then we make the snake grow using the growSnake() function.

// growSnake - makes the snake grow
void growSnake(SNAKE *snake) {
	short int loop;

	// add snake positions for growth
	for (loop = snake->length; loop < (snake->length+SNAKE_GROW); loop++) {
		snake->location[loop].x = snake->location[snake->length].x;
		snake->location[loop].y = snake->location[snake->length].y;
	}

	// adjust the snakes length
	snake->length += SNAKE_GROW;
}

Okay, the snake grows by replicating the end of the tail to the lower pieces of the snake position array. When we redraw the snake, these pieces will be moved and drawn along with the rest of the snake. Finally, just update the snake length and we're done.

If there are no more apples to eat, we are finished with the level. Give the player the bonus for finishing the level, some points and an extra life.

Finally, if we are past the last level, we are the winner.

if (snake.dead && !cheater) {
	// cheaters get infinite lives, but they pay for it
	if (--(snake.lives) < 0) {
		done = TRUE;
		ending = LOSER;
	}
}

If we crashed, the snake is now dead. If they are not using the cheat function, decrement their lives. If they have no more lives, they have lost the game.

if (cheater) {
	score = 0;
	ending = CHEATER;
}

// display ending message
drawEndMessage(ending);

// wait for keypress before continuing
delay(KEYDELAY);
while (!getKey());
delay(KEYDELAY);

If the player is cheating, don't allow them to keep their score at the end. Cheaters should not be in the hall of fame. Then we draw the ending message, either that they lost, quit, won, or cheated. These images are defined in gfx.h.

Wait for the user to press a key before continuing.

// drawEndMessage - draws the message at games end (i.e. winner, loser, quitter)
void drawEndMessage(short int ending) {
	short int x, offset = 0;
	unsigned short int *sprite = endings[--ending];

	for (x = 8; x < 152; x += 16) {
		Sprite16(x,38,25,sprite+offset,LCD_MEM,SPRT_AND);
		Sprite16(x,38,25,sprite+offset,LCD_MEM,SPRT_OR);
		offset += 25;
	}
}

The drawEndMessage function just displays a sprite, like the drawCrash() function.

// display the hiscore board
drawHiScoreBoard(score);

At the end of the game, we draw the hiscore board. In addition, this function supplies the user's score, which will be added to the hiscore table if they acheived a hiscore.

// drawHiScoreBoard - draws the hiscore board, and updates it with the hiscore from the latest game
void drawHiScoreBoard(unsigned long int newscore) {
	SCORE scores[MAX_HISCORES];
	short int loop, pos = -1;
	char name[10], str[50], *error = "File I/O Error";
	HANDLE dlg;

    // restore auto-interrupts
    SetIntVec(AUTO_INT_1,autoint1);
    SetIntVec(AUTO_INT_5,autoint5);

	if (!loadHiScores(scores)) {
		// cannot open hiscore file -- display error
		DlgMessage(error,"Unable to Load HiScore Data",BT_OK,BT_NONE);
		return;
	}

The Hiscore board uses AMS keyboard handing so we can input the player's name, so we first restore the interrupt handlers.

Then we load the hiscores from file using the loadHiScores() function.

// loadHiScores - loads the hiscores from file into the hiscore table
// 	returns TRUE if hiscores were read, FALSE otherwise
short int loadHiScores(SCORE *hiscores) {
	short int success = TRUE;
	FILE *f;

	// open file for reading and check for errors
	if ((f = fopen(scorefile,"rb")) == NULL) {
		success = FALSE;
	} else {
		// read the hiscore table and check for I/O errors
		if (fread(hiscores,sizeof(SCORE),MAX_HISCORES,f) != MAX_HISCORES) {
			success = FALSE;
		}

		fclose(f);
	}

	return success;
}

The loadHiScores() function is very simple if you understood lesson 10. We open the scorefile (defined at the top of hiscore.c) and read the data into the hiscore array. If we encounter an I/O error, we return FALSE.

// check if latest score is a highscore
for (loop = (MAX_HISCORES - 1); loop >= 0; loop--) {
	if (newscore > scores[loop].score) {
		// new HiScore!!
		pos = loop;
	}
}

Next we check to see if the score the player had is a hiscore. So, start at the top and check to see if any score on the list is higher than it. Equal scores do not replace old scores on the list. If we find one, we set the pos variable.

if (pos != -1) {
	// if we found a new hiscore
	if ((dlg = DialogNewSimple(DLGWIDTH,DLGHEIGHT)) == H_NULL) {
		DlgMessage("Memory Allocation Error","Not Enough Free Memory!",BT_OK,BT_NONE);
	} else {
		DialogAddTitle(dlg,"New Hiscore!",BT_OK,BT_NONE);
		DialogAddRequest(dlg,5,25,"Your Name:",0,9,11);

		sprintf(str,"You earned the #%hd hiscore position!",pos+1);
		DialogAddText(dlg,5,15,str);

		do {
			// truncate name variable
			name[0] = 0;
		} while (DialogDo(dlg,CENTER,CENTER,name,NULL) != KEY_ENTER);

		// free the dialog box memory
		HeapFree(dlg);

So, assuming we have a hiscore, we enter here. We built a custom dialog box to ask the player for their name.

// move the hiscore list down
if (pos < (MAX_HISCORES - 1)) {
	for (loop = (MAX_HISCORES - 1); loop > pos; loop--) {
		scores[loop].score = scores[loop - 1].score;
		scores[loop].name[0] = 0;
		strcpy(scores[loop].name,scores[loop - 1].name);
	}
}

This here is a very critical piece of code. If we have a hiscore that is better than the lowest hiscore, then we need to move the other entries on the list down.

So, if pos (our hiscore) is < (better than) the lowest hiscore, then loop through the hiscores starting at the bottom and going until we reach the hiscore position we beat. For each of these scores, move their position in the array one down.

That piece of code doesn't look complex, but it's deceitful like that.

// fill in the new hiscore
scores[pos].score = newscore;
scores[pos].name[0] = 0;
strcpy(scores[pos].name,name);

if (!saveHiScores(scores)) {
	DlgMessage(error,"Unable to save HiScore Board",BT_OK,BT_NONE);
}

Finally, we put our hiscore on the table and save the new hiscore table to file.

// saveHiScores - saves the hiscore table to the hiscore file
//	returns TRUE if the scores were saved to file, FALSE otherwise
short int saveHiScores(SCORE *hiscores) {
	const char *filetype = "N68K";
	short int success = TRUE;
	FILE *f;

	// delete any old scorefile before recreating
	unlink(scorefile);

	// open the hiscore file
	if ((f = fopen(scorefile,"wb")) == NULL) {
		success = FALSE;
	} else {
		// write the hiscore table to disk - check for I/O errors
		if (fwrite(hiscores,sizeof(SCORE),MAX_HISCORES,f) != MAX_HISCORES) {
			success = FALSE;
		}

		// write the file tag
		fputc(0,f);
		fputs(filetype,f);
		fputc(0,f);
		fputc(OTH_TAG,f);

		fclose(f);
	}

	return success;
}

The saveHiScores() function shouldn't be any more difficult than loadHiScores(). We delete the old score file first, then we open a new score file. We write the array to the file, then write the file tag. If we encounter I/O errors, we return FALSE.

// display the hiscore board

// clear the screen
ClrScr();

// draw the screen borders
drawBorder();

// draw the game logo
drawLogo();

FontSetSys(F_8x10);
DrawStr(25,35,"Hiscore Board",A_NORMAL);
FontSetSys(F_6x8);

for (loop = 0; loop < 5; loop++) {
	printf_xy(20,50+loop*10,"#%hd %-9s %lu",loop+1,scores[loop].name,scores[loop].score);
}

ngetchx();

// redirect auto-interrupts 1 and 5
SetIntVec(AUTO_INT_1,DUMMY_HANDLER);
SetIntVec(AUTO_INT_5,DUMMY_HANDLER);

// wait for keypresses to dissipate
delay(KEYDELAY);

Finally, we are ready to display the hiscore table. Everything should be simple except for the loop's printf statement. The format string is a little complex. You can read all about the miracle of string formatting tags in stdio.h's TIGCC docs under the printf function, but I will just tell you what this one does for now.

The x and y should be simple enough, so here is what the format string means.

#%hd (#number, in this case, #1, #2, #3, #4, and #5 for each hiscore number)

%-9s (print the name with minimally 9 characters. If there are less than 9 characters, put spaces to the right of the string)

%lu - long unsigned decimal integer.

There are a horde of useful formatting tags you can use when you need text data to look good.

Finally, we disable interrupts and wait for our keys to dissipate.

Step 3 - Conclusions

Wow, just when you were thinking it would never end, it's over.

I hope this lesson was an eye-opener. It's sometimes hard to tell what skills you have before you know what you can do with them. If you've learned how to read the TIGCC docs, use the TIGCC programming forum, and try things to see what happens, you already have all the necessary skills you need to program the TI-89/92+/V200.

I will most likely begin writing more advanced C tutorials now, so if you still have questions, write me. I'm usually glad to help. Also, if you don't get a quick answer from me, it is often easier (and faster) to get a response from the TIGCC programming forum.

 


Review 3: Intro to Graduate Programming
Questions or Comments? Feel free to contact us.


 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit