Lesson 10 – Drawing on The Console

Believe it or not, I can actually draw.
Jean-Michel Basquiat

Practical Programming

As we learned in previous lessons, programmers often build libraries of functions to provide a “customized” version of C/C++ language. Today, we are going to build our own library of functions to draw things on the console. Drawing on the console is not a part of C/C++ standard and the underlying functions we will be using are specific to the Microsoft Windows API. The Linux, Mac OSX or other operating systems will require a different implementation.

Microsoft Windows API is very well documented through Microsoft Developer Network (MSDN). We are going to look at the Consoles section and the Console Functions. But before we jump into the implementation, lets define what the console is and what functions do we want to implement.

A console is a two dimensional rectangular screen with (0,0) point in the top left corner. X-axis goes from left to right and Y axis goes from top to bottom. A console has a size – the rectangular width and height. Only the points inside this rectangular are visible to the user. A console also has a notion of “cursor” – a point on the console where we can print a character. When we print a character, the cursor moves one position to the right. If we hit the console’s right border, then cursor moves to the first position on the next’s line. To draw on the console, we would need to implement the following functions:

SIZE ConsoleGetSize();       // gets the size of the console
COORD ConsoleGetCursor();    // gets the current position of the cursor
void ConsoleSetCursor(COORD p); // moves cursor to point p
void ConsolePrint(char ch);  // prints a character at the current cursor’s position
void ConsoleClear();         // clears the console

To implement the first two functions, we will need to use GetConsoleScreenBufferInfo() function. This function queries the Windows console object and returns back information about the console in the CONSOLE_SCREEN_BUFFER_INFO structure:

  • the current console size is returned in the dwSize field;
  • the current cursor position is returned in the dwCursorPosition field.

We will also need to use GetStdHandle() function to retrieve the “handle” of the current console. An application may use multiple consoles to improve performance and make rendering smoother. For now, let’s simply use the main console. Finally, don’t forget to include <windows.h> file that contains definition for the Windows API functions we are going to use!

// returns a console size or invalid size {-1,-1} on error
SIZE ConsoleGetSize() {
    HANDLE hConsole;
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    SIZE szError = {-1, -1}; // special return value to indicate error

    // get special console handle to identify the console
    hConsole = GetStdHandle( STD_OUTPUT_HANDLE );
    if(hConsole == INVALID_HANDLE_VALUE) {
        return szError; // the invalid size value to indicate error
    }

    // get information about console
    if (GetConsoleScreenBufferInfo(hConsole, &csbi) == 0) {
       return szError; // the invalid size value to indicate error
    }

    // done
    return csbi.dwSize;
}

Can you write a similar function ConsoleGetCursor() that returns dwCursorPosition field instead of the dwSize field?

To implement the ConsoleSetCursor() function we will need to use  SetConsoleCursorPosition() function from Microsoft Windows API. Again, we will need first to get the console’s handle (this is the first parameter for the SetConsoleCursorPosition() function). The rest should be easy! Can you write ConsoleSetCursor() yourself?

Finally, you already know how to implement ConsolePrint() and ConsoleClear() functions: use the standard “cout” to print a character on the console and use the system(“cls”) call to clear the console!

Now we have all the console functions to draw a few things! Checkout a few examples of ASCII art – drawing on the screen with symbols instead of pixels for inspirations.

Exercises

1) Create console.h and console.cpp files in your project. Put functions’ declarations in the .h file and function implementations into .cpp file. Make sure to “add” these two files to all the other projects you will build for this lesson.
2) Write ConsoleGetCursor(), ConsoleSetCursor(), ConsolePrint() and ConsoleClear () functions.
3) Draw a picture of a tree in the middle of the screen (feel free to change it!):

\/  \/
 \  /
  \/
  ||

4) Draw a car. Can you draw an animation of a car going from the left to the right side of the screen:

   _
_/___\_
o     o

5) Draw an animation of a man walking form the left to the right side of the screen:

  0        0      0       0
 /|\      /|\    /|\     /|\
 / \       |\     |      /|

6) Draw graphics of the following functions (use ‘*’ for the points of the graph):

a) y(x) = 5
b) y(x) = x^2
c) y(x) = sqrt(x)

7) Add rocket animation to the Lunar Lander game.

 / \
/   \
\   /
 |_|
/   \
Posted in Practical Programming | 1 Comment

Lesson 9 – Three Ways to Pass Parameters To a Function

