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

       Part I

       Part II

       Part III

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

     Structures

     Bit Manipulation

     Advanced Pointers

     File I/O

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

TIGCC Programming Lessons

Lesson Review 1 - Text Display, Keyboard Input Handling, and Sprite Graphics

} else if (key == KEY_LEFT) {
  // make sure the piece is still drawn, so it's still there when we move
  if (!on) {
    Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
             pieces[currentPiece], LCD_MEM, SPRT_XOR);
    on = 1;
  }

  // if there is a left position to move to      
  if (position > 0) {
    // move left
    position--;
        
    // if we are at the empty slot, move left again
    if (puzzle[position] == -1 && position > 0) {
      position--;
        
    // if we can't move left anymore, start at the end
    } else if (puzzle[position] == -1 && position == 0) {
      position = 15;
    }
  } else {
    // set the position to the end
    position = 15;
        
    // if we found the empty slot, move left one
    if (puzzle[position] == -1) {
      position--;
    }
  }

  // reset the puzzle position holder
  currentPiece = puzzle[position];

Now we can look into the other key presses that we need to examine. The first key we will take a look at is the LEFT arrow. The arrow keys move around the puzzle changing the current piece, so we can move different pieces around. If we had a real slider puzzle, we would simply put our finger on the piece we want to move, but we had to alter that for the digital plane. I thought about using the numbered keys to move the different pieces, but that would have been complex, and what about pieces higher than 9? So I decided on the current strategy instead.

You'll notice the first thing we do is make sure the piece is drawn. We explained how this worked when we were doing the move right code segment, so we will skip it here. It just checks the on variable, and draws the piece if it's not on.

Albeit a bit inconsistent, I thought it would be easier for the movements to wrap around. However, the left and right actually wrap around from the end of one row to the beginning of the next, or from the end piece to the beginning piece, because it does not do checking for end positions, only array indexes past 16 (for complete global wrap-around, instead of local).

So, the first check is to see whether or not the last position in the array exists (i.e. position 0 has no last position, so we need to perform wrap-around). A better version of the puzzle might make the left and right movement wrap around to the same row, instead of the next/previous, or beginning/end row, but we didn't write a better version, we wrote this one, so we'll use this for now. It's simpler this way, and it means I have to explain less. :-)

If there is a previous position in the array (i.e. the current position is > 0), then the rest is pretty simple. We shift the position one to the left. However, we also need to check for the empty slot. To do this, we check to see if our new position has a previous position, and if it is, did we hit the empty slot. If these two conditions are true, then we shift the position left again. If we did hit the empty slot, but we are at the beginning, then we must wrap-around to 15, the end position.

But what happens if there was no left position to begin with? Simple again. We just move directly to position 15 (the end slot). Then we check for the empty slot, and if we hit it, move left one. We do not need to check for another previous slot, because we are already at 15, so there will definitely be a previous slot.

Now that we have shifted the position, we need to update the current piece to the puzzle piece of the slot we moved into. This is also very trivial to do.

} else if (key == KEY_RIGHT) {
  // make sure the piece is drawn, so we can move away safely
  if (!on) {
    Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
             pieces[currentPiece], LCD_MEM, SPRT_XOR);
    on = 1;
  }
      
  // if there is a position to move right to
  if (position < 15) {
    // move one position to the right
    position++;
      
    // if we hit the empty slot, move right again
    if (puzzle[position] == -1 && position < 15) {
      position++;
        
    // if we hit the empty slot and the edge, start over
    } else if (puzzle[position] == -1 && position == 15) {
      position = 0;
    }
  // else, we need to start over at the beginning
  } else {
    // move to the beginning
    position = 0;
        
    // if we hit the empty slot, move right again
    if (puzzle[position] == -1) {
      position++;
    }
  }
      
  // reset the puzzle position holder
  currentPiece = puzzle[position];

This code looks very similar to the code used to move to the left. We just need to move to the right instead, so instead of shifting the position one left, we shift it one right. All the rules still apply, just in the opposite direction. Just remember that to do wrap-around now, we don't check position 0, we check position 15, because that's where we will be if we are at the right-most position, not 0, as the left-most position is. Everything else should be readily identifiable.

} else if (key == KEY_UP) {
  // make sure the piece is drawn, so we can move safely
  if (!on) {
    Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
             pieces[currentPiece], LCD_MEM, SPRT_XOR);
    on = 1;
  }
  
  // if there is an up position to move to
  if ((position - 4) >= 0) {
    // move up one position
    position-=4;
        
    // if we hit the empty slot
    if (puzzle[position] == -1) {
      // if there is another up position, go there
      if ((position - 4) >= 0) {
        position-=4;
      // otherwise, run to the bottom row
      } else {
        position = 16 - (4 - position);
      }
    }
  // else, move to the bottom row
  } else {
    // set position at the bottom row
    position = 16 - (4 - position);
        
    // if we hit the empty slot, move up again
    if (puzzle[position] == -1) {
      position-=4;
    }
  }
      
  // reset the puzzle position holder
  currentPiece = puzzle[position];

