diff --git a/main.c b/main.c index 42d7837..f9dcb43 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,8 @@ +#include +#include #include #include +#include int main() { // Enable UTF-8 by explicitly initializing our locale before initializing @@ -24,15 +27,92 @@ int main() { // Hide the terminal cursor curs_set(0); - printw("Type any character to see it in bold\n"); + printw("Type any character to fill it in an alternating grid\n"); refresh(); - int ch = getch(); - printw("Your character:\n"); - attron(A_BOLD); - printw(" %c\n", ch); - attroff(A_BOLD); - printw("Press any key to exit"); + // 'chtype' is the type of character that ncurses uses. It will be an + // ASCII-like value, if that's what the user hit on the keyboard, but + // 'chtype' is larger than an 8-bit number and could have something else in + // it (some Unicode character, a control character for the terminal, etc.) + chtype ch = getch(); + // We get the dimensions that the terminal is currently set to, so we know + // how big of a buffer to allocate. We'll fill the buffer with some + // characters after we've allocated it. + int term_height = getmaxy(stdscr); + int term_width = getmaxx(stdscr); + assert(term_height >= 0 && term_width >= 0); + // We use 'size_t' when we talk about the size of memory. We also sometimes + // use it when looping over indices in an array, but we won't do that this + // time, since we already have the terminal width and height as regular ints. + size_t term_cells = term_height * term_width; + + // 'calloc' uses the C runtime library to give us a chunk of memory that we + // can use to do whatever we want. The first argument is the number of things + // we'll put into the memory, and the second argument is the size of the + // those things. The total amount of memory it gives us back will be (number + // of guys * size of guys). + // + // There is also another function you may have heard of -- malloc -- which + // does mostly the same thing. The main differences are that 1) malloc does + // not turn all of the memory into zeroes before giving it to us, and 2) + // malloc only takes one argument. + // + // Because malloc doesn't zero the memory for us, you have to make sure that + // you always clear (or write to it) yourself before using it. That wouldn't + // be a problem in our example, though. + // + // Because malloc only takes one argument, you have to do the multiplication + // yourself, and if you want to be safe about it, you have to check to make + // sure the multiplication won't overflow. calloc does that for us. + // + // sizeof is a special thing that returns the size of an expression or type + // *at compile time*. + chtype* buff = calloc(term_cells, sizeof(chtype)); + + // For each row, in the buffer, fill it with an alternating pattern of spaces + // and the character the user typed. + for (int iy = 0; iy < term_height; ++iy) { + // Make a pointer to the start of this line in the buffer. We don't + // actually have to do this -- we could replace line[ix] with (buff + iy * + // term_width + ix), but this makes it easier to see what's going on. + chtype* line = buff + iy * term_width; + for (int ix = 0; ix < term_width; ++ix) { + // Note that 'if' here is being used with a numerical value instead a + // boolean. C doesn't actually have real booleans: a 0 value (whatever + // the number type happens to be, int, char, etc.) is considered 'false', + // and anything else is 'true'. + if ((iy + ix) % 2) { + line[ix] = ' '; + } else { + line[ix] = ch; + } + } + } + + // Loop over each row in the buffer, and send the entire row to ncurses all + // at once. This is the fastest way to draw to the terminal with ncurses. + for (int i = 0; i < term_height; ++i) { + // Move the cursor directly to the start of the row. + move(i, 0); + // Send the entire line at once. If it's too long, it will be truncated + // instead of wrapping. + // + // We use addchnstr instead of addchstr (notice the 'n') because we know + // exactly how long the line is, and we don't have a null terminator in our + // string. If we tried to use addchstr, it would keep trying to read until + // it got to the end of our buffer, and then past the end of our buffer + // into unknown memory, because we don't have a null terminator in it. + addchnstr(buff + i * term_width, term_width); + } + + // We don't need our buffer anymore. We call `free` to return it back to the + // operating system. If we don't do this, and we lose track of our `buff` + // pointer, the memory has leaked, and it can't be reclaimed by the OS until + // the program is terminated. + free(buff); + + // Refresh the terminal to make sure our changes get displayed immediately. refresh(); + // Wair for the user's next input before terminating. getch(); endwin(); return 0;