xhartae/chapters/chapter_2.c

339 lines
13 KiB
C
Raw Normal View History

/*
Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic
Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation.
And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version.
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
*/
#ifndef CHAPTER_2_SOURCE
#define CHAPTER_2_SOURCE
#include "chapter_2.h" // We're copying function and variable declarations from this file here. This shouldn't be copied twice, more on that later...
/*
Function 'hello_world_0' uses (rougly said) a system call instead of a function, but I don't even know what will different compilers do to that line of code. What's a system call?
Well, when you're writing assembly, you have some general purpose registers, on mine and your machine, there's 16 of them, and you move some data in them, then use 'syscall'
instruction, which magically does something, like open or close a file descriptor, read or write to it, but more on that later! A lot more... Important thing to know is that when
you use keyword 'sizeof' on a string, it'll count null terminator, so it'll have value 14, and type 'size_t'. It's provided in <unistd.h> header file.
@C
2023-11-10 13:30:15 -05:00
write ( // mov rax 1 ; Literal of Linux write system call, defined internally.
STDOUT_FILENO, // mov rdi 1 ; Literal of standard output file descriptor, defined as 'STDOUT_FILENO'.
"Hello world!\n", // mov rsi X ; Address of our string.
sizeof ("Hello world!\n") // mov rdx [Y] ; Literal of size of our string.
); // syscall ; Ask kernel to do some work.
@
We'll talk about assembly a lot in future chapters, but you should filter that out of your brain until you learn C well...
*/
void hello_world_0 (void) {
write (STDOUT_FILENO, "Hello world!\n", sizeof ("Hello world!\n"));
}
/*
In function 'hello_world_1' we're using function 'puts' this time, which is considered unsafe because it'll fail when input string isn't null terminated. Keep in mind that it also
prints line feed (new line) on the end. Why I call it line feed? Some terminals and text editors distinguish carriage return ('\r' / 13 / CR) and line feed ('\n' / 10 / LF), and
some of them use both, like old teleprinters used to do, where the name 'tty' on UNIX-based operating systems comes from.
*/
void hello_world_1 (void) {
puts ("Hello world!");
}
/*
Now, in function 'hello_world_2' and function 'hello_world_3', we're using 'printf' function, which is in my opinion important for debugging. It has many variations, so we'll have
a separate chapter only about it. Know that it's a variadic function, which means that it can take more than one input argument, and later we'll learn to make our own variadic
functions as well. It's also a bit unsafe, if you're not careful how you use it, but nothing drastic can happen, most compilers catch those kinds of errors.
*/
void hello_world_2 (void) {
printf ("Hello world!\n");
}
/*
It's also worth noting that we're using it's arguments in function 'hello_world_3', function 'printf' will print characters one by one from its' format, until it encounters '%',
if the next character is '%', it prints one '%', if it's 's', then it'll print a string, which we provide in the next argument. When compiler optimizations are turned on, it will
change all sorts of things here, since it's obvious that we're using simple string in this example.
*/
void hello_world_3 (void) {
printf ("%s %s!\n", "Hello", "world");
}
/*
Lastly, we have a global variable, some people like to call it that, and consider it evil. It's not evil if you know the pros and cons of using them, which causes intellectual
overhead for certain sort of people. Pros, makes your code shorter and easier to refactor. Cons, it can potentially make your code less safe if you don't know what you're doing.
You should use them, external variables as I like to call them, when you're working on your programs or in a small group, but they can lead to bad things if you're working with a
lot of people, someone could change it in some bad place, and cause a bug that's difficult to track.
We could initialize it also in any part of our code later, in some function for example, as following:
@C
// One by one:
hello_world [0] = hello_world_0;
hello_world [1] = hello_world_1;
hello_world [2] = hello_world_2;
hello_world [3] = hello_world_3;
// Or use it for some completely unrelated function if we wanted to, as long as function "kind" is same, they both take no input and no output:
static void unrelated_function (void) {
puts ("I couldn't care less...");
exit (EXIT_FAILURE);
}
hello_world [2] = unrelated_function;
@
Just know that an array(s) of function pointers is my favorite kind of variable for bot AI in simple games, index can be derived from bot state.
*/
void (* hello_world [4]) (void) = {
hello_world_0,
hello_world_1,
hello_world_2,
hello_world_3
};
2023-11-10 13:30:15 -05:00
/*
Now, we'll talk more about cursed functions! I broke code formatting rules for this, but it's worth it, time to learn.
*/
static char curses_format [CURSES_FORMAT + 1] = "\033[-;3-m-\033[0m";
static char curses_cursor [CURSES_CURSOR + 1] = "\033[---;---H";
static char * curses_screen = NULL;
static int curses_action_count = 0;
static int * curses_activator = NULL;
static void (* * curses_action) (void) = NULL;
static struct termios curses_old_terminal;
static struct termios curses_new_terminal;
static void curses_free (void) {
curses_screen = deallocate (curses_screen);
curses_activator = deallocate (curses_activator);
curses_action = deallocate (curses_action);
terminal_clear ();
fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_old_terminal) == -1, "tcsetattr: Failed to set default terminal attributes.");
}
static char * curses_screen_offset (int x, int y) {
// log_in (LOG_FAILURE, x <= -1, "curses_screen_offset: X position is below the lower bound.");
// log_in (LOG_FAILURE, y <= -1, "curses_screen_offset: Y position is below the lower bound.");
// log_in (LOG_FAILURE, x >= curses_screen_width, "curses_screen_offset: X position is above the upper bound.");
// log_in (LOG_FAILURE, y >= curses_screen_height, "curses_screen_offset: Y position is above the upper bound.");
limit (& x, 0, curses_screen_width - 1);
limit (& y, 0, curses_screen_height - 1);
return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]);
}
static char * curses_format_character (char character, int colour, int effect) {
// log_in (LOG_WARNING, character_is_invisible (character), "curses_format_character: Can not format invisible characters.");
// log_in (LOG_FAILURE, colour >= COLOUR_COUNT, "curses_format_character: Colour is invalid enumeration value.");
// log_in (LOG_FAILURE, effect >= EFFECT_COUNT, "curses_format_character: Effect is invalid enumeration value.");
if (character_is_invisible (character) != 0) {
character = ' ';
}
colour %= COLOUR_COUNT;
effect %= EFFECT_COUNT;
switch (effect) {
case EFFECT_NORMAL: effect = 0; break;
case EFFECT_BOLD: effect = 1; break;
case EFFECT_ITALIC: effect = 3; break;
case EFFECT_UNDERLINE: effect = 4; break;
case EFFECT_BLINK: effect = 5; break;
case EFFECT_REVERSE: effect = 7; break;
default: effect = 0; break;
}
curses_format [2] = (char) effect + '0';
curses_format [5] = (char) colour + '0';
curses_format [7] = character;
// log_out ("curses.log");
return (curses_format);
}
static void curses_idle (void) { return; } // If you have a lot of short functions that are intended to be in array of function pointers, you can align them like this.
static void curses_exit (void) { curses_active = 0; }
int curses_realign_x = 0;
int curses_realign_y = 0;
int curses_tab_width = 8;
int curses_character = 0;
int curses_signal = SIGNAL_NONE;
int curses_screen_width = 0;
int curses_screen_height = 0;
int curses_active = 1;
/*
External function definitions, those found in "chapter_2.h" header file.
*/
void curses_configure (void) {
struct winsize screen_dimension;
atexit (curses_free);
fatal_failure (ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension) == -1, "ioctl: Failed to get terminal dimensions.");
curses_screen_width = (int) screen_dimension.ws_col;
curses_screen_height = (int) screen_dimension.ws_row;
fatal_failure (tcgetattr (STDIN_FILENO, & curses_old_terminal) == -1, "tcgetattr: Failed to get default terminal attributes.");
curses_new_terminal = curses_old_terminal;
curses_new_terminal.c_cc [VMIN] = (unsigned char) 0;
curses_new_terminal.c_cc [VTIME] = (unsigned char) 1;
curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
curses_new_terminal.c_oflag &= (unsigned int) ~(OPOST);
curses_new_terminal.c_cflag |= (unsigned int) (CS8);
curses_new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG);
fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_new_terminal) == -1, "tcsetattr: Failed to set reverse terminal attributes.");
curses_screen = allocate (CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR + 1);
curses_bind (SIGNAL_ESCAPE, curses_exit);
terminal_clear ();
string_copy (& curses_screen [0], "\033[H");
}
void curses_synchronize (void) {
int signal;
curses_signal = curses_character = signal = 0;
out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR);
in (& signal, 4);
curses_character = signal;
if (signal == '\033') {
curses_signal |= SIGNAL_ESCAPE;
} else if (character_is_digit (signal) != 0) {
curses_signal |= SIGNAL_0 + (int) (signal - '0');
} else if (character_is_lowercase (signal) != 0) {
curses_signal |= SIGNAL_A + (int) (signal - 'a');
} else if (character_is_uppercase (signal) != 0) {
curses_signal |= SIGNAL_A + (int) (signal - 'A');
curses_signal |= SIGNAL_SHIFT;
} else {
curses_signal = SIGNAL_NONE;
}
for (signal = 0; signal != curses_action_count; ++signal) {
if (curses_signal == curses_activator [signal]) {
curses_action [signal] ();
}
}
}
void curses_bind (int signal, void (* action) (void)) {
++curses_action_count;
curses_activator = reallocate (curses_activator, curses_action_count * (int) sizeof (* curses_activator));
curses_action = reallocate (curses_action, curses_action_count * (int) sizeof (* curses_action));
curses_activator [curses_action_count - 1] = signal;
curses_action [curses_action_count - 1] = action;
}
void curses_unbind (int signal) {
(void) signal;
curses_activator [curses_action_count - 1] = SIGNAL_NONE;
curses_action [curses_action_count - 1] = curses_idle;
--curses_action_count;
}
void curses_render_cursor (int x, int y) {
x %= 1000;
y %= 1000;
string_copy_limit (curses_cursor + 2, string_realign (number_to_string (y + 1), 3, '0'), 3);
string_copy_limit (curses_cursor + 6, string_realign (number_to_string (x + 1), 3, '0'), 3);
string_copy_limit (& curses_screen [CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height], curses_cursor, CURSES_CURSOR);
}
void curses_render_character (char character, int colour, int effect, int x, int y) {
if ((x >= curses_screen_width) || (y >= curses_screen_height)) {
return;
}
string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT);
}
void curses_render_background (char character, int colour, int effect) {
int x, y;
for (y = 0; y != curses_screen_height; ++y) {
for (x = 0; x != curses_screen_width; ++x) {
curses_render_character (character, colour, effect, x, y);
}
}
}
/*
We've mentioned before, in chapter zero, that you can implement 'string_*' functions by 'string_*_limit', and here's an example of that.
*/
int curses_render_string (char * string, int colour, int effect, int x, int y) {
return (curses_render_string_limit (string, string_length (string), colour, effect, x, y));
}
int curses_render_number (int number, int colour, int effect, int x, int y) {
return (curses_render_number_limit (number, 32, colour, effect, x, y));
}
int curses_render_string_limit (char * string, int limit, int colour, int effect, int x, int y) {
int offset;
for (offset = 0; offset != limit; ++offset) {
if (string [offset] == '\n') {
x = 0;
y += 1;
} else if (string [offset] == '\t') {
x += 8;
} else {
curses_render_character (string [offset], colour, effect, x, y);
x += 1;
}
}
return (limit);
}
int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y) {
(void) number;
(void) limit;
(void) colour;
(void) effect;
(void) x;
(void) y;
return (limit);
}
#endif