Super Mario pyramids in C

Just to give a heads-up to the regular readers of my blog, this post is quite different from the usual topics that I write about.

Long story short, I decided to start learning programming, and as I’ve learned many times throughout the years of running this blog, there is no better way to learn something than by explaining it to others! After all, systemizing knowledge is one of the reasons I run my blog, so don’t be surprised to see some ‘weird’ posts here from now on.

In this post, I’ll give my walkthrough of solving the problem as a way to reinforce what I’ve learned. If by any chance you are a seasoned programmer, you are more than welcome to point out any mistakes or ways to improve for a newbie like me. However, if programming isn’t quite your thing, feel free to simply scroll through!

So, a couple of days ago I started by taking a course on computer science, and one of the first little homework I had was the following:

Toward the beginning of World 1-1 in Nintendo’s Super Mario Brothers, Mario must hop over adjacent pyramids of blocks. Implement a program in C that recreates that pyramid, using hashes (#) for bricks, as in the below:

...#  #
..##  ##
.###  ###
####  ####

And let’s allow the user to decide just how tall the pyramids should be by first prompting them for a positive number between, say, 1 and 8, inclusive.

Super Mario game on Nintendo

So, I’ll start by creating a variable with a type integer called height and the value equal to the return of the get_int function, which prompts a user for a number input. In case you wonder, get_int is a custom function defined in the CS50 library, so I included it in the header:

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int height = get_int("Height: ");
}

Next, I’m adding validation of the user input since we need to accept only a specific range of numbers. To do so, I chose Do While Loop. Why this particular type of loop over the others? From my understanding so far, here are the best use cases for each type of loop:

  • For Loop works best when we know the exact number of repetitions, e. g. in some sort of counter;
  • While Loop works best when the number of repetitions is unknown;
  • Do While is pretty much the same as While Loop, but it’s guaranteed to trigger at least once.

So, here is the code, which in plain English says “Ask for input and keep asking while the provided number is less than one or greater than eight”:

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    do
    {
        int height = get_int("Height: ");
    }
    while (height < 1 || height > 8);
}

Now the problem is when I’m trying to compile the program, I get the following error: use of undeclared identifier ‘height’. This is happening because the variable height is defined within the Do part of the loop, so the While part doesn’t know about it. Basically, everything defined within the curly brackets stays within the curly brackets. This is a known problem of scope.

To solve the scope issue, I simply need to declare the height variable before the loop and outside its curly brackets:

#include <cs50.h>
#include <stdio.h>

int height;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < 1 || height > 8);
}

Note that I don’t need to assign any value to the variable, this is why a simple line int height; does the job. Also, note that in the curly brackets, height no longer needs int before, as I already defined the type of this variable as an integer at the top, and here I’m only assigning this variable a value.

Now this program works, and it only accepts numbers from 1 to 8 inclusive, as per the task description. If I type any negative number, zero, a character, or a number greater than 8, then the program is going to keep asking for another number, thanks to the Do While Loop.

Although this is correct, I don’t quite like the explicit usage of numbers 1 and 8 in the While expression. Why these particular numbers, and not 20 or 50, for example? Well, I surely know this from the problem description, but let’s say if I share this program with a colleague, it might be not so obvious to them. Numbers that appear in code seemingly out of the blue are called Magic Numbers.

To avoid Magic Numbers, I’m going to replace 1 and 8 with two variables, minHeight and maxHeight respectively. Keeping the scope in mind, I declare these variables at the top of the file, and since their values are set by the task rules, I’m giving them the type of const just to indicate that these numbers are the contestants and won’t change:

#include <cs50.h>
#include <stdio.h>

int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);
}

Functionally, this is exactly the same program as above, but I think it now has a slightly better design.

Next, I need to find a way of printing the hashes. If you think about it for a moment, what is a pyramid according to the problem set, for example, 3×3? It’s basically a brick with a length and height of 3, like so:

###
###
###

This obviously isn’t a pyramid shape yet, but we’ll get to it. Let’s think about how to print that brick first. The easiest way would be to simply repeat the printf function that many number of times:

printf("###\n");
printf("###\n");
printf("###\n");

You can see that the rows are identical, and whenever there is something copy-pastable, it might be a good indicator that there is a better solution. The first step would be to replace the rows by For Loop expression:

for (int i = 0; i < 3; i++)
{
    printf("###\n");
}

In this loop, I created a variable named i with a value of 0, and if i is less than three, then it prints the hashes, then it incrementally increases the value of і by 1, and then it checks it again until the expression of i is less than 3 is no longer true. In other words, this code does exactly the same as above – printing three rows of hashes – but in a more clever way.

But even though I’m printing rows dynamically, the number of columns is still set to a very specific number of hashes (three in this case), so I can go further to improve this. To do this, I’ll create another For Loop within that loop using a different variable j:

for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3, j++)
    {
        printf("#");
    }
    printf("\n");
}

Note that now I can type only one hash in the printf function, and the rest is calculated mathematically. This nested structure of a loop within another loop seems exactly what I need for printing the pyramid, conceptually.

However, because the pyramid I need requires a few more parameters to get the actual pyramid shape, I think making this structure would be too large and hard to read. To slightly simplify the process, I’m going to move out the loop that prints rows into its own separate function.

To do so, I’ll add a new function called print_row. What kind of input it should take? It must be a number of hashes in a row to modify the length, so I’ll add one input parameter of a type integer that I call length. And it shouldn’t return any value, it just prints things on the screen, so I’ll keep its return value as void, which basically means nothing.

Going back to my main code now, here is what it looks like now:

#include <cs50.h>
#include <stdio.h>

int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);
}

void print_row(int length)
{

}

Nothing happens as the function literally does nothing so far, but I can work on that now. When I use this function print_row, I can pass in some number, and I can loop it that many times.