Moving up is a different story than moving right or left. This is because wrap-around was implemented more properly, by keeping the same column, and only moving to the bottom row. Since up from the top row would wrap around to the bottom row, in the same column.

We see it begins much the same way as right or left, by ensuring that our piece is drawn. Then we move on to array position checking. To find the position one above from us, we move 4 positions back in the array, since there are 4 columns in each row.

If such a position exists, we shift to it. Now we must check for the empty slot. If we hit it, and there is another up position in the array before us, then we need to move to it. However, if no such position exists, then we need to find the position on the bottom row in our same column. To find this value, we take the number of slots in the array (so as to start from the end), then subtract the inverse of the column number, which is 4 - the column, since there are 4 rows in a column. This is because the position 1 is 3 (or 4 - 1) from the end. We could also have done 12 + position instead of 16 - (4 - position), but they are both equivalent. I just thought of the 16 - (4 - position) first. 12 + 1 = 16 - (4 - 1) = 13, which is also the same as 12 + x = 16 - (4 - x) for all x's in the real number set. I hope that was already self-evident. :-)

Knowing this information, the rest of it should be easy to follow, as it's basically the same as the left movement.

} else if (key == KEY_DOWN) {
  // make sure the piece is still drawn, so we can move safely
  if (!on) {
    Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
             pieces[currentPiece], LCD_MEM, SPRT_XOR);
    on = 1;
  }
      
  // if there is a down position to move to
  if ((position + 4) <= 15) {
    // move down one position
    position+=4;
        
    // if we hit the empty slot
    if (puzzle[position] == -1) {
      // if there is another down position
      if ((position + 4) <= 15) {
        // move to the next down position
        position+=4;
      // otherwise, goto the top row
      } else {
        position%=4;
      }
    }
  // else, move to the top row
  } else {
    // move to the top row
    position%=4;
        
    // if we hit the empty slot
    if (puzzle[position] == -1) {
      // move down one more time
      position+=4;
    }
  }
    
  // reset the puzzle position holder
  currentPiece = puzzle[position];
}

Now we examine the down movement. It is similar to the up, left, and right movements, but we have a new way for calculating the wrap-around position, as you might well imagine.

We also have to make sure the next position down (4 positions forward in the array) is <= to 15, so we don't go past the end of the array. Remember that this is dangerous (i.e. address error's are not only possible, they are mandatory). So, if we have a down position, then we need to go to it. Next we check again for the empty slot. If we have another down position, and we hit the empty slot, we can move here, but if we do not, then we need to find the wrap-around position.

Calculating the wrap-around position requires some knowledge of integer division, and modulo division. Remember that we are not working with floating point numbers (floats cannot even be processed by the 68000 processor natively), but rather with simple whole integers. This means when we do division, we end up with two things. The quotient and the remainder. The quotient is the number of times the divisor will evenly divide into the number. For example, 7/4's quotient would be 1, because we can evenly divide 7 into 4 pieces 1 time. 8/4's quotient would be 2, since 8 can be evenly divided by 4 two times. Now let's discuss the remainder. The remainder is the number we are left with after evenly dividing the number by the divisor. So, in our examples above, 7/4's remainder would be 3, since we have 3 left over after dividing 4 into 7 one time. 8/4's remainder would be 0, because 8/4 is a perfect quotient. These are all basic algebraic operations which you should know already, but it's worth mentioning here since C does not create floating point numbers from the division of integers, because we can only store whole numbers (i.e. integers) in integer variables. We have no use for floats at this juncture.

Now that we've had an introduction to integer and modulo division (the quotient and the remainder, respectively), we can talk about how this helps us calculate the position of the correct column in the first row. If we think about it, the remainder of the division between the position we are at now, and the number of columns per row (4) would produce the column number. Since the column number is in the first row, we do not need to further adjust this number. We already have the correct position within the array, since the column number + 0 would be the correct offset in the puzzle array.

So, we know that the column position is calculated by the modulo division of the position and the number of columns, 4.

With this information, the rest of the code is rather similar to the other directions. Just know the the position down is actually 4 forward from the current position, and you should be fine.

// blink the current piece so we know which one it is
on = !on;
Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
         pieces[currentPiece], LCD_MEM, SPRT_XOR);

Now that we are done with keystroke management, we can move on to the next part; the blinking current position. To do the blinking, we use the timer to timeout every 1/2 second, then when it expires (or the user presses a key, though this is actually a side-effect), we flip the piece off (or on). To keep track of the current status of the piece, we use the on variable. You'll remember that earlier I said it is set to not true if it is off. This is what I meant. In C, we can toggle a variable's true/false status by assigning its not value. The statement on = !on; means, whatever on is, reverse it. Make it whatever it is not. This sounds weird, but it's just like a light switch you flip on, or off. You don't need to check whether the light switch is flipped to the on position to turn it off. We just flip it to the inverse of whatever it is.

The Sprite16() function is pretty self-explanatory, since we already maintain the current piece, we always know which one to blink.

// set the winner to one, then try to disqualify it with
// the for loop. it's easier to reach a false conclusion than
// a true conclusion, so start true, and work towards false
winner = 1;