Curiouser and curiouser!
Lewis Carroll, Alice in Wonderland

Practical Programming

When you call a function and pass into the function values for the function’s input parameters, the C/C++ compiler creates a copy of all the input parameters’ values and only then calls a function to process these values. For example, if you have a function defined as

void DrawCircle(COORD p, int r);

And you perform a call

COORD x = {10, 15};
DrawCircle(x, 23);

Then the computer will perform the following steps:

  1. Allocate memory for input parameters x and r.
  2. Copy values p => x, 23 => r.
  3. Execute the function DrawCircle().

This method of passing input parameters to a function is called passing by value because only values are passed into the function. It works great for built-in types since these types are small but it quickly becomes expensive for more complex struct types. It is not unusual to have a struct with several dozens fields. Allocating memory for a large struct and copying all the fields is a costly process. Thus, C/C++ language provides another methods for passing parameters to a function: passing by reference and passing by pointer.

To declare an input parameter as passed by reference, you need to put an ampersand ‘&’ in front of the variable name in the function declaration:

void DrawCircle(COORD & p, int r);

When you call the function in the usual way:

COORD x = {10, 15};
DrawCircle(x, 23);

The computer will no longer allocate memory for the input parameter p and will simply create a reference (alias) that maps variable p to the function’s input parameter x. Note that parameter r is still passed by value: it is just an integer and passing it by value is not slower than passing it by reference.

When you pass a parameter by reference, the function has access to the original variable and can modify it. This comes handy when you need to return multiple values from a function. For example, it is very common to have a function return a status code that indicates if there are any errors during function execution while the actual data is returned from a function through parameters passed by reference. For example, the following function will return status code from the function and the screen size will be returned in sz input parameter:

STATUS_CODE GetScreenSize(SIZE & sz);
...
SIZE screenSize;
if(GetScreenSize(screenSize) == 0) {
    cout << “Screen size: “ << screenSize.w << “ x ” << screenSize.h << endl;
} else {
    cout << “Error getting screen size” << endl;
}

If the function doesn’t modify input parameters, it can declare this by using the const keyword. For example, the DrawCircle function doesn’t need to modify its parameters and we can declare it as follows (note that there is no point in using const keyword for parameters passed by value because function can’t modify the variables regardless):

void DrawCircle(const COORD & p, int r);

It is usually a good idea to always mark with const the input parameters that the function doesn’t modify. This gives a extra clue to the compiler and the developer of how this function should be used and it helps to avoid stupid mistakes.

The last but not the least option for passing input parameters is passing by pointer. It is very similar to passing by reference: no value copying happens, the function gets access to the variable passed into the function and can potentially modify it. The syntax for passing by pointer is a little bit more complex than syntax for passing by reference and requires changes for function declaration and function call. The star ‘*’ is used in the function declaration to indicate that this parameter is passed by pointer and ampersand ‘&’ is used during the function call to get a pointer from a variable:

STATUS_CODE GetScreenSize(SIZE *sz);
...
SIZE screenSize;
if(GetScreenSize(&screenSize) == 0) {
    cout << “Screen size: “ << screenSize.w << “ x ” << screenSize.h << endl;
} else {
    cout << “Error getting screen size” << endl;
}

This is probably somewhat confusing but we are going to discuss pointers in great details later. Passing by reference is the preferred C++ way to avoid input parameters copying or return extra data from a function . Passing by pointers is the “old” C way used in C libraries and APIs (for example, Windows API is a C API).

Exercises

1) Look at the following functions and describe how input parameters are passed in and which parameters can be modified by the function:

bool GetScreenInfo(HANDLE hScreen, SCREEN_INFO & info);
bool SetScreenInfo(HANDLE hScreen, const SCREEN_INFO & info);
bool GetScreenInfo(HANDLE hScreen, SCREEN_INFO * info);
bool SetScreenInfo(HANDLE hScreen, SCREEN_INFO const * info);

2) On a piece of paper, write example of how you would call each of the functions from 1).

3) Describe the meaning of the parameters for the “main” function (note that “char *” type is a string and that “char **” is simply a pointer to a string):

int main(int argc, char const ** argv);

4) Modify the Lunar Lander program and use passing by reference where appropriate. Don’t forget to use const keyword when function doesn’t modify input parameters passed by reference.

Posted in Practical Programming | 1 Comment

Lesson 8 – Defining Your Own Data Types: struct

The difference between a bad programmer and a good one is whether he considers his code or his data structures more important. Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
Linus Torvalds

