In this project, you’ll be implementing (most of) the game of Mastermind. It’s a simple logic game where you try to guess a random “password” of four colors. (You can easily find online and app versions of this game, but most of them are pretty hard because they use 6 colors.)

Each guess you make will be evaluated for correctness. You are told two things:

For example, if the password is red-red-green-blue (rrgb), and you guess green-red-blue-green (grbg), then:

I implemented this “answer-checking” algorithm for you cause wow is it annoyingly tricky and not actually that interesting to implement in assembly. But just about everything else, you get to write :)


Grading Rubric

The numbers in brackets are point values.


0. Getting Started

Right click and download this .asm file, and rename it so it’s your username.

Open it, and replace the comment at the top with your name and username. If you assemble and run it right now, it does nothing.

Points of interest:


1. How the code works

The main loop continues while playing is true. To implement “boolean” variables like this, we use integers with the following semantics:

(There’s also a boolean player_won variable which works the same way.)

Here’s the main idea (details below):

  1. The game asks how many colors should be used, to select the difficulty, and puts it in num_colors.
    • This is not how long the password is! This is “how many possibilities for each position in the password.”
    • The length of the password is the constant PUZZLE_SIZE.
  2. It generates a random puzzle.
  3. If GRADER_MODE is not 0, it shows the puzzle solution so the grader can see.
  4. It plays one game, where the player has NUM_TRIES tries to guess the password. For each try:
    1. It asks the player for their guess, which is a string like "rbgo".
    2. It converts that string to an array of color values (using the color constants RED, GREEN etc.)
    3. It checks if the guess matches the generated puzzle. If it matches, the player wins.
    4. If the player runs out of tries, the player loses.
  5. It asks if they want to play again, and if yes, it goes back to the first step.

2. Generating the puzzle

Below is pseudocode for the first four empty functions. Read these notes before you get started, then see what the program should do after the pseudocode.

void ask_for_num_colors() {
    do {
        print("How many colors?: ");
        num_colors = syscall_5();
    } while(num_colors < MIN_COLORS || num_colors > MAX_COLORS);
}

void generate_puzzle() {
    for(int i = 0; i < PUZZLE_SIZE; i++) {
        puzzle[i] = syscall_42(0, num_colors)
    }
}

void show_solution_to_grader() {
    if(GRADER_MODE != 0) {
        print("(SOLUTION: ");
        show_solution();
        print(")\n");
    }
}

void show_solution() {
    for(int i = 0; i < PUZZLE_SIZE; i++) {
        syscall_11(color_names[puzzle[i]]);
    }
}

Checkpoint: see if your program works right

If you set GRADER_MODE to 1 (make that line .eqv GRADER_MODE 1), your program should do something like:

How many colors?: 2
How many colors?: 9
How many colors?: -3
How many colors?: 4
(SOLUTION: yyyb)

-- program is finished running --

That is, it keeps asking “how many colors?” until the user types something in the range MIN_COLORS to MAX_COLORS (3 to 6), generates a puzzle, and shows the solution as a 4-character string.


3. User input

Let’s skip play_game for now and move onto game_prompt, convert_guess, and char_to_color.

In play_game, let’s put some testing code for now (you’ll remove it later).:

play_game:
push ra
    # TESTING CODE ----------------------------------------
    _test_loop:
        jal game_prompt
        jal convert_guess
        # print out whether or not it succeeded
        move a0, v0
        li v0, 1
        syscall

        li a0, '\n'
        li v0, 11
        syscall
    j _test_loop
    # --------------------------------- END OF TESTING CODE
pop ra
jr  ra

Then, you can implement the functions based on the pseudocode below. Notes:

int game_prompt() {
    print("(");
    print(tries_left);
    print(" tries remaining) Enter your guess: ");
    // since read_line puts its return value in v0, you don't
    // have to do anything to return its value. just call it as
    // the last thing you do in this function before pop ra/jr ra.
    return read_line();
}

bool convert_guess() {
    for(int i = 0; i < PUZZLE_SIZE; i++) {
        int color = char_to_color(str_input[i]);

        if(color == INVALID || color >= num_colors) {
            return false;
        } else {
            guess[i] = color;
        }
    }

    return true;
}

int char_to_color(char c) {
    switch(c) {
        case 'r': case 'R': return RED;
        case 'g': case 'G': return GREEN;
        case 'b': case 'B': return BLUE;
        case 'y': case 'Y': return YELLOW;
        case 'o': case 'O': return ORANGE;
        case 'p': case 'P': return PURPLE;
        default:            return INVALID;
    }
}

Checkpoint: see if your program works right

The testing code in play_game just repeatedly calls game_prompt and convert_guess, printing out the return value from convert_guess (a 1 or a 0). 1 means the guess is valid, 0 means it’s invalid. So for example:

How many colors?: 3
(SOLUTION: brbg)
(0 tries remaining) Enter your guess: ffff
0
(0 tries remaining) Enter your guess: rbba
0
(0 tries remaining) Enter your guess: yopy
0
(0 tries remaining) Enter your guess: rgbb
1
(0 tries remaining) Enter your guess:

You can see that guesses which use non-color characters are invalid, as are colors which are beyond num_colors.


4. The game itself!

Now that you’ve implemented all the helper functions, you can make the actual game!

Replace the testing code in play_game with this. You’re on your own now…

void play_game() {
    // both of these are global variables I gave you!
    player_won = false;
    tries_left = NUM_TRIES;

    while(tries_left > 0) {
        // keep asking until they enter something the right length.
        while(game_prompt() != PUZZLE_SIZE) {
            print("Enter ");
            print(PUZZLE_SIZE);
            print(" letters.\n");
        }

        if(convert_guess()) {
            // I wrote check_guess for you.
            check_guess();

            if(player_won) {
                break; // exit the while loop
            } else {
                tries_left--;
            }
        } else {
            print("Invalid guess.\n");
        }
    }
}

Checkpoint: see if your program works right


5. Game over and playing again

These last two functions are easy.

void game_over_message() {
    if(player_won) {
        print("That's right! You win!\n");
    } else {
        print("Sorry, you're out of guesses...\nThe solution was: ");
        show_solution(); // you wrote this already!
        print("\n");
    }
}

void ask_play_again() {
    // loop until they hit y or n
    while(true) {
        print("Play again (y/n)?: ");
        switch(syscall_12()) {
            case 'y': case 'Y':
                playing = 1;
                return;

            case 'n': case 'N':
                playing = 0;
                return;

            default:
                // do nothing, loop again
        }
    }
}

And with that, your program is done!


Submission

Make sure your file is named correctly. My username is jfb42, so:

Submit here. Drag your asm file into your browser to upload. If you can see your file in the folder, you uploaded it correctly!

You can also re-upload to resubmit. It will overwrite your old submission (but we can still access the old one through Box).

Just so we’re clear, if you submit on Saturday, and then resubmit on Sunday, you will get the late penalty. But hey, if you submit a 60%-worthy project on Saturday and then a 90%-worthy one on Sunday, minus the 10% penalty, that’s still an 80%!