|
~ Introduction to Programming: C ~ Session 11 - Programming Style and Games |
In This Session...
In this session we will be covering the following topics:-
Last session we covered:-
Introduction to Programming Style
This session we will be looking at the rather yawn-inspiring sounding subject of Programming Style and to offset this, the rather more interesting-sounding fundamental behind all games - the appearance of randomness.
Programming style refers to the way that you set out your program instructions when you type them out. The C language is very flexible on this. As long as you conform to the language requirements - for example putting semi-colons after instructions, using curly brackets / brackets in the right places etc., you can space out your program any way you like. The exception to this is the use of directives (i.e. commands which begin with a hash (#) character such as #include and #define). These must be one-per-line, and consequently, do not have a semi-colon on the end of them to show where they end.
The following is taken from last session's notes - it displays a times table from 1x10 to 10x10. Can you understand how it is structured?
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#define MAX_TABLE 10
main(){int num1,num2,result;for(num1=1;num1<=MAX_TABLE;num1++){
printf("%2d times table\n",num1);printf("==============\n");
for(int num2=1;num2<=MAX_TABLE;num2++)result=num1*num2;
printf("%2d x %2d = %3d\n",num2,num1,result);getch();clrscr();}}
Copy and paste the text into a new project (e.g. call it something like ex39 or exercise39 for both C source file name and project filename).
Try executing it by clicking on the
button. Do the results look right to you?
I have introduced a deliberate error which leaves the C program operating - i.e. the syntax or language used is correct. However the logic or function of the program is now incorrect, causing it to operate incorrectly.
Let's take a look at how we might structure this program better so that it is easier to locate where the mistake might be:-
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#define MAX_TABLE 10
main()
{
int num1, num2, result;
for ( num1=1; num1<=MAX_TABLE; num1++ )
{
printf( "%2d times table\n", num1 );
printf( "==============\n" );
for ( num2=1; num2<=MAX_TABLE; num2++ )
result = num1 * num2;
printf( "%2d x %2d = %3d\n", num2, num1, result );
getch(); clrscr();
}
}
Remember that a block is a number of instructions grouped together by an opening and closing curly bracket { and }. See if you can spot where a pair of these might be missing from the program above.
For the solution, look at the example (which includes explanatory comments) in the Comments section - the curly brackets are highlighted in green.
A well-structured program should indent (i.e. move to the right by pressing the space bar two or three times) all instructions inside each block. Therefore, a block inside another block will be indented twice etc. This helps to show which instructions will be executed, and also helps to line up opening and curly braces.
It is often helpful to separate stages of a program by putting an extra line between each stage. In the above example, the #include statements are separated from the #define statement, which is separated from the main() function, to show they do three separate tasks.
Inside the main() function, the steps are separated using blank lines to give:-
Variable Declaration (naming and giving types to new variables)
Displaying the title
Showing the outer for loop
Showing the inner for loop
Waiting for user input and clearing the screen.
You may be able to see the problem if you look carefully. The inner for loop is missing its curly braces. By arranging the program in this way, it is easier (with practice) to spot where you miss a brace or two. If you forget your closing curly brace, then you have a better idea of where it is missing should you get an error.
Another useful technique that helps the readability inside each instruction is to put spaces after commas when listing parameters (i.e. data) to be given to a function - e.g.
printf("%2d x %2d = %3d\n", num2, num1, result);
This helps identify where each parameter starts and makes the program easier to read. This can equally be applied with listing the three parts of a for loop.
Similarly, it can make it easier to put spaces inside formulae (maths stuff). For example:-
float amount, vat;
amount = 100.0; vat=17.5; printf( "%4.1f\% added to %5.1f is %6.2f\n",
amount, vat, amount * ( (vat/100) + 1 )
);
Quite a lot is going on here, but spacing things out makes it a little easier to follow. As you can see, you can continue a number of parameters to be used in the printf across several lines, to make it easier to follow. In particular, the calculation has spaces included in it to help identify which parts of the equation or calculated together - i.e. vat is divided by 100 (to give 0.175), and then 1 is added to this (to make 1.175 and then this value is multiplied by the amount of 100 ( to give 117.50). This gives us 17.5% added on to the amount (i.e. 17.5% of 100 is 17.5, added on to 100 gives 117.5). The mathematics is less important in this context than understanding how the use of spaces can make something look clearer. Here's a possible unclear alternative:-
float amount,vat;amount=100.0;vat=17.5;printf("%4.1f\% added to %5.1f is %6.2f\n",amount,vat,amount*((vat/100)+1));
What would you prefer to see if you were looking at somebody else's program?
Going back to our original program, we could make it even easier to follow by explaining what is happening at each step of the way, and by giving an explanation at the top of the program to show what the purpose of the program is, and who wrote it. This is useful in case you ever have to work on somebody else's program, and you need to ask them for advice on how it works.
/* Exercise 39 - ex39.cpp
** Written by Simon Huggins
** 3rd December 2002
** -----------------------------------
** Times table listing program
*/
/* Include standard C libraries */
#include <stdio.h> #include <conio.h> #include <ctype.h>
/* Define MAX_TABLE constant to give largest
** times table to go up to
*/
#define MAX_TABLE 10
/* Main program */
main()
{
/* Set up variables in main():-
** - num1 = outer loop counter
** - num2 = inner loop counter
** - result = multiplication calculation
*/
int num1, num2, result;
/* Outer for loop - counts from 1 to MAX_TABLE */
for ( num1=1; num1<=MAX_TABLE; num1++ )
{
printf( "%2d times table\n", num1 );
printf( "==============\n" );
/* Inner for loop - counts from 1 to MAX_TABLE */
for ( num2=1; num2<=MAX_TABLE; num2++ )
{ result = num1 * num2; printf( "%2d x %2d = %3d\n", num2, num1, result ); }
/* After each times table, wait for a key to be
pressed, and then clear the screen for the next
*/
getch(); clrscr(); } }
To start a comment, use the /* symbols together. You can then use any text you like, which can span any number of lines. Terminate the comment using the */ symbols together. The above example uses extra asterisks (*) in some places to line up the left-margins of subsequent description lines, and to make the comments stand out more.
The advantages of structuring your programs in this way are manifold (many):-
Easier for you (or others - examiners included!) to understand your program if you need to look at it / change it later
Easier to separate out the logical steps in your program
Easier to identify who created the program, and when.
Easier to make amendments, and put the amendments in the correct place.
Easier to spot where mistakes may be - i.e. debugging.
Using comments can 'self-document' a program (i.e. if not remove, very much reduce the need to provide systems documentation), as well as help you remember how the program works. This is a contentious point - some people feel that there should be thorough separate documentation describing the function of a program. Others feel that such documentation rarely gets read or maintained in real-life working environments.
It takes a little more effort initially to properly structure your program, but as the greatest amount of a programmer's time is spent maintaining programs rather than creating new programs, you can see that this small amount of initial effort pays dividends later.
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#define VAT_RATE 17.5
#define TRUE 1
main()
{float amount,vat,total,total_vat;char ch;total=0.0;total_vat=0.0;
do{
printf("Option? [Z]ero rated, [V]ATable, [Q]uit: ");ch=toupper(getche());
if(ch=='Q')break;printf("\n");
if((ch!='Z')&&(ch!='V')){printf("Incorrect choice. Try again.\n");continue;}
printf(">>> Enter amount: ");scanf("%f",&amount);total+=amount;
if(ch=='V'){vat=amount*(VAT_RATE/100);total_vat+=vat;}else vat=0.0;
printf(" Amount: %6.2f\n VAT: %6.2f\n",amount,vat);
printf("---------- ------\n Total: %6.2f\nVAT total: %6.2f\n",total,total_vat);
printf("========== ======\n Gross: %6.2f\n\n",total+total_vat);
}while(TRUE);}
Copy the program listed above into a new project - use Copy and Paste is fine. Try executing the program. The program is a calculator that will add up sales prices for items, taking into account whether the item is zero-rated (e.g. books are zero-rated at the moment - you don't pay VAT on them), or taxable (at a rate of 17.5%). A running total is kept showing the total amount, the total vat, and the total gross amount (incl. VAT). If the Q key is pressed, the program is quit. Note that in this case, a switch...case statement has not been used. This is due to the fact that two options need data (i.e. the sales price) from the keyboard, and the other two options (Quit, or wrong choice selected) do not. This scenario is difficult to fit easily into a switch..case statement, and in this case it is arguably clearer to use multiple if statements.
The output should look something like this:-
Option? [Z]ero rated, [V]ATable, [Q]uit: z
>>> Enter amount: 50
Amount: 50.00
VAT: 0.00
---------- ------
Total: 50.00 VAT total: 0.00 ========== ======
Gross: 50.00
Option? [Z]ero rated, [V]atable, [Q]uit: V
>>> Enter amount: 100
Amount: 100.00
VAT: 17.50
---------- ------
Total: 150.00 VAT total: 17.50 ========== ======
Gross: 167.50 Option? [Z]ero rated, [V]atable, [Q]uit: g Incorrect choice. Try again. Option? [Z]ero rated, [V]atable, [Q]uit: Q
Try to make the program easier to read, and better structured using comments, spacing, line gaps, indentation, and also by ensuring that no more than one instruction is included on one line - e.g. the line:-
printf("Option? [Z]ero rated, [V]atable, [Q]uit: ");ch=toupper(getche());
would be split to:-
printf( "Option? [Z]ero rated, [V]atable, [Q]uit: " );
ch = toupper( getche() );
Compare what you get with the sample given at the back. It doesn't have to be identical - you may even come up with something that looks a little clearer!
If you divide two numbers together, then you may get a remainder. The modulus is another name for the remainder.
For example, if you divide 6 by 3, you get 2. If you divide 7 by 3, you get 2, with 1 left over. The 1 left over is the modulus. If you divide 8 by 3, you get 2, with 2 left over. The 2 left over is the modulus. If you divide 9 by 3, you get 3, with 0 left over. The 0 left over is the modulus.
The way of finding this modulus in C is to use the % operator. Taking the above examples, 7%3=1, 8%3=2, 9%3=0. It is tempting to think of this as a percentage (after all, it is using the percentage symbol!), but think of it as looking more like a division symbol - something over something else, but instead of numbers, we have holes where the remainder goes!!!
Try the following program, which will ask you for a number from 1-10, and show the modulus of this number when dividing 1 to 20 by this number:-
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#define MAX_SHOWN 20
void main()
{
int i,number,modulus;
do
{
printf( "Enter a number from 1-10: " ); scanf( "%d", &number );
}
while ( (number<1) || (number>10) );
for ( i=1; i<=MAX_SHOWN; i++ )
{
modulus = i % number;
printf( "%2d %% %d = %d\n", i, number, modulus );
}
getch(); /* wait for a key to be pressed */
}
Here is a sample output from this program, entering the number 3:-
Enter a number from 1-10: 3 1 % 3 = 1 2 % 3 = 2 3 % 3 = 0 4 % 3 = 1 5 % 3 = 2 6 % 3 = 0 7 % 3 = 1 8 % 3 = 2 9 % 3 = 0 10 % 3 = 1 11 % 3 = 2 12 % 3 = 0 13 % 3 = 1 14 % 3 = 2 15 % 3 = 0 16 % 3 = 1 17 % 3 = 2 18 % 3 = 0 19 % 3 = 1 20 % 3 = 2 _
You may have noticed a useful side-effect of using the modulus operator - the range of numbers you get is from zero (0) to one less than the number you are dividing by. This may seem obvious - after all, if the first number divides by the second number, the remainder will go back to zero again. However, this proves to be very useful when we go on to using random numbers in the next section.
Finally, try the program again entering the number two (2). You get 1s and 0s. Can you see any connection between the numbers on the left and the modulus given? E.g. Odd numbers and Even numbers? This is another useful technique, where you want to test for odd numbers or even numbers - just find the modulus 2 of the number you are testing, and if it is 0, then you have an even number, if it is 1, then you have an odd number.
i.e.
if ( (i % 2 ) == 0 ) printf("i is even");
if ( (i % 2 ) == 1 ) printf("i is odd");
So what is the fundamental requirement for any game that you wish to create? I would say an element of unexpectedness. If you played a game of cards where the same hand was played every time, you would soon stop playing that game. Similarly, if you played a battle simulation where the opposing army's tactics are always the same, or played a game of chess where the computer made the same moves each time, then you would quickly become bored.
The basic method that all games use to provide this unexpectedness (or randomness) is to make use of a random-number generator. This is a function that, each time it is used, will provide a number that appears to be random - i.e. appears to have no connection to the previous number it generated. In reality, this is not the case, but such generators can be seeded - i.e. pre-set to start a different sequence of numbers to make sure that the same sequence of numbers is not generated each time a program is executed. This is typically done by performing an equation on the current time on the computer, and using this as the starting number - or random number seed - for the random number generator. Consequently, 'random' numbers generated in this way are known as pseudo-random numbers - i.e. fake random numbers.
To generate a random number, we can use the rand() function which can be found in the stdlib.h library. This will give us a number from 0 to (2^15)-1 (or 0 to 32767 to you and me!)
So what if you want a number between 1 and 10? This is where the use of the modulus operator comes in. The modulus operator ensures that the number we get will be from 0 to the number we are dividing by minus 1. E.g. n%10 (where n is any integer number we like) will give us a number from 0 to 9. If we add 1 to this, we get a number from 1 to 10.
Therefore, to get a random number from 1-10, we could use:-
i = ( rand() % 10 ) + 1;
In order to start the 'random' sequence at a different number each time, we need to set the random number seed using the srand function. We supply this with any integer number. In order to make this different each time we run the program, we will take the system time and convert it to a number to use for the seed. We can do this using the time() function, held in the time.h library.
The command to seed the random number generator in this way would be:-
srand( (unsigned int) time( NULL ) );
The (unsigned int) part converts the result of the time() function to an integer from 0 to 65535 - this is what is known as a type cast. Don't worry too much about this - try to remember simply to use this line at the beginning of any program you use that uses random numbers to ensure your numbers appear random.
Exercise 42: 'Higher-Lower' Game
The following program is our first attempt at a game, using random numbers. It is essentially a 'guessing game' that generates a random number from 1 and 10. The computer then asks if the next number generated will be higher or lower. If the user guesses correctly, a new number is generated, and the user gets to guess again. If he guesses incorrectly, the game ends. A score is maintained - i.e. 1 point for each correct guess that is made...
So let's build our first game....
/* Exercise 43 - Higher / Lower Number Game ** (c) Copyright 2002-4 Simon Huggins ** Written 4th December 2002 */ /* Include standard C libraries to use */ #include <stdio.h> #include <conio.h> #include <ctype.h> #include <stdlib.h> #include <time.h> /* Define TRUE / FALSE constants, and also maximum number (1-n) to use */ #define TRUE 1 #define FALSE 0 #define LOWEST_NUM 1 #define MAX_NUMS 10
/* Main program */
main()
{
/* Set up variables:
** - score: The score - starts at 0, increased by 1 with a correct guess
** - number: The number chosen by the computer from 1 to MAX_NUMS
** - prior_number: The previous number the computer chose
** - have_guessed: TRUE if user has guessed at least once. Used to make
sure do not do comparison first time around loop
** - guess: User's guess - must be H for higher or L for lower
*/
int score, number, prior_number, have_guessed;
char guess;
/* Set up variables, including first random number to be displayed */
have_guessed = FALSE; score = 0;
srand( (unsigned int) time( NULL ) );
number = ( rand () % MAX_NUMS ) + LOWEST_NUM;
printf( "Initial number is %d\n", number );
do
{
/* Only do the following if statement after have been around the loop
once */
if ( have_guessed == TRUE )
{
/* If the guess was H and the number was higher than the previous
number, or if the guess was L and the number was lower than the
previous number... */
if ( (( guess == 'H' ) && ( number > prior_number )) ||
(( guess == 'L' ) && ( number < prior_number )) )
{
printf( "Correct! The number was %d\n", number );
score++; /* increase the score by 1 point */
printf( "Score is now %d", score );
}
/* Otherwise the user made a wrong guess, so break out of the loop and
end the game */
else
{
printf( "Incorrect! The number was %d\n", number );
printf( "Score was %d\nGAME OVER!", score );
break;
}
} /* end of if not first time around loop */
/* remember the previous number */
prior_number = number;
/* get the next random number the computer will use */
number = ( rand () % MAX_NUMS ) + LOWEST_NUM;
/* Get the user's input, which must be upper case H or L */
do
{
printf( "\nHigher or Lower (H/L) for a number %d-%d? ",
LOWEST_NUM, LOWEST_NUM + MAX_NUMS - 1 );
guess = toupper( getche() );
}
while ( ( guess != 'H' ) && ( guess != 'L' ) );
printf( "\n\n" );
/* We have been once around the loop, so we can compare number to
prior_number now, so set have_guessed to TRUE */
have_guessed = TRUE;
} while ( TRUE ); /* Loop around without any condition */
/* Outside loop - must be game over. So wait for user to press a key. */
getch();
}
Change the program to pick out numbers between 1 and 5 instead of 1 and 10.
How about picking a number between 5 and 9 (hint: from 5 to 9 there are 5 possible numbers that could be picked, and the lowest number is 5. If you found a random number between 0 and 4 and added 5 to this, you would get a number between 5 and 9. Try to apply this logic to the program above.
Finally, if you have the time, change the program so that the same number cannot be picked by the computer twice in a row. To do this, you will need to:-
Create a new variable to store the previous number selected by the computer (e.g. call it prior_number). Set the variable to initially contain something that cannot be chosen - e.g. -1.
Place a do..while loop around the selection of the new random number (this is not necessary for the very first number picked out). The condition should be that the prior number equals the selected number - i.e. if the numbers are the same, the loop executes again to pick another number.
After the loop, you will need to set the prior number variable to the contents of the new number, ready for the next number that may be selected (if the user makes a correct choice!)
Here is a sample solution for how to structure the sample program:-
/* Introduction to C Programming Course
Exercise 40 - Sample Solution
Written by (c) Copyright 2002-4 Simon Huggins
3rd November 2002
=================================
This program is a calculator designed to add up sales amounts
taking into account whether each item is VAT zero-rated or not.
It shows a running total of the total NET (non-VAT) amount, the
total VAT amount, and the total GROSS amount (amount incl. VAT)
*/
/* Standard C library header includes */
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
/* Constant amounts - VAT rate here in case it changes */
#define VAT_RATE 17.5
#define TRUE 1
/* The main body of the program */
main()
{
/* Variable declarations within the main() function scope:-
** - amount: The amount input at the keyboard by the user
** - vat: The vat calculated as a percentage of the amount
** - total: The accumulated total - i.e. amounts added together
** - total_vat: The accumulate total - i.e. vat amounts added together
** - ch: The menu option chosen - i.e. Zero Rated or Normal VAT or Quit
*/
float amount, vat, total, total_vat;
char ch;
/* Initialise the variables */
total = 0.0;
total_vat = 0.0;
/* Main program loop - do this forever (until exit condition met) */
do
{
/* Select an option - convert to upper case and store in variable 'ch' */
printf( "Option? [Z]ero rated, [V]ATable, [Q]uit: " );
ch = toupper( getche() );
/* Exit condition: If the user chose Q then quit the do..while loop */
if ( ch=='Q' ) break;
/* Otherwise, move cursor onto next line from the input */
printf( "\n" );
/* If the user did not make a valid choice - i.e. not Z nor V... */
if ( (ch!='Z') && (ch!='V') )
{
/* show incorrect choice made, and continue from the top of the loop */
printf( "Incorrect choice. Try again.\n" );
continue;
}
/* At this point, we know either Z or V must have been pressed.
In either case, we wish to enter an amount for the sale.
This goes into the 'amount' variable using a scanf statement */
printf( ">>> Enter amount: " );
scanf( "%f", &amount );
/* This is added to the accumulated NET total */
total += amount;
/* If the 'V' option was chosen, then there is VAT to calculate */
if ( ch == 'V' )
{
vat = amount * ( VAT_RATE / 100 );
/* Add this on to the accumulated VAT total */
total_vat += vat;
}
else
/* otherwise the VAT will be zero on this transaction */
vat = 0.0;
/* Display the Amount and calculated VAT for this item */
printf( " Amount: %6.2f\n VAT: %6.2f\n",
amount, vat );
/* Display the net total amount and total VAT for all items */
printf( "---------- ------\n Total: %6.2f\nVAT total: %6.2f\n",
total, total_vat );
/* Display the combined total and vat to give the gross amount */
printf( "========== ======\n Gross: %6.2f\n\n",
total + total_vat );
} while( TRUE ); /* Carry on unconditionally from the 'do' */ } /* End of main() function */
One consequence of using LOTS of comments is that it can make your program look cluttered. Consequently, I would advise that when you start using comments, use them everywhere, as shown in this example, to help reinforce what is going on in your program, almost on a line-by-line basis. When you come to look back at your program, it should help jog your memory about what is happening. It also helps to show examiners that you understand what is happening in the program.
However, when you become more confident, you will start to find this amount of explanation tiresome and messy, in which case, restrict your comments to explaining how large chunks of a program (e.g. whole functions - we will look at this later) work, what variables are used for, and explaining only difficult-to-understand blocks of programs (e.g. where complicated business processes are being modelled).
In the next session we will be covering the following topics:-
Arrays
Using an array and a loop to record a list of numbers
Arrays of characters as strings
The strcpy function
Using printf and scanf and gets with strings
Silly strings
![]()
(c) Copyright
2002-4 Simon Huggins. All Rights Reserved.
If you have any issues or questions regarding the content of this web
site, please contact the
author by clicking here.
Alternatively, you can leave a voice message on 00 44 (0)7050-618-297 or fax
on 00 44 (0)7050-618-298
This Page was last updated: 29 January 2004 13:06