Practical Programming

In addition to fundamental types built into the language (integer, float, character, …) the C/C++ language allows you to create custom types. Custom types are used to combine related variables and to extend the C/C++ language to simplify it and make it easier to use when solving a particular problem. For example, if you are writing an application that draws pictures on the computer screen, then many of your functions will require screen coordinates (x, y) as the input parameters:

// draw a line between points (x1,y1) and (x2,y2);
void DrawLine(int x1, int y1, int x2, int y2);

// draw a circle of radius r with center in point (x,y)
void DrawCircle(int x, int y, int r);

Repeating “int x, int y” everywhere is confusing and error prone. Can you understand the following code without looking at the functions definitions?

DrawLine(114, 124, 125, 565); // is it x1, y1, x2, y2 or x1, x2, y1, y2 or ???
DrawCircle(56, 42, 67); // where is radius? first? or last? or in the middle?

Instead, you can define a new custom type COORD using the struct keyword. In our new type the two individual coordinates are “combined” together to create a new entity:

struct COORD {
  int x;
  int y;
};

We can use our new data type anywhere we use built-in types:

// draw a line between points p1 and p2
void DrawLine(struct COORD p1, struct COORD p2);  

// draw a circle of radius r with center in point p
void DrawCircle(struct COORD p, int r); 

... 

struct COORD p1 = { 114, 124 };
struct COORD p2 = { 125, 565 };
struct COORD p3 = { 56, 42 };

DrawLine(p1, p2);
DrawCircle(p3, 67);

You can access individual “fields” in the structure by using the “dot” notation:

struct COORD p;

p.x = 123;
p.y = 124;

cout << “The coordinates are x = “ << p.x << “ and y = “ << p.y << endl;
if(p.x > p.y) {
  cout << “The x coordinate is greater than y” << endl;
} else if(p.y > p.x) {
  cout << “The y coordinate is greater than x” << endl;
}

The structures are simple collection of individual fields and can include fields of different types (including other structures themselves!):

// define a structure for a circle: center point + radius
struct CIRCLE {
  COORD center_point;
  int radius;
};

// define size of something: two fields - witdh (w) and height (h)
struct SIZE {
  int w;
  int h;
};

// define a structure for a rectangle: top left corner + size
struct RECT {
  COORD top_left_corner;
  SIZE size;
};

To access fields in complex data structures, simply use the “dot” on each “level”:

struct RECT foo;
foo. top_left_corner.x = 120;
foo. top_left_corner.y = 435;
foo. size.w = 14;
foo. size.h = 94;

Finally, to make the code look nicer, you can use “typedef” to assign an alternative name (alias) to a type (and remove the need to use “struct” keyword everywhere!):

typedef struct CIRCLE CIRCLE;

...

void DrawCircle(CIRCLE c);

...

CIRCLE c;
c.center.x = 56;
c.center.y = 89;
c.radius = 10;
DrawCircle(c);

You can even use typedef and struct together in one statement!

typedef struct COORD {
  int x;
  int y;
} COORD;

In this example the “struct COORD { … }” part defines the type and the rest is a standard “typedef“.

You can use typedef to assign an alias to any type including the built-in types. For example, to make a program more readable you can typedef integer type to a special ERROR_CODE type to clearly distinguish error codes from all other possible usages for integers:

typedef int ERROR_CODE;

ERROR_CODE DoSomething(); // the function returns an error code, nothing else!

Using struct and typedef allows a programmer to “customize” the C/C++ language and create better abstractions for solving a particular problem. In the future, we will be looking at different libraries (collections of functions) that can be used as building blocks for simplifying common tasks. All the good libraries extensively use struct and typedef to provide a simple yet powerful interface for developers.

Usually the data structures design is hard to change and it lives longer than the algorithm. A good data structures design makes programming easy and enjoyable. A bad data structures design leads to nightmares and bugs in the code.

Exercises

1) Write a function that calculates a distance between two given points.
2) Write a function that calculates the middle point between the two given points.
3) Write functions that calculates the area of a circle and a rectangle.
4) Define structures for 3-dimensional coordinates, a sphere, and a cube. Write functions to calculate volume of a sphere and a cube.
5) Define a data structure for 
a Lunar Module from the game you wrote. Refactor the game to use the new data structure. Try to separate the code responsible for game physics from the code that reads/writes data to/from user.

 

Posted in Practical Programming | 1 Comment