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 void functions 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:

  • calculateArea is a function that performs a calculation and returns a result.

  • printResults is 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 0 is conventionally used to indicate that the program has completed successfully without errors. The operating system interprets this 0 value 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 main to determine if a program executed successfully. Returning 0 makes it compatible with these systems and helps ensure that other processes or scripts function as expected.

  • Standardized Convention: According to the C standard, main has an int return type, meaning it should return an integer. Following the convention of returning 0 for 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 calls finalCalculation with this modified value and further modifies the result by adding 5.

  • startCalculations: This function doubles its input, calls processIntermediate with this value, and then adjusts the result by subtracting 3.

  • main: This function calls startCalculations with an initial input and prints the final result.

The call sequence creates a call depth of 3:

  • main calls startCalculations (depth 1)

  • startCalculations calls processIntermediate (depth 2)

  • processIntermediate calls finalCalculation (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 in stdio.h.

  • scanf: Used for input, defined in stdio.h.

  • rand: Used to generate pseudo-random numbers, defined in stdlib.h.

  • sqrt: Used to calculate the square root of a number, defined in math.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:

  1. Each player chooses one of the three moves: rock, paper, or scissors.

  2. The moves are revealed simultaneously.

  3. 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);
}

Last modified: Saturday, 23 November 2024, 7:21 PM