Modularity
Modularity is a crucial cpncept in programming that allow us to break down code into reusable and manageable parts. By organizing tasks into separate modules, we improve readability, reduce redundancy, and make debugging easier. This lecture introduces functions, the distinctions between functions and procedures, different methods of calling functions, parameter passing, scope, and more.
1 Purpose of Using Functions
Functions serve several important purposes:
-
Reducing Redundancy: Functions prevent repetitive code by enabling us to write once, use many times.
-
Modular Design: Breaking a large program into smaller functions makes each part more manageable and maintainable.
-
Improved Readability: Clear, well-named functions help readers understand the purpose and flow of the code.
-
Easier Debugging and Testing: Isolated functions can be tested individually, allowing easier detection and fixing of errors.
-
Enhanced Collaboration: Multiple programmers can work on separate functions concurrently, facilitating team-based development.
2 Functions vs. Procedures
In many languages, functions and procedures have different purposes:
-
Functions: These return a value after performing an operation, such as mathematical calculations.
-
Procedures: Procedures (or
voidfunctions in C) perform an action but do not return a value.
In C, the distinction is expressed through the return type:
int sum(int a, int b) { return a + b; } // Function
void printHello() { printf("Hello, World!"); } // Procedure
3 Syntax of Function Definition and Call
The general syntax of a function in C is as follows:
return_type function_name(parameter_list) {
// Code block
return value; // Only if return_type is not void
}
For example:
int multiply(int x, int y) {
return x * y;
}
This function multiply takes two integers as input and returns their product.
Calling a function involves using its name and passing the required arguments:
int result = multiply(4, 5); // Calls the multiply function
3.1 Example of Modularity
To illustrate the concept of modularity, let’s compare a program written without functions/procedures and one using function/procedures.
3.1.1 Initial Program (Without Functions)
The following program calculates the area and perimeter of a rectangle without using functions or procedures:
#include <stdio.h>
void main()
{
int length = 5, width = 3;
int area = length * width;
int perimeter = 2 * (length + width);
printf("Area: %d\n", area);
printf("Perimeter: %d\n", perimeter);
}
3.1.2 Modular Program (With Function and Procedure)
In the modular version, we separate the tasks into a function to calculate the area and a procedure to print the results, making the code easier to read and manage.
#include <stdio.h>
// Function to calculate the area of a rectangle
int calculateArea(int length, int width)
{
return length * width;
}
// Procedure to print area and perimeter
void printResults(int area, int perimeter)
{
printf("Area: %d\n", area);
printf("Perimeter: %d\n", perimeter);
}
void main()
{
int length = 5, width = 3;
int area = calculateArea(length, width); // Function call
int perimeter = 2 * (length + width); // Function call
printResults(area, perimeter); // Procedure call
}
In this version:
-
calculateAreais a function that performs a calculation and returns a result. -
printResultsis a procedure that performs an action (printing) but does not return a value.
This modular approach improves readability, reusability, and maintainability.
4 Common Errors in Using Functions
Some common errors beginners make include:
-
Forgetting to Return a Value: Functions with a non-void return type must return a value.
-
Incorrect Parameter Types: The argument types must match the parameter types.
-
Undefined Functions: Calling a function before it is defined or declared.
5 Function Prototypes
In C, function declarations, or function prototypes, must appear before their first use. This tells the compiler about the function’s name, return type, and parameters.
int add(int a, int b); // Prototype
int main() {
int result = add(3, 4);
}
int add(int a, int b) {
return a + b;
}
Some modern C compilers are forgiving, and will let you get away without defining function prototypes.
6 Why Does the main Function Return 0?
In C, the main function returns an integer value, typically 0, to indicate the program’s termination status to the operating system. This return value is a convention established in C and many other programming languages to signal whether the program executed successfully or encountered an error. Here’s why main usually returns 0:
-
Indicating Successful Execution: A return value of
0is conventionally used to indicate that the program has completed successfully without errors. The operating system interprets this0value as a sign that the program encountered no issues. -
Error Codes for Failure: If the program fails or encounters an error, it can return a non-zero value (such as
1,2, etc.) to indicate a specific type of error. These non-zero values are often referred to as error codes, which can help identify where or why the program failed. -
Compatibility with Other Programs: In many systems, particularly in Unix and Linux environments, other programs or scripts may rely on the return value of
mainto determine if a program executed successfully. Returning0makes it compatible with these systems and helps ensure that other processes or scripts function as expected. -
Standardized Convention: According to the C standard,
mainhas anintreturn type, meaning it should return an integer. Following the convention of returning0for successful execution aligns with the standard, making the code portable and understandable to other programmers.
In the following example, the main function returns 0 to signal that the program ran successfully:
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0; // Return 0 to indicate successful execution
}
If the program encounters an error, we might return a different value:
#include <stdio.h>
int main()
{
printf("Error: Something went wrong.\n");
return 1; // Return a non-zero value to indicate an error
}
7 Function Calls and Parameter Passing
When calling a function, arguments can be passed in two primary ways:
-
Pass-by-Value: A copy of the variable’s value is passed, and any modifications within the function do not affect the original variable.
-
Pass-by-Reference: A reference (address) to the variable is passed, allowing the function to modify the original variable.
In the C programming language, all function arguments are passed by value. This means that when a variable is passed to a function, a copy of its value is sent, and any changes made to the parameter within the function do not affect the original variable in the calling function.
However, C can simulate pass-by-reference behavior by using pointers. By passing the address of a variable (using pointers) to the function, we enable the function to indirectly modify the original variable.
7.1 Pass-by-Value Example
In the following example, changes made to x inside the changeValue function do not affect a in the main function:
#include <stdio.h>
void changeValue(int x) {
x = 10; // Only changes local variable x
}
int main() {
int a = 5;
changeValue(a);
printf("a = %d\n", a); // Output will be: a = 5
return 0;
}
Since a is passed by value, only a copy of a is passed to changeValue, and modifying x within the function does not affect the original variable.
7.2 Simulating Pass-by-Reference with Pointers (Next Semester)
To modify a within the changeValue function, we can pass a pointer to a instead, allowing the function to access and modify the original variable.
#include <stdio.h>
void changeValue(int *x) {
*x = 10; // Modifies the original variable using the pointer
}
int main() {
int a = 5;
changeValue(&a);
printf("a = %d\n", a); // Output will be: a = 10
return 0;
}
Here, we pass the address of a to changeValue, which allows the function to modify the actual variable a using the pointer *x.
To summarize:
-
C uses pass-by-value for all function arguments.
-
Using pointers, C can simulate pass-by-reference by passing the address of a variable, enabling functions to modify the original variable.
8 Function Calls, Call Stacks, and Function Graph
The call stack is a data structure used by operating systems to manage function calls and variable storage during runtime. Each function call creates an activation record (or stack frame) on the call stack, containing:
-
Function parameters: Copies of values (in call by value) or addresses (in call by reference).
-
Local variables: Temporary variables declared within the function.
-
Return address: The location in memory where the program should resume once the function completes.
When a function is called, its stack frame is pushed onto the stack. After the function completes, its stack frame is popped from the stack, releasing memory and returning control to the caller.
A call graph is a visual representation of function calls within a program. Each node in a call graph represents a function, and each edge represents a call from one function to another. This graph helps understand dependencies and the flow of execution, aiding debugging and optimization.
The following C program demonstrates a function call chain where main calls a function, which in turn calls another function, and that function calls yet another function. This structure creates a call graph with a depth of three.
#include <stdio.h>
// Function to perform the final calculation
int finalCalculation(int x)
{
return x * x; // Square the value
}
// Function to process the intermediate result
int processIntermediate(int y)
{
int result = finalCalculation(y + 2); // Add 2 to y and pass to finalCalculation
return result + 5; // Add 5 to the result
}
// Function to start the calculations
int startCalculations(int z)
{
int intermediate = processIntermediate(z * 2); // Double z and pass to processIntermediate
return intermediate - 3; // Subtract 3 from the intermediate result
}
int main()
{
int input = 4;
int result = startCalculations(input); // Call depth 1
printf("The final result is: %d\n", result); // Output the final result
return 0;
}
-
finalCalculation: This function performs the final computation by squaring its input value. -
processIntermediate: This function modifies its input by adding 2, then callsfinalCalculationwith this modified value and further modifies the result by adding 5. -
startCalculations: This function doubles its input, callsprocessIntermediatewith this value, and then adjusts the result by subtracting 3. -
main: This function callsstartCalculationswith an initial input and prints the final result.
The call sequence creates a call depth of 3:
-
maincallsstartCalculations(depth 1) -
startCalculationscallsprocessIntermediate(depth 2) -
processIntermediatecallsfinalCalculation(depth 3)
9 Scope and Lifetime of Variables in Functions
Variables declared within a function are local to that function, meaning they cannot be accessed outside it. Additionally:
-
Scope: Defines where a variable can be accessed. Local variables exist only within their function.
-
Lifetime: Refers to how long a variable persists. Local variables in C have automatic storage duration and are destroyed when the function exits.
10 Local and Global Variables
10.1 Definition
-
Local Variable: Declared inside a function or block and accessible only within that scope.
-
Global Variable: Declared outside all functions and accessible from any function in the program.
10.2 Key Differences
| Aspect | Local Variables | Global Variables |
|---|---|---|
| Scope | Limited to the function/block | Entire program |
| Lifetime | Created and destroyed with function/block | Exists throughout program execution |
| Access | Only within its function | Any function can access |
| Initialization | Must be initialized before use | Initialized to zero by default |
Example: Local and Global Variables
#include <stdio.h>
int globalVar = 10; // Global variable
void demonstrateVariables() {
int localVar = 5; // Local variable
printf("Local Variable: %d\n", localVar);
printf("Global Variable: %d\n", globalVar);
}
int main() {
demonstrateVariables();
printf("Global Variable in main(): %d\n", globalVar);
// printf("Local Variable in main(): %d\n", localVar); // Error
return 0;
}
11 Functions and Procedures with Side Effects
11.1 Definition
A side effect occurs when a function modifies a state outside its scope, such as:
-
Changing global variables.
-
Modifying arguments passed by reference.
-
Performing I/O operations (e.g., printing, reading).
11.2 Example: Changing a Global Variable
#include <stdio.h>
// Global variable declaration
int score = 0;
// Function that modifies the global variable
void updateScore(int points) {
score += points; // Add points to the global score
}
int main() {
printf("Initial Score: %d\n", score);
// Update score using the function
updateScore(10);
printf("Score after adding 10 points: %d\n", score);
updateScore(5);
printf("Score after adding 5 more points: %d\n", score);
return 0;
}
11.3 Example: When It Makes Sense to Use Global Variables
One good use of global variables is in situations where you need to maintain application-wide state, such as tracking the scores of players in a game.
#include <stdio.h>
// Global variables to track game state
int player1_score = 0;
int player2_score = 0;
// Function to update Player 1's score
void updatePlayer1Score(int points) {
player1_score += points;
}
// Function to update Player 2's score
void updatePlayer2Score(int points) {
player2_score += points;
}
// Function to display the current scores
void displayScores() {
printf("Player 1 Score: %d\n", player1_score);
printf("Player 2 Score: %d\n", player2_score);
}
int main() {
printf("Initial Scores:\n");
displayScores();
// Update scores
updatePlayer1Score(10);
updatePlayer2Score(15);
printf("\nScores After Updates:\n");
displayScores();
return 0;
}
11.4 Implications of Side Effects
Pros:
-
Convenient for maintaining state across functions (e.g., counters, configuration).
-
Useful in real-world scenarios (e.g., updating databases, user interfaces).
Cons:
-
Can lead to unintended consequences (bugs) if global variables are modified unexpectedly.
-
Makes debugging harder due to hidden state changes.
Best Practices:
-
Minimize the use of global variables.
-
Use function arguments and return values instead of modifying global state.
-
Clearly document any side effects in function comments.
12 User, Library, and External Functions
Functions in C can generally be categorized into three main types:
-
User-defined Functions.
-
Standard Library Functions.
-
Functions from External Libraries.
12.1 User-defined Functions
User-defined functions are created by the programmer to perform specific tasks within a program. They help in organizing code into reusable, modular sections, which simplifies complex programming tasks.
These are the functions that you write yourself to tackle the different subtasks of the problem for which you are writing a program.
12.2 Standard Library Functions
Standard library functions are pre-built functions provided by the C standard library. These functions offer a variety of commonly used operations such as input/output, memory management, string manipulation, and mathematical calculations. They are defined in standard library headers like stdio.h, stdlib.h, and math.h.
12.2.1 Examples of Common Standard Library Functions
Some commonly used standard library functions include:
-
printf: Used for output, defined instdio.h. -
scanf: Used for input, defined instdio.h. -
rand: Used to generate pseudo-random numbers, defined instdlib.h. -
sqrt: Used to calculate the square root of a number, defined inmath.h.
12.2.2 Example of Using Standard Library Functions
#include <stdio.h>
#include <math.h>
int main() {
double num = 25.0;
double result = sqrt(num); // Standard library function from math.h
printf("Square root of %.2f is %.2f\n", num, result);
return 0;
}
Standard library functions help programmers save time and effort by providing pre-written code for common tasks.
12.3 Functions from External Libraries
External libraries provide additional functionalities not included in the C standard library. These libraries may be used for specialized tasks such as graphics, networking, or scientific calculations. To use functions from external libraries, the library files must be linked to the project, and the necessary headers included.
When you grow as a developer, you might write your own libraries and make them available to other developers. For these developers, your libraries will be external libraries.
13 Example for Fun: Creating and Using a Simple User-Defined Library
Here’s an example of a simple user-defined library for arithmetic operations:
-
Header file (
my_super_lib.h):
#ifndef MY_SUPER_LIB_H
#define MY_SUPER_LIB_H
int multiply(int a, int b);
int add(int a, int b);
#endif
-
Source file (
my_super_lib.c):
#include "my_super_lib.h"
int multiply(int a, int b) {
return a * b;
}
int add(int a, int b) {
return a + b;
}
-
Main program (
program.c):
#include <stdio.h>
#include "my_super_lib.h"
int main() {
int result1 = multiply(6, 3);
int result2 = add(10, 2);
printf("Multiplication result: %d\n", result1);
printf("Addition result: %d\n", result2);
return 0;
}
-
Compilation:
# Compile my_super_lib.c to my_super_lib.o
$ gcc -c my_super_lib.c
# Compile program.c to program.o
$ gcc -c program.c
# Link to create the executable
$ gcc my_super_lib.o program.o -o program_with_library
# Run
$ ./program_with_library
Multiplication result: 18
Addition result: 12
14 Modularity in Action
The rock-paper-scissors game is a classic hand game typically played between two players. The game is often used as a decision-making tool, similar to flipping a coin or drawing straws. Each player simultaneously forms one of three shapes with their hand, and the winner is determined by the rules below.
In rock-paper-scissors, each player chooses one of three possible moves:
-
Rock
-
Paper
-
Scissors
The game has the following rules:
-
Rock beats Scissors: Rock crushes scissors, so rock wins if the opponent chooses scissors.
-
Scissors beat Paper: Scissors cut paper, so scissors win if the opponent chooses paper.
-
Paper beats Rock: Paper covers rock, so paper wins if the opponent chooses rock.
-
Tie: If both players choose the same move, the game is a tie.
To play the game:
-
Each player chooses one of the three moves: rock, paper, or scissors.
-
The moves are revealed simultaneously.
-
Based on the chosen moves, a winner is determined according to the rules outlined above.
Let us create a C program that implements this game by using what we have covered up until now. The following are versions of the game, each version builds on the previous version by adding new functionality or improving modularity of the code.
14.1 Version 01
/**********************************************************
* Version 01 of the game rock-paper-scissor.
* Features:
* The player can player a single round.
* The player chooses a move.
* The ai generates a random move.
* The outcome of the moves is printed to the screen.
* Known Bugs:
* Entering an invalid move causes the AI to win.
***********************************************************/
// We need this for I/O
// It gives us functions: printf() and scanf(),
// among other functions
#include <stdio.h>
// We need this for random-number generation
// It gives us functions: srand() and rand(),
// among other functions
#include <stdlib.h>
// We need this to initialise the random number generator
// It gives us function: time()
// among other functions
#include <time.h>
int main()
{
// We need to initialise the random number generator
srand(time(0));
// This variable captures the player move
// It is of type char, and can store any character value
// We expect the player to input 'r', 'p', or 's'
char playerMove;
// This variable captures the ai/cpu move
// It is of type char, and can store any character value
// It will exclusively have the values 'r', 'p', or 's'
char aiMove;
// A super-exciting welcome message to the player
printf("Welcome to the rock-paper-scissors game!\n");
// Ask the user to make a move
printf("Please make a move (r=rock, p=paper, s=scissors): ");
// Get the move that the player made
scanf("%c", &playerMove);
// Generate the ai/cpu move
// The move should be generated randomly
// Since the random number generator only
// generates integer numbers,
// We will make it generate the numbers 0, 1, and 2
int aiMoveIndex = rand() % 3;
// Then, we map them to 'r', 'p', and 's' respectively
// 0 -> 'r', '1' -> p, '2' -> s
// 0 -> 'r'
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
// 1 -> 'p'
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
// 2 -> 's'
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
// We output informative messages about
// the player's move
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else if(playerMove == 's')
{
printf("Player chose scissors.\n");
}
// We output informative messages about
// the ai's move
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
// We compare the moves and decide the winner
// If both player and ai make the same move,
// they tie
if(playerMove == aiMove)
{
// So we output this
printf("We tied!\n");
}
// If the player makes a better move,
// player wins
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
// So we output this
printf("Player wins!\n");
}
// Otherwise,
// ai wins
else
{
// So we output this
printf("AI wins!\n");
}
// this else part will also account for the case when
// the player does not enter a valid move
// i.e. if player enters 'd' for example, AI wins.
// We will fix this when we introduce a loop
}
14.2 Version 02
/**********************************************************
* Version 02 of the game rock-paper-scissor.
* Updates:
* The player can player as many rounds as (s)he desires.
* The player chooses when to end the game.
* Invalid player input is handled correctly.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand(time(0));
char playerMove;
char aiMove;
printf("Welcome to the rock-paper-scissors game!\n");
// We introduce a loop in the game to make it
// longer and more exciting
while(1)
{
// We also need to offer a clean way by which the user
// can exit the game
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
// This handles the exit input,
if(playerMove == 'x')
{
// We print a sensible message,
printf("Goodbye!\n");
// and escape the loop using "break"
break;
}
// This handles the case where the input,
// is not the expected input
// We escape the current iteration and
// start the next iteration using "continue"
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
}
else
{
printf("AI wins!\n");
}
}
}
14.3 Version 03
/**********************************************************
* Version 03 of the game rock-paper-scissor.
* Updates:
* A score table is introduced in the game.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand(time(0));
char playerMove;
char aiMove;
// Since the player can play multiple rounds,
// it makes sense to introduce a score variable
// for both player and ai
int playerScore = 0;
int aiScore = 0;
printf("Welcome to the rock-paper-scissors game!\n");
while(1)
{
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
if(playerMove == 'x')
{
printf("Goodbye!\n");
break;
}
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
// We update the player score
playerScore++;
}
else
{
printf("AI wins!\n");
// We update the aiScore
aiScore++;
}
// We print a score table
// which is not really table
printf("Player = %d aiScore = %d\n", playerScore, aiScore);
}
}
14.4 Version 04
/**********************************************************
* Version 04 of the game rock-paper-scissor.
* Updates:
* The code is made modular by introducing a new
* function makeAiMove() to handle the generation of
* ai moves.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// This is a function prototype for a new function we added
// inside main
// Go find this function inside the code
char makeAiMove();
int main()
{
srand(time(0));
char playerMove;
char aiMove;
int playerScore = 0;
int aiScore = 0;
printf("Welcome to the rock-paper-scissors game!\n");
while(1)
{
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
if(playerMove == 'x')
{
printf("Goodbye!\n");
break;
}
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
// Introduce modularity in the code by creating a function
// that generates a random ai move
// i.e. we substitute the below code by a new function
/*int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}*/
// We replace the above code by the following
// function call
aiMove = makeAiMove();
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
playerScore++;
}
else
{
printf("AI wins!\n");
aiScore++;
}
printf("Player = %d aiScore = %d\n", playerScore, aiScore);
}
}
// This function replaces the commented out code inside main()
// It contains the same code we commented out
// We still need to declare at the beginning
// the variable that we want to return,
// and add a return statement at the end
char makeAiMove()
{
char aiMove;
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
return aiMove;
}
14.5 Version 05
/**********************************************************
* Version 05 of the game rock-paper-scissor.
* Updates:
* The code is made more modular by introducing a new
* procedure printMoves() to print the outcomes of the
* player and ai moves.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
char makeAiMove();
// This is a prototype for a new procedure we added
// inside main
// Go find this function inside the code
void printMoves(char playerMove, char aiMove);
int main()
{
srand(time(0));
char playerMove;
char aiMove;
int playerScore = 0;
int aiScore = 0;
printf("Welcome to the rock-paper-scissors game!\n");
while(1)
{
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
if(playerMove == 'x')
{
printf("Goodbye!\n");
break;
}
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
aiMove = makeAiMove();
// Improve modularity in the code by introducing
// a procedure that replaces the following commented-out code
/*if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}*/
printMoves(playerMove, aiMove);
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
playerScore++;
}
else
{
printf("AI wins!\n");
aiScore++;
}
printf("Player = %d aiScore = %d\n", playerScore, aiScore);
}
}
char makeAiMove()
{
char aiMove;
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
return aiMove;
}
// This procedure replaces the commented out code inside main()
// It contains the same code we commented out
// The procedure does not return anything
// and therefore it is "void"
// The procedure takes two arguments because
// the algorithm inside it needs these arguments
void printMoves(char playerMove, char aiMove)
{
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
}
14.6 Version 06
/**********************************************************
* Version 06 of the game rock-paper-scissor.
* Updates:
* A winning streak element in the game has been added.
* The game records the current winning streak of the
* player, as well as, the highest winning streak.
* A procedure has been implemented to account for the
* new addition.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
char makeAiMove();
void printMoves(char playerMove, char aiMove);
// This is a prototype for a new procedure we added
// inside main
// Go find this function inside the code
void printStreakInfo(int winningStreak, int highestWinningStreak, int playerScore);
int main()
{
srand(time(0));
char playerMove;
char aiMove;
int playerScore = 0;
int aiScore = 0;
// This variable holds the longest run of wins that the player
// achieves before losing to ai at any given time
int winningStreak = 0;
// This variable holds the longest run of wins that the player
// achieves before losing to ai at all times
int highestWinningStreak = 0;
printf("Welcome to the rock-paper-scissors game!\n");
while(1)
{
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
if(playerMove == 'x')
{
printf("Goodbye!\n");
break;
}
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
aiMove = makeAiMove();
printMoves(playerMove, aiMove);
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
playerScore++;
// The player wins, so we increment the current winning
// streak value
winningStreak++;
// and update the highest winning streak
if(winningStreak > highestWinningStreak)
{
highestWinningStreak = winningStreak;
}
}
else
{
printf("AI wins!\n");
aiScore++;
// The player loses, so the winning streak is set back to 0
winningStreak = 0;
}
// A procedure to print information about winning streak to the player
printStreakInfo(winningStreak, highestWinningStreak, playerScore);
printf("Player = %d aiScore = %d\n", playerScore, aiScore);
}
}
char makeAiMove()
{
char aiMove;
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
return aiMove;
}
void printMoves(char playerMove, char aiMove)
{
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
}
void printStreakInfo(int winningStreak, int highestWinningStreak, int playerScore)
{
// If winning streak is 0 and the player has never score before
if(winningStreak == 0 && playerScore > 0)
{
// tell the player about the loss
printf("You have lost your winning streak!\n");
}
// Otherwise
else if(winningStreak > 0)
{
// let the player rejoice
printf("Your winning streak is %d.\n", winningStreak);
}
// Also, show the highest winning streak ever achieved
printf("Your highest winning streak is %d.\n", highestWinningStreak);
}
14.7 Version 07
/**********************************************************
* Version 07 of the game rock-paper-scissor.
* Updates:
* A "cheating" AI is added to the game to improve its
* competitiveness.
* If the player wins by a large margin, the game will
* generate the moves that make it win.
* If the player loses by a large margin, the game will
* generate the moves that make the player win.
***********************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
char makeAiMove(int playerScore, int aiScore, char playerMove);
char makeRegularAiMove();
char forceAiWin(char playerMove);
char forceAiLoss(char playerMove);
void printMoves(char playerMove, char aiMove);
void printStreakInfo(int winningStreak, int highestWinningStreak, int playerScore);
int main()
{
srand(time(0));
char playerMove;
char aiMove;
int playerScore = 0;
int aiScore = 0;
int winningStreak = 0;
int highestWinningStreak = 0;
printf("Welcome to the rock-paper-scissors game!\n");
while(1)
{
printf("Please make a move (r=rock, p=paper, s=scissors, x=exit): ");
scanf("%c", &playerMove);
if(playerMove == 'x')
{
printf("Goodbye!\n");
break;
}
if(playerMove != 'r' && playerMove != 'p' && playerMove != 's' && playerMove != 'x')
{
continue;
}
aiMove = makeAiMove(playerScore, aiScore, playerMove);
printMoves(playerMove, aiMove);
if(playerMove == aiMove)
{
printf("We tied!\n");
}
else if((playerMove == 'r' && aiMove == 's') ||
(playerMove == 's' && aiMove == 'p') ||
(playerMove == 'p' && aiMove == 'r'))
{
printf("Player wins!\n");
playerScore++;
winningStreak++;
if(winningStreak > highestWinningStreak)
{
highestWinningStreak = winningStreak;
}
}
else
{
printf("AI wins!\n");
aiScore++;
winningStreak = 0;
}
printStreakInfo(winningStreak, highestWinningStreak, playerScore);
printf("Player = %d aiScore = %d\n", playerScore, aiScore);
}
}
// We change the old makeAiMove() to this new one
// which will either generate move, a winning move, or a losing move
char makeAiMove(int playerScore, int aiScore, char playerMove)
{
char aiMove;
// This is the value after which, we activate the
// ai win by cheating
int playerWinTolerance = 3;
// This is the value after which, we activate the
// player win by cheating
int playerLossTolerance = 2;
// Compute the difference between the player and ai scores
// If the player's score is larger than the ai score by
// playerWinTolerance (3 in this case),
if(playerScore - aiScore > playerWinTolerance)
{
// make the ai win
aiMove = forceAiWin(playerMove);
}
// If the ai score is larger than the player's score by
// playerLossTolerance (2 in this case),
// then make the ai lose
else if(aiScore - playerScore > playerLossTolerance)
{
aiMove = forceAiLoss(playerMove);
}
// Otherwise, be fair and square
else
{
aiMove = makeRegularAiMove();
}
return aiMove;
}
// This function returns a regular move
// i.e. randomly
char makeRegularAiMove()
{
char aiMove;
int aiMoveIndex = rand() % 3;
if(aiMoveIndex == 0)
{
aiMove = 'r';
}
else if(aiMoveIndex == 1)
{
aiMove = 'p';
}
else if(aiMoveIndex == 2)
{
aiMove = 's';
}
return aiMove;
}
// This function generates the winning move against
// the player's move
char forceAiWin(char playerMove)
{
char aiMove;
if(playerMove == 'r')
{
aiMove = 'p';
}
if(playerMove == 'p')
{
aiMove = 's';
}
if(playerMove == 's')
{
aiMove = 'r';
}
return aiMove;
}
// This function generates the losing move against
// the player's move
char forceAiLoss(char playerMove)
{
char aiMove;
if(playerMove == 'r')
{
aiMove = 's';
}
if(playerMove == 'p')
{
aiMove = 'r';
}
if(playerMove == 's')
{
aiMove = 'p';
}
return aiMove;
}
void printMoves(char playerMove, char aiMove)
{
if(playerMove == 'r')
{
printf("Player chose rock.\n");
}
else if(playerMove == 'p')
{
printf("Player chose paper.\n");
}
else
{
printf("Player chose scissors.\n");
}
if(aiMove == 'r')
{
printf("AI chose rock.\n");
}
else if(aiMove == 'p')
{
printf("AI chose paper.\n");
}
else
{
printf("Ai chose scissors.\n");
}
}
void printStreakInfo(int winningStreak, int highestWinningStreak, int playerScore)
{
if(winningStreak == 0 && playerScore > 0)
{
printf("You have lost your winning streak!\n");
}
else if(winningStreak > 0)
{
printf("Your winning streak is %d.\n", winningStreak);
}
printf("Your highest winning streak is %d.\n", highestWinningStreak);
}