// test for winner
// loop through all game pieces, and see if their positions
// match their piece numbers. If they do, then the player won
// if we find any piece that is not right, we have no winner
// and we stop the for loop
for (loop = 0; loop < 15; loop++) {
  if (*y[loop] != ((loop / 4) * 16) || *x[loop] != ((loop % 4) * 16)) {
    winner = 0;
    break;
  }
}

Here is where we check for a winner. It would actually be more efficient to do this only when we move a piece, but it was simpler to do it this way. This is just another place where we have to stick within the confines of what we have already learned in lessons 1, 2, and 3, so as not to make the review too complicated. As I've said before, this program is definitely not the best that it could be, if it were properly written. We will probably touch upon this in a future lesson or review.

To see if we have a winner, we first set the winner variable to true. We do this because it is very easy to disprove this statement, but very hard to prove it. It's just easier to make this kind of test, because instead of proving every position is in the correct position, all we need to do is find one piece which is in the incorrect position. To prove that the winner were true, we would need to check every spot for the winning circle, which means we would have to check every slot, for the correct position. Instead, we just assume this is true, and then disprove it. (That sounds more complicated than it did in my head...)

To see if we have a winner, we must loop through the puzzle array, and examine each piece until we 1) come to the end of the array (in which case we have a winner), or 2) find a piece which is not in the winning position.

To find if a piece is in the winning position, we calculate the correct position for the piece. The y position is correct for a particular position in the array if it's value is the row times 16 pixels (with the row numbers being 0-3). To find the row position, we use integer division. The quotient of the loop position in the array and 4 yields the row number. Now to find the correct x position. The x position is correct for a particular position in the array if it's value is the\ column times 16 pixels (with column numbers being 0-3). To find a column number, we use modulo division. The remainder of the loop position in the array and 4 yields the column number, as we save before above. If either of these conditions yield false, then we set the winner variable to 0, and break from the loop.

You'll notice the new keyword break. The break statement exits from a loop, and stops it from going on. So, if we hit a break inside this for loop at position 5, the rest of the positions won't be evaluated. This is more efficient because we can stop when we've found one piece out of place, which is very soon.

// if we have a winner, make sure the piece is drawn and
// set done to yes
if (winner) {
  // if we win, then we are done, obviously
  done = 1;
      
  // make sure the piece is drawn so it looks nice
  if (!on) {
    Sprite16(*x[currentPiece], *y[currentPiece], PIECE_HEIGHT, 
             pieces[currentPiece], LCD_MEM, SPRT_XOR);
    on = 1;
  }
      
  // tell the player they won
  DlgMessage("Winner", "You have completed the puzzle!", BT_OK, BT_NONE);
}

If we still have a winner at the end of our check, we need to exit the outer while loop, by setting the done variable to true. Then we make sure the piece which was blinking stops blinking. Finally, we use a dialog pop-up box to tell the user they have solved the puzzle.

DlgMessage() is a new function. It is used to bring up a message dialog box with a simple message and a title. The arguments are as follows: title message, display message, first button, second button. Since we haven't really covered dialog box messages, it is enough to know that the BT_OK means an OK button, and BT_NONE means no button. We'll cover dialog boxes in more detail in a future lesson. For now, just understand that this function will bring up a pop-up dialog displaying "Winner" as a title, and "You have completed the puzzle!" in the main window, with one button, and OK button. You can exit this dialog by pressing ESC or ENTER.

Step 3c - Program Conclusions

I hope this review has been rewarding. There were a couple completely new concepts, but most things were taken directly from lessons 1, 2, and 3. This review should show you how powerful your programming can become already with only these few simple techniques. Remember to read Zeljko Juric's TIGCC documentation, as it documents MANY MANY functions which can be used with TIGCC to aid your programs. Everything I have learned about TI-89/92+/V200 specific C programming has come from that documentation.

If you are learning C for the first time, this is a lot to absorb. Take it slow. C is not as complicated or terse as assembly, but it is relatively complicated for a high level language. Rome wasn't built in a day. I've been programming in C for almost 10 years. It takes awhile to get the hang of it, but once you learn it, it becomes second nature.

Step 4 - Conceptual Review and Conclusion

After the first 3 lessons and this review, you should have a good understanding of how text display, basic keyboard input, and unmasked sprites work. Other things about the C language should be becoming more clear now, but you have a long way to go in terms of overall C knowledge. We've only touched about 10% of the concepts in C. However, don't let that discourage you. Look what we can do with only 10% of C. Think what we could do if we added that other 90%?

Just remember to take these things slow. If you don't understand something, play around with the code. See what happens if you change something. As long as you use TiEmu, you can't do any real damage. I wouldn't even venture a guess as to how many times I've locked up the virtual calculator testing some program. Things that seem like a good idea one minute can seem very stupid when the calc crashes.

Anyway, just remember that patience is a virtue. And if you want to be a C programmer, you're going to need a lot of it. Have fun with these lessons. I'll try to write more as soon as possible. For now, my hand is cramping, which means this conclusion is at an end.

 


Review 1: Text Display, Keyboard Input, and Sprite Graphics
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