#include <cs50.h>
#include <stdio.h>

int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);
}

void print_row(int length)
{
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }
    printf("\n");
}

This is basically the loop I created earlier in the brick example, but I still can’t see its result because this function is not used anywhere yet. Now I can try to use print_row after the Do While loop, and also to pass it the number that a user inputs in the program:

#include <cs50.h>
#include <stdio.h>

void print_row(int length);
int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);
    print_row(height);
}

void print_row(int length)
{
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }
    printf("\n");
}

Note that I had to add the prototype of the print_row function at the top of my file again to solve the scope issue, similar to how I’ve done previously with my variables. In plain English, this program now asks a user to input a number from 1 to 8 and then prints that exact number of hashes in a single line. Why only a single line? Because when I use the print_row function, it gets only one number that a user types in, the variable height, and that’s it.

So now I need to make the print_row function repeat itself some number of times, just like I did earlier in the nested loops example, and kind of wrap it around another loop. How many times the loop should be repeated? As many as the number that I get from a user:

#include <cs50.h>
#include <stdio.h>

void print_row(int length);
int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);

    for (int i = 0; i < height; i++)
    {
        print_row(height);
    }
}

void print_row(int length)
{
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }
    printf("\n");
}

It works, and now I get a brick of size x×x. For example, if I type in number 4, I get this:

####
####
####
####

... and I type in number 8, I get this:

########
########
########
########
########
########
########
########

So I’m getting close, but for now, it’s still a square brick. How to make the rows more dynamic, i.e. print only one hash on the first row, two hashes on the second row, and so forth? I guess instead of using height as my input parameter in print_row(height);, I can replace it with some other variable. I think I can use the variable i, which I use for counting the number of repetitions, however, since it starts with 0, I would get zero hashes on the first line, one hash on the second line, and so on. So to offset this, I’ll tell my program to start counting by i + 1, like so:

#include <cs50.h>
#include <stdio.h>

void print_row(int length);
int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);

    for (int i = 0; i < height; i++)
    {
        print_row(i + 1);
    }
}

void print_row(int length)
{
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }
    printf("\n");
}

And this now produces the following result, for example, if I type in 8:

#
##
###
####
#####
######
#######
########

Half of the pyramid is done, yay! Almost there. The next question is how to add another half of the pyramid and what’s the difference between them.

Let me draw the left half of the pyramid just to look at it for a moment, let’s say with a height of 4:

...#
..##
.###
####

So it seems that I need to calculate not only the number of hashes but also the number of spaces (dots in this case, for the sake of visibility) to offset the hashes on each line.

To do so, I’ll add another input to my print_row function, an integer named spaces, not forgetting to mirror it in the function prototype at the top. Then inside the function, I need to adjust what is printed on every row. According to the pyramid shape, it must be some number of dots (spaces) first, then hashes for the left side of the pyramid, then a gap, and then hashes for the right side of the pyramid. For the right side, no extra spaces are needed after hashes since they won’t be visible anyway.

Also, there is a curious relationship between a row number and the amount of spaces needed to offset the hashes. For example, for the 8th row, no spaces are needed; for the 7th row, only 1 space is needed, and so forth. So my For Loop representing spaces, I’m going to assign the variable i a value of height, and then count downwards, incrementally decreasing the number of spaces by 1.

Lastly, need to not forget to add the second input when I’m calling the print_row function to print both hashes and spaces in each column.

And here is the final code:

// A program in C that recreates the pyramid from Mario using hashes (#) for bricks

#include <cs50.h>
#include <stdio.h>

void print_row(int spaces, int length);
int height;
const int minHeight = 1;
const int maxHeight = 8;
int main(void)
{
    // Prompting a user to input a number to define the height of the pyramid, accepting only numbers from 1 to 8, inclusive
    do
    {
        height = get_int("Height: ");
    }
    while (height < minHeight || height > maxHeight);

    // Printing columns of the pyramid
    for (int i = 0; i < height; i++)
    {
        print_row(i + 1, i + 1);
    }
}

// Printing rows of the pyramid
void print_row(int spaces, int length)
{
    // Printing the dots (spaces) on the left
    for (int i = height; i > spaces; i--)
    {
        printf(" ");
    }

    // Printing hashes for the left side of the pyramid
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }

    // Printing the gap between the sides of the pyramid
    printf("  ");

    // Printing hashes for the right side of the pyramid
    for (int i = 0; i < length; i++)
    {
        printf("#");
    }

    printf("\n");
}

Update

As kindly pointed out by Sergey Khivuk after I published the post, there are at least a few ways to improve the code. I’ll leave his feedback below:

  • Instead of passing two identical parameters to void print_row(int spaces, int length), just pass one and replace spaces with length. The pyramid is always square in terms of width and height anyway.
  • Instead of:
for (int i = 0; i < height; i++)
{
  print_row(i + 1);
}

...just use:

for (int i = 1; i <= height; i++)
{ 
  print_row(i);
}

There will be no need to calculate the parameter every time the function is called.

 366   2 mo   C   CS50   Programming
Next
1 comment
Ivan Ogorelkov 2 mo

Hi Daniel!
I have a few suggestions for your code:

  1. It would be better to move the definitions of variables and constants from the global scope to the main function (global variables are not a good idea in most cases);
  2. You can avoid adding the prototype of the print_row function simply by putting its definition before the main function;
  3. Your main function is defined with the int type, so it should return a value to the system with a code indicating how the program exited. Normal exit is indicated by a 0, so you can simply add “return 0;” at the end of the main function.
Daniel Sokolovskiy 2 mo

For a newbie like me, these little suggestions are pure gold, thank you!

© Daniel Sokolovskiy, 2024
Powered by Aegea