Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
b61756a718 | |||
12e1108938 |
@ -1,9 +1,14 @@
|
||||
/*
|
||||
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_0_SOURCE // These two, and "#endif" at the end of the file are header guards, we'll talk about them more when the time comes!
|
||||
#define CHAPTER_0_SOURCE
|
||||
|
||||
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free, atexit, rand and exit (some will be used in future chapters).
|
||||
#include <unistd.h> // And this one for read and write.
|
||||
|
||||
#include "chapter_0.h" // We're pasting macros, enumerations and function declarations from header file "chapter_0.h" into this file, at this location.
|
||||
|
||||
/*
|
||||
@ -133,18 +138,17 @@ int string_compare (char * string_0, char * string_1) {
|
||||
int string_length (char * string) {
|
||||
int length;
|
||||
|
||||
fatal_failure (string == NULL, "string_length: String is null pointer."); // If variable 'string' is null pointer, we abort the program.
|
||||
fatal_failure (string == NULL, "string_length: String is null pointer.");
|
||||
|
||||
for (length = 0; string [length] != CHARACTER_NULL; ++length); // In C, strings are null terminated, looping until we see null character is strings' length.
|
||||
for (length = 0; string [length] != CHARACTER_NULL; ++length); // Since in C, strings are null terminated, looping until we see null character is strings' length.
|
||||
|
||||
return (length); // If we pass it non-null terminated string, program will fault!
|
||||
return (length);
|
||||
}
|
||||
|
||||
/*
|
||||
Now, I've implemented "unlimited" versions of string comparison, copying and concatenation different from "limited" versions. They correspond with standard library functions
|
||||
'strcmp', 'strcpy', 'strcat', 'strncmp', 'strncpy' and 'strncat' found in header file <string.h>. In "unlimited" versions, I rely on the fact that we want to apply the operation
|
||||
on entire strings, that those strings are null terminated and I used that in my advantage. For example, function 'string_compare' could be something like example below. Also,
|
||||
notice that 'destination / source' versus 'string_0 / string_1', which do you think is more readable?
|
||||
on entire strings, that those strings are null terminated and I used that in my advantage. For example, function 'string_compare' could be something like this:
|
||||
|
||||
@C
|
||||
int string_compare (char * string_0, char * string_1) {
|
||||
@ -168,51 +172,51 @@ use it to interate the strings, while in "unlimited" versions, we iterate on poi
|
||||
same results, you can use any of them.
|
||||
*/
|
||||
|
||||
int string_compare (char * destination, char * source) {
|
||||
fatal_failure (destination == NULL, "string_compare: Destination string is null pointer."); // This will be seen in next 5 functions too, we don't want NULL here.
|
||||
fatal_failure (source == NULL, "string_compare: Source string is null pointer.");
|
||||
int string_compare (char * string_0, char * string_1) {
|
||||
fatal_failure (string_0 == NULL, "string_compare: Destination string is null pointer."); // This will be seen in next 5 functions too, we don't want NULL here.
|
||||
fatal_failure (string_1 == NULL, "string_compare: Source string is null pointer.");
|
||||
|
||||
for (; (* destination != CHARACTER_NULL) && (* source != CHARACTER_NULL); ++destination, ++source) { // We iterate until either string reaches the null character.
|
||||
if (* destination != * source) { // In case that characters at the same offset are different:
|
||||
return (FALSE); // > We return FALSE, 0, since strings aren't the same...
|
||||
for (; (* string_0 != CHARACTER_NULL) && (* string_1 != CHARACTER_NULL); ++string_0, ++string_1) { // We iterate until either string reaches the null character.
|
||||
if (* string_0 != * string_1) { // In case that characters at the same offset are different:
|
||||
return (FALSE); // > We return FALSE, 0, since strings aren't the same...
|
||||
}
|
||||
}
|
||||
|
||||
if (* destination != * source) { // Now, we'll do one last termination check.
|
||||
if (* string_0 != * string_1) { // Now, we'll do one last termination check.
|
||||
return (FALSE);
|
||||
}
|
||||
|
||||
return (TRUE); // Otherwise, strings are same, we return TRUE, 1.
|
||||
return (TRUE); // Otherwise, strings are same, we return TRUE, 1.
|
||||
}
|
||||
|
||||
char * string_copy (char * destination, char * source) {
|
||||
char * result = destination; // We need to save pointer to destination string before changing it.
|
||||
char * string_copy (char * string_0, char * string_1) {
|
||||
char * result = string_0; // We need to save pointer to destination string before changing it.
|
||||
|
||||
fatal_failure (destination == NULL, "string_copy: Destination string is null pointer.");
|
||||
fatal_failure (source == NULL, "string_copy: Source string is null pointer.");
|
||||
fatal_failure (string_0 == NULL, "string_copy: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_copy: Source string is null pointer.");
|
||||
|
||||
for (; * source != CHARACTER_NULL; ++destination, ++source) { // This time and in next function, we iterate only source string.
|
||||
* destination = * source; // And we assign character at the same offset to destination string (aka copy it).
|
||||
for (; * string_1 != CHARACTER_NULL; ++string_0, ++string_1) { // This time and in next function, we iterate only source string.
|
||||
* string_0 = * string_1; // And we assign character at the same offset to destination string (aka copy it).
|
||||
}
|
||||
|
||||
* destination = CHARACTER_NULL; // Copying null termination, since the loop stopped on that condition.
|
||||
* string_0 = CHARACTER_NULL; // Copying null termination, since the loop stopped on that condition.
|
||||
|
||||
return (result); // Lastly we return the destination string, in order to be able to bind functions.
|
||||
return (result); // Lastly, we return the destination string, in order to be able to bind functions.
|
||||
}
|
||||
|
||||
char * string_concatenate (char * destination, char * source) {
|
||||
char * result = destination;
|
||||
char * string_concatenate (char * string_0, char * string_1) {
|
||||
char * result = string_0;
|
||||
|
||||
fatal_failure (destination == NULL, "string_concatenate: Destination string is null pointer.");
|
||||
fatal_failure (source == NULL, "string_concatenate: Source string is null pointer.");
|
||||
fatal_failure (string_0 == NULL, "string_concatenate: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_concatenate: Source string is null pointer.");
|
||||
|
||||
destination += string_length (destination); // We'll first offset destination string to the end of it.
|
||||
// Because we want to start copying from the end, aka concatenate it.
|
||||
for (; * source != CHARACTER_NULL; ++destination, ++source) { // The rest of the function is same as string_copy, so:
|
||||
* destination = * source; // We could even use it here, but that defies the purpose of learning now.
|
||||
string_0 += string_length (string_0); // We'll first offset destination string to the end of it.
|
||||
// Because we want to start copying from the end, aka concatenate it.
|
||||
for (; * string_1 != CHARACTER_NULL; ++string_0, ++string_1) { // The rest of the function is same as string_copy, so:
|
||||
* string_0 = * string_1; // We could even use it here, but that defies the purpose of learning now.
|
||||
}
|
||||
|
||||
* destination = CHARACTER_NULL; // Again, assign null termination.
|
||||
* string_0 = CHARACTER_NULL; // Again, assign null termination.
|
||||
|
||||
return (result);
|
||||
}
|
||||
@ -228,54 +232,56 @@ write comments for 'string_copy_limit', 'string_concatenate_limit' and 'string_r
|
||||
current knowledge of C language.
|
||||
*/
|
||||
|
||||
int string_compare_limit (char * destination, char * source, int limit) {
|
||||
int string_compare_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset;
|
||||
|
||||
fatal_failure (destination == NULL, "string_compare_limit: Destination string is null pointer."); // This is the new trend, check for unimportant things.
|
||||
fatal_failure (source == NULL, "string_compare_limit: Source string is null pointer."); // At least this isn't too verbose. I hope...
|
||||
fatal_failure (string_0 == NULL, "string_compare_limit: Destination string is null pointer."); // This is the new trend, check for unimportant things.
|
||||
fatal_failure (string_1 == NULL, "string_compare_limit: Source string is null pointer."); // At least this isn't too verbose. I hope...
|
||||
|
||||
for (offset = 0; offset < limit; ++offset) { // Now, we'll iterate until 'limit' is reached, but it can overrun.
|
||||
if (destination [offset] != source [offset]) { // All said here applies to next two functions as well...
|
||||
return (FALSE); // As soon as 2 characters mismatch, they're not same, we return FALSE.
|
||||
for (offset = 0; offset < limit; ++offset) { // Now, we'll iterate until 'limit' is reached, but it can overrun.
|
||||
if (string_0 [offset] != string_1 [offset]) { // All said here applies to next two functions as well...
|
||||
return (FALSE); // As soon as 2 characters mismatch, they're not same, we return FALSE.
|
||||
}
|
||||
}
|
||||
|
||||
return (TRUE); // Otherwise, we're reached the end, they're same, we return TRUE.
|
||||
return (TRUE); // Otherwise, we're reached the end, they're same, we return TRUE.
|
||||
}
|
||||
|
||||
char * string_copy_limit (char * destination, char * source, int limit) {
|
||||
char * string_copy_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset;
|
||||
|
||||
fatal_failure (destination == NULL, "string_copy_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_0 == NULL, "string_copy_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_copy_limit: Source string is null pointer.");
|
||||
|
||||
if ((limit <= 0) || (source == NULL)) {
|
||||
return (destination);
|
||||
if ((limit <= 0) || (string_1 == NULL)) {
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
for (offset = 0; offset < limit; ++offset) {
|
||||
destination [offset] = source [offset];
|
||||
string_0 [offset] = string_1 [offset];
|
||||
}
|
||||
|
||||
return (destination);
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
char * string_concatenate_limit (char * destination, char * source, int limit) {
|
||||
int offset, destination_length, source_length;
|
||||
char * string_concatenate_limit (char * string_0, char * string_1, int limit) {
|
||||
int offset, length_0, length_1;
|
||||
|
||||
fatal_failure (destination == NULL, "string_concatenate_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_0 == NULL, "string_concatenate_limit: Destination string is null pointer.");
|
||||
fatal_failure (string_1 == NULL, "string_concatenate_limit: Source string is null pointer.");
|
||||
|
||||
if ((limit <= 0) || (source == NULL)) {
|
||||
return (destination);
|
||||
if ((limit <= 0) || (string_1 == NULL)) {
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
destination_length = string_length (destination);
|
||||
source_length = string_length (source);
|
||||
length_0 = string_length (string_0);
|
||||
length_1 = string_length (string_1);
|
||||
|
||||
for (offset = 0; (offset < source_length) && (offset < limit); ++offset) {
|
||||
destination [destination_length + offset] = source [offset];
|
||||
for (offset = 0; (offset < length_1) && (offset < limit); ++offset) {
|
||||
string_0 [length_0 + offset] = string_1 [offset];
|
||||
}
|
||||
|
||||
return (destination);
|
||||
return (string_0);
|
||||
}
|
||||
|
||||
char * string_reverse_limit (char * string, int limit) {
|
||||
@ -340,20 +346,17 @@ char * string_realign (char * string, int amount, char character) { //
|
||||
}
|
||||
|
||||
/*
|
||||
Ignore what next two functions do, it's about memory stuff that we'll cover in later chapters.
|
||||
Ignore what next two functions do, it's about memory management that we'll cover in later chapters.
|
||||
*/
|
||||
|
||||
int memory_compare (void * destination, void * source, int length) {
|
||||
int memory_compare (void * memory, void * source, int length) {
|
||||
int offset;
|
||||
|
||||
char * cast_0 = (char *) destination;
|
||||
char * cast_0 = (char *) memory;
|
||||
char * cast_1 = (char *) source;
|
||||
|
||||
fatal_failure (destination == NULL, "memory_compare: Memory is null pointer.");
|
||||
|
||||
if (source == NULL) {
|
||||
return (FALSE);
|
||||
}
|
||||
fatal_failure (memory == NULL, "memory_compare: Memory is null pointer.");
|
||||
fatal_failure (source == NULL, "memory_compare: Source is null pointer.");
|
||||
|
||||
for (offset = 0; offset != length; ++offset) {
|
||||
if (cast_0 [offset] != cast_1 [offset]) {
|
||||
@ -364,17 +367,14 @@ int memory_compare (void * destination, void * source, int length) {
|
||||
return (TRUE);
|
||||
}
|
||||
|
||||
void memory_copy (void * destination, void * source, int length) {
|
||||
void memory_copy (void * memory, void * source, int length) {
|
||||
int offset;
|
||||
|
||||
char * cast_0 = (char *) destination;
|
||||
char * cast_0 = (char *) memory;
|
||||
char * cast_1 = (char *) source;
|
||||
|
||||
fatal_failure (destination == NULL, "memory_copy: Memory is null pointer.");
|
||||
|
||||
if (source == NULL) {
|
||||
return;
|
||||
}
|
||||
fatal_failure (memory == NULL, "memory_copy: Memory is null pointer.");
|
||||
fatal_failure (source == NULL, "memory_copy: Source is null pointer.");
|
||||
|
||||
for (offset = 0; offset != length; ++offset) {
|
||||
cast_0 [offset] = cast_1 [offset];
|
||||
@ -382,7 +382,8 @@ void memory_copy (void * destination, void * source, int length) {
|
||||
}
|
||||
|
||||
/*
|
||||
Again, please consider these 'terminal_*' functions some form of black magic... For now, just take a look at how I format the code in them.
|
||||
Again, please consider these 'terminal_*' functions black magic, as well as 'number_to_string' and 'format_to_string' as they are more complex to cover them at this point, we'll
|
||||
talk more about them later... For now, just take a look at how I format the code in them.
|
||||
*/
|
||||
|
||||
void terminal_clear (void) {
|
||||
@ -410,4 +411,84 @@ void terminal_show_cursor (int show) {
|
||||
}
|
||||
}
|
||||
|
||||
char * number_to_string (int number) {
|
||||
int i, sign;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
sign = 1;
|
||||
} else {
|
||||
sign = 0;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = (char) (number % 10) + '0';
|
||||
number /= 10;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
char * format_to_string (int number, int sign, int base, int amount, char character) {
|
||||
int i;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = "0123456789ABCDEF" [number % base];
|
||||
number /= base;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,15 +1,20 @@
|
||||
/*
|
||||
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_0_HEADER
|
||||
#define CHAPTER_0_HEADER
|
||||
|
||||
/*
|
||||
In this chapter, you'll learn about:
|
||||
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free and exit.
|
||||
#include <fcntl.h> // This one for open and O_ flags.
|
||||
#include <unistd.h> // And this one for read, write, close and lseek.
|
||||
|
||||
- Code formatting
|
||||
- Operators
|
||||
- Function and variable declarations
|
||||
- Enumerations
|
||||
- Some basic functions
|
||||
- String and memory functions
|
||||
/*
|
||||
> Code formatting
|
||||
|
||||
It's very important that your code is aligned, readable and safe. So, we'll talk about it even before the first chapter, the so-called "zero chapter". I write assembly and Ada
|
||||
beside C, but still, 90% of my projects are written in C programming language! Why? Because C is like a girl that's a bit older than you, quiet but very smart and respectable, and
|
||||
@ -23,7 +28,7 @@ the start, it should happen over time, naturally. Just be sure to always be cons
|
||||
|
||||
General code formatting should follow these rules in C programming language:
|
||||
|
||||
- Always use tabs for indentation, and spaces for alignment. You'll hear people say the otherwise, don't trust them, they don't know.
|
||||
- Always use tabs for indentation, and spaces for alignment. You'll hear people say the otherwise, don't trust them, it's pure cancer.
|
||||
- Separate most (not all!) operators with space before and after it. We'll provide exceptional cases for this rule below.
|
||||
- Align most (not all!) operators horizontally, and logical units vertically. Again, examples are the best showcase.
|
||||
- Always use curly braces and round braces, even when they're not required by the compiler. Most linters warn about this.
|
||||
@ -31,7 +36,6 @@ General code formatting should follow these rules in C programming language:
|
||||
- Think very carefully if you'll limit the line length, it depends on your workflow and attention span... For me, 180 characters per line.
|
||||
- If you want, use pagination, but I don't recommend it when working with C. I use it in Ada, because comments there start with "--".
|
||||
- Always use snake case (this_style), for enumerations and macros (THIS_STYLE), and never put underscore before or after the identifier.
|
||||
+ Feel free the break the rules if, by your jugdement, that will improve readability or emphasize the intention, I break them too.
|
||||
|
||||
For now, don't worry about what's an identifier, compiler, linter, those scary and unknown words will be explained later, when the time comes.
|
||||
|
||||
@ -54,7 +58,7 @@ D) Again, same extension but for binary operators, that'll use left-hand variabl
|
||||
E) Logical operators, and, or, not and operators '?' and ':' are used together, as they form ternary operator.
|
||||
F) Comparison operators, equals, not-equals, less-or-equal, greater-or-equal, less and greater.
|
||||
|
||||
Some more symbols are also operators (and some are not), but my advice is not to treat them as such, just consider them some kind of special cases.
|
||||
Some more symbols are also operators, but my advice is not to treat them as such, just consider them some kind of special cases.
|
||||
|
||||
- Before:
|
||||
|
||||
@ -100,7 +104,7 @@ many function agruments, and always place them in consistent and logical order.
|
||||
@C
|
||||
// Internal and external function declaration:
|
||||
|
||||
static int string_copy_limit (char * destination, char * source, int limit);
|
||||
static int string_copy_limit (char * string_0, char * string_1, int limit);
|
||||
|
||||
extern int32_t string_copy (char * string_a,
|
||||
char * string_b,
|
||||
@ -111,8 +115,6 @@ extern size_t copy_string_n (
|
||||
string_t b,
|
||||
size_t n
|
||||
);
|
||||
|
||||
// And you can improve last 2 examples by using 'destination' for 'string_0/a' and 'source' for 'string_1/b', since it's more clear what the function does.
|
||||
@
|
||||
|
||||
Internal ('static') and external ('extern') variable declarations are different from function declarations, because you need to initialize internal variables to some value, I like
|
||||
@ -122,19 +124,15 @@ redeclaring them, some will warn you that they're already initialized to zero, s
|
||||
@C
|
||||
// Internal and external variable declaration:
|
||||
|
||||
static int curses_active = 0; // You can declare and define internal variables right away, all in one line.
|
||||
static int curses_active = 0;
|
||||
static char curses_signal = '\0';
|
||||
static char * curses_screen = NULL;
|
||||
|
||||
extern unsigned int positive; // You need to declare external variables without initializing them immediately.
|
||||
extern unsigned int positive;
|
||||
extern int negative;
|
||||
extern int * * coordinate_system;
|
||||
|
||||
unsigned int positive = +1; // And then to define them separately, unlike internal variables.
|
||||
int negative = -1;
|
||||
int * * coordinate_system = NULL;
|
||||
|
||||
static void (* encode_type_0 [TYPE_0_COUNT]) (void) = { NULL }; // We're declaring arrays of function pointers this way, we'll cover them in later chapters.
|
||||
static void (* encode_type_0 [TYPE_0_COUNT]) (void) = { NULL };
|
||||
static char * (* encode_type_1 [TYPE_1_COUNT]) (int l) = { NULL };
|
||||
static int (* encode_type_2 [TYPE_2_COUNT]) (int l, int r) = { NULL };
|
||||
|
||||
@ -144,31 +142,20 @@ extern xcb_pixmap_t blesses_pixmap;
|
||||
extern xcb_connection_t * blesses_connection;
|
||||
extern xcb_screen_t * blesses_screen;
|
||||
extern xcb_image_t * blesses_image;
|
||||
|
||||
xcb_window_t blesses_window; // Here, same example for external variables, but you don't even need to initialize them.
|
||||
xcb_gcontext_t blesses_context; // If you know what you're doing, and when they'll be initialized, you can leave them like this.
|
||||
xcb_pixmap_t blesses_pixmap;
|
||||
xcb_connection_t * blesses_connection;
|
||||
xcb_screen_t * blesses_screen;
|
||||
xcb_image_t * blesses_image;
|
||||
@
|
||||
|
||||
Only for 'main' function, you shouldn't add 'static' or 'extern' keywords before it. Every C file needs to have exactly one 'main' function, it's the programs' entry point. Also,
|
||||
some people like to define their own types, with keyword 'typedef', I'm not a fan of it, since in C types are also keywords unlike in Ada. Again, it'll become more clean in future
|
||||
chapters why I dislike user-defined types, unions and structures. That doesn't mean we won't use them tho, because I need to show-case their usage. Also, as for main function, it
|
||||
can have either 'void' as argument, so the program takes no command-line arguments, or 'int argc' and 'char * * argv / char * argv []'.
|
||||
chapters why I dislike user-defined types, unions and structures.
|
||||
|
||||
@C
|
||||
// Main function:
|
||||
|
||||
int main (void) {
|
||||
int main (int argc, char * * argv) {
|
||||
|
||||
int32_t main (int32_t argc,
|
||||
char * argv []) {
|
||||
|
||||
// Somewhere before, with using 'typedef':
|
||||
// typedef int number_t;
|
||||
// typedef char * string_t;
|
||||
number_t main (
|
||||
number_t argc,
|
||||
string_t argv []) {
|
||||
@ -176,37 +163,14 @@ number_t main (
|
||||
|
||||
Now, I'll write some basic functions that'll be used later in the program, so you can see that code formatting I've spent whole hour writing. Don't mind what they do, just keep an
|
||||
eye on how are they aligned and named. I'll reimplement some standard functions, and you can revisit them after reading few more chapters, it'll be more understandable then.
|
||||
|
||||
Also, what is keyword 'enum' for? It's for enumerations, and early C programming language didn't have it. It assigns a value to some identifier, if not specified, it's automatic:
|
||||
|
||||
@C
|
||||
// Values of A, B and C are same for these 3 enumerations.
|
||||
enum { A, B, C };
|
||||
enum { A = 0, B = 1, C };
|
||||
enum { A = 0, B = 1, C = 2 };
|
||||
// However, if you use:
|
||||
enum { A = 1, B, C };
|
||||
// Then, A is 1, B is 2 and C is 3. Easy, right? Also:
|
||||
enum { A, B, C = 0, D };
|
||||
// In this last case, A and C are 0, B and D are 1.
|
||||
@
|
||||
|
||||
By default, it always starts from zero, if you assign some value to it (must be integer), next one will be increment of previous value, if otherwise not specified.
|
||||
*/
|
||||
|
||||
enum {
|
||||
// This is completely unnecesary, but I don't care, it's a good showcase how boolean type can work, true is 1 and false is 0.
|
||||
enum { // This is completely unnecesary, but I don't care, it's a good showcase how boolean type can work, true is 1 and false is 0.
|
||||
FALSE,
|
||||
TRUE
|
||||
};
|
||||
|
||||
enum {
|
||||
// I use these to explicitly identify character literals outside of strings.
|
||||
// This is also one of my preferences, to use CHARACTER_NULL or CHARACTER_LINE_FEED instead of '\0' or '\n' in special (non-string) cases.
|
||||
// I align them like this, you can do whatever you want here, I just don't like to write a lot of short lines of code...
|
||||
// I align long identifiers by 40 characters + indentation width (denoted with T). I won't align next two comments, to show you the offset.
|
||||
// T + 0 * 40 T + 1 * 40 T + 2 * 40 T + 3 * 40
|
||||
// ^ ^ ^ ^
|
||||
enum { // This is also one of my preferences, to use CHARACTER_NULL or CHARACTER_LINE_FEED instead of '\0' or '\n' in special (non-string) cases.
|
||||
CHARACTER_NULL, CHARACTER_START_HEADER, CHARACTER_START_TEXT, CHARACTER_END_TEXT,
|
||||
CHARACTER_END_TRANSMISSION, CHARACTER_ENQUIRY, CHARACTER_ACKNOWLEDGE, CHARACTER_BELL,
|
||||
CHARACTER_BACKSPACE, CHARACTER_TAB_HORIZONTAL, CHARACTER_LINE_FEED, CHARACTER_TAB_VERTICAL,
|
||||
@ -215,17 +179,15 @@ enum {
|
||||
CHARACTER_DEVICE_CONTROL_4, CHARACTER_NOT_ACKNOWLEDGE, CHARACTER_SYNCHRONOUS_IDLE, CHARACTER_END_TRANSMISSION_BLOCK,
|
||||
CHARACTER_CANCEL, CHARACTER_END_MEDIUM, CHARACTER_SUBSTITUTE, CHARACTER_ESCAPE,
|
||||
CHARACTER_FILE_SEPARATOR, CHARACTER_GROUP_SEPARATOR, CHARACTER_RECORD_SEPARATOR, CHARACTER_UNIT_SEPARATOR,
|
||||
CHARACTER_COUNT // Not an actual ASCII table count (128), but for ending special invisible characters.
|
||||
CHARACTER_COUNT // Not an actual ASCII table count (128), but for starting invisible characters.
|
||||
};
|
||||
|
||||
enum {
|
||||
// I like to align enumerations with 10, 20 or 40 characters per name, and optionally use NAME_ as prefix and NAME_COUNT as last element, look above...
|
||||
enum { // I like to align enumerations with 10, 20 or 40 characters per name, and optionally use NAME_ as prefix and NAME_COUNT as last element.
|
||||
EFFECT_NORMAL, EFFECT_BOLD, EFFECT_ITALIC, EFFECT_UNDERLINE, EFFECT_BLINK, EFFECT_REVERSE,
|
||||
EFFECT_COUNT
|
||||
};
|
||||
|
||||
enum {
|
||||
// Because of text auto-completition, it's always easy to find what I want, or to use NAME_COUNT in arrays. In some cases, order matters!
|
||||
enum { // Because of text auto-completition, it's always easy to find what I want, or to use NAME_COUNT in arrays. In some cases, order matters!
|
||||
COLOUR_GREY, COLOUR_RED, COLOUR_GREEN, COLOUR_YELLOW, COLOUR_BLUE, COLOUR_PINK, COLOUR_CYAN, COLOUR_WHITE,
|
||||
COLOUR_COUNT
|
||||
};
|
||||
@ -245,15 +207,15 @@ extern void * deallocate (void * data );
|
||||
|
||||
extern int string_length (char * string); // We deal with strings a lot in this program, so string functions will be more important than character functions from chapter one.
|
||||
|
||||
extern int string_compare (char * destination, char * source); // See how nicely they align, right?
|
||||
extern char * string_copy (char * destination, char * source);
|
||||
extern char * string_concatenate (char * destination, char * source);
|
||||
extern char * string_reverse (char * string); // Notice last function, we didn't align ');'...
|
||||
extern int string_compare (char * string_0, char * string_1); // See how nicely they align, right?
|
||||
extern char * string_copy (char * string_0, char * string_1);
|
||||
extern char * string_concatenate (char * string_0, char * string_1);
|
||||
extern char * string_reverse (char * string); // Notice last function, we didn't align ');'...
|
||||
|
||||
extern int string_compare_limit (char * destination, char * source, int limit); // These ones too, it's beautiful (in my opinion), tho some consider it useless.
|
||||
extern char * string_copy_limit (char * destination, char * source, int limit);
|
||||
extern char * string_concatenate_limit (char * destination, char * source, int limit);
|
||||
extern char * string_reverse_limit (char * string, int limit); // And we align the last argument in this case, use whatever you prefer.
|
||||
extern int string_compare_limit (char * string_0, char * string_1, int limit); // These ones too, it's beautiful (in my opinion), tho some consider it useless.
|
||||
extern char * string_copy_limit (char * string_0, char * string_1, int limit);
|
||||
extern char * string_concatenate_limit (char * string_0, char * string_1, int limit);
|
||||
extern char * string_reverse_limit (char * string, int limit); // And we align the last argument in this case, use whatever you prefer.
|
||||
|
||||
extern char * string_realign (char * string, int amount, char character); // This is a simple function that realigns a string to right, we'll use it way later ahead.
|
||||
|
||||
@ -266,4 +228,8 @@ extern void terminal_colour (int colour, int effect); // Set terminal chara
|
||||
extern void terminal_cancel (void); // Reset terminal character attributes.
|
||||
extern void terminal_show_cursor (int show); // Toggle rendering of terminal cursor.
|
||||
|
||||
// These will be useful in chapter three, where we'll learn about 'printf' function. It's too complex to cover it at this point.
|
||||
extern char * number_to_string (int number);
|
||||
extern char * format_to_string (int number, int sign, int base, int amount, char character);
|
||||
|
||||
#endif
|
||||
|
@ -1,10 +1,14 @@
|
||||
/*
|
||||
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_1_SOURCE
|
||||
#define CHAPTER_1_SOURCE
|
||||
|
||||
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free, atexit, rand and exit (some will be used in future chapters).
|
||||
#include <fcntl.h> // This one for open and 'O_' flags.
|
||||
#include <unistd.h> // And this one for read, write, close and lseek.
|
||||
|
||||
#include "chapter_1.h" // Get used to this... We're pasting macros, enumerations and function declarations again...
|
||||
|
||||
int character_is_uppercase (char character) { // Returns a boolean value, aka FALSE or TRUE, aka 0 or 1...
|
||||
@ -69,8 +73,7 @@ int character_is_hexadecimal (char character) { // Same as function 'character_i
|
||||
/*
|
||||
Now, we can see how function 'character_compare_array' was implemented, but know that it could be even shorter, like you see below. However, I really think it's for the best to
|
||||
use curly and round braces, when even the compiler won't warn about them. You can easily see the scope of something if you have a text editor capable of highlighting matching
|
||||
braces, and almost all of them have that feature. Also, don't be afraid of longer lines of code. You can even enable word-wrapping in your ed text editor running on Commodore 64
|
||||
with 128 GiB of RAM. Just focus more on what you're doing, don't limit yourself to mere 80 characters per line.
|
||||
braces, and almost all of them have that feature.
|
||||
|
||||
@C
|
||||
int character_compare_array (char character, char * character_array) {
|
||||
@ -95,16 +98,6 @@ int character_compare_array (char character, char * character_array) { // I didn
|
||||
return (FALSE);
|
||||
}
|
||||
|
||||
int character_count (char * string, char character, int from, int to, char stop) { // This function seem trickier to understand, but it's nothing special if you think about it.
|
||||
int count;
|
||||
|
||||
for (count = 0; (from != to) && (string [from] != stop); from += ((to < from) ? -1 : 1)) { // We want to know how many 'character's are in our 'string'.
|
||||
count += (int) ((string [from] == character) || (character == '\0')); // This covers searching forward and backward, depending on 'from' and 'to'.
|
||||
}
|
||||
|
||||
return (count); // And we found our count!
|
||||
}
|
||||
|
||||
/*
|
||||
You can see important information about some functions on manual pages in every Linux distro, with 'man ([optional] number) function_name', for example 'man 2 open', and I'll list
|
||||
few important ones down below with some copy & paste magic, but I'll keep it short.
|
||||
@ -114,14 +107,13 @@ few important ones down below with some copy & paste magic, but I'll keep it sho
|
||||
#include <sys/stat.h> // Core something, I don't even know...
|
||||
#include <fcntl.h> // Few system calls.
|
||||
|
||||
int open (const char * pathname, int flags );
|
||||
int open (const char * pathname, int flags, mode_t mode);
|
||||
int creat (const char * pathname, mode_t mode);
|
||||
int open (const char *pathname, int flags);
|
||||
int open (const char *pathname, int flags, mode_t mode);
|
||||
int creat (const char *pathname, mode_t mode);
|
||||
int openat (int dirfd, const char *pathname, int flags);
|
||||
int openat (int dirfd, const char *pathname, int flags, mode_t mode);
|
||||
|
||||
int openat (int dirfd, const char * pathname, int flags );
|
||||
int openat (int dirfd, const char * pathname, int flags, mode_t mode);
|
||||
|
||||
// Some commonly used file flags (modes, one of the first three must always be present in mode mask):
|
||||
// Flags (modes, one of the first three must always be present in mode mask):
|
||||
// - O_RDONLY: Open or create file as 'read only', prohibit writing to that file.
|
||||
// - O_WRONLY: Open or create file as 'write only', so you have permission to modify it.
|
||||
// - O_RDWR: Open or create file as 'read and write', so you can do whatever you want with it...
|
||||
@ -145,8 +137,6 @@ int file_open (char * name, int mode) {
|
||||
// Or align it to break two longer function arguments:
|
||||
// fatal_failure ((descriptor = open (name, mode)) == -1,
|
||||
// "file_open: Failed to open file, function open returned invalid descriptor.");
|
||||
// As you can see, if you want to inline variable assignment, you must use braces, like I did in uncommented example.
|
||||
// And don't confuse '==' equality comparison and '=' assignment operators!
|
||||
|
||||
return (descriptor); // Return opened file descriptor.
|
||||
}
|
||||
@ -184,9 +174,8 @@ int file_size (char * name) {
|
||||
int size = -1; // Lets just assume that everything is wrong, everything falls apart...
|
||||
int file = -1; // Everything is just -1 around us...
|
||||
|
||||
file = file_open (name, O_RDONLY); // We open a file to read it.
|
||||
|
||||
size = (int) lseek (file, 0, SEEK_END); // We set the offset to the end of the file.
|
||||
file = file_open (name, O_RDONLY); // We open a file to read it.
|
||||
size = (int) lseek (file, 0, SEEK_END); // We set the offset to the end of the file.
|
||||
|
||||
fatal_failure (size == -1, "file_size: Failed to get size of file, invalid file size."); // Again, error of 'lseek' would be -1, so we check for that...
|
||||
|
||||
@ -219,7 +208,7 @@ int file_type (char * name) {
|
||||
return (-1); // If it's not in array, we return -1, so we don't access the wrong value in some other array.
|
||||
}
|
||||
|
||||
char * file_record (char * name) {
|
||||
void * file_record (char * name) {
|
||||
int file = -1; // You can also initialize local variables in this way.
|
||||
int size = -1;
|
||||
char * data = NULL;
|
||||
@ -237,92 +226,4 @@ char * file_record (char * name) {
|
||||
return (data); // We return pointer to new memory, but remember, we have to free it later.
|
||||
}
|
||||
|
||||
/*
|
||||
I won't cover next two functions at this point, because they might be harder to understand, but if you feel confident, try to guess what they do...
|
||||
*/
|
||||
|
||||
char * number_to_string (int number) {
|
||||
int i, sign;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
sign = 1;
|
||||
} else {
|
||||
sign = 0;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = (char) (number % 10) + '0';
|
||||
number /= 10;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
char * format_to_string (int number, int sign, int base, int amount, char character) {
|
||||
int i;
|
||||
|
||||
static char string [32];
|
||||
|
||||
for (i = 0; i != 32; ++i) {
|
||||
string [i] = CHARACTER_NULL;
|
||||
}
|
||||
|
||||
if (number == 0) {
|
||||
string [0] = '0';
|
||||
string [1] = CHARACTER_NULL;
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
if (number < 0) {
|
||||
number *= -1;
|
||||
}
|
||||
|
||||
for (i = (string [0] == '-'); number != 0; ++i) {
|
||||
string [i] = "0123456789ABCDEF" [number % base];
|
||||
number /= base;
|
||||
}
|
||||
|
||||
if (sign != 0) {
|
||||
string [i] = '-';
|
||||
++i;
|
||||
}
|
||||
|
||||
string [i] = CHARACTER_NULL;
|
||||
|
||||
string_reverse (string);
|
||||
|
||||
string_realign (string, amount, character);
|
||||
|
||||
return (string);
|
||||
}
|
||||
|
||||
int randomize (int minimum, int maximum) { // Now, we're simply returning random integer between 'minimum' and 'maximum', inclusively.
|
||||
return (rand () % (maximum - minimum + 1) + minimum);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,18 +1,17 @@
|
||||
/*
|
||||
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_1_HEADER
|
||||
#define CHAPTER_1_HEADER
|
||||
|
||||
#include "chapter_0.h" // We need this header file for some core enumeration definitions and function declarations.
|
||||
|
||||
/*
|
||||
In this chapter, you'll learn about:
|
||||
|
||||
- Keywords
|
||||
- Normal identifiers
|
||||
- Types
|
||||
- Character functions
|
||||
- File functions
|
||||
- ASCII table
|
||||
|
||||
Now that you've read the chapter zero, we should get into some technical details about C programming language. This will be the most imporant chapter for people who know some
|
||||
other lower level programming language, or who intuitively understand "building blocks" of some system. Below is just a simple matrix of them, 8 x 4, so you can see that there are
|
||||
really 32 keywords (ANSI C standard, I dislike newer standards), and even more below we'll categorize them. Also, keep in mind that I'll briefly talk about other C standards such
|
||||
@ -76,11 +75,7 @@ see that sometimes is preferable to use switch statement somewhere, or while loo
|
||||
library that internally used structures everywhere, so you'll need to adapt to it, we'll see examples later...
|
||||
|
||||
So, real men need these keywords { char, int, void, sizeof, static, if, else, for, enum, return }, and use the rest of them in order to silence compiler warnings, use some
|
||||
standard library functions, clean the source code or access an API / library / header file. Lets see some more examples and keep in mind code formatting... Also, since this is a
|
||||
book about C, I'll write comments and use those keywords I dislike, much to my dismay. You surely have different point of view on certain things than me, which is completely
|
||||
natural, but always remember one thing:
|
||||
|
||||
There's a huge difference in being smart and in not being stupid. I'm simply not stupid, but I'm not smart, use this book to write something smarter than I did.
|
||||
standard library functions, clean the source code or access an API / library / header file. Lets see some more examples and keep in mind code formatting...
|
||||
|
||||
@C
|
||||
extern int this_is_kind (kind_type this);
|
||||
@ -91,9 +86,6 @@ extern int this_is_kind (kind_type this);
|
||||
// this - we named our argument about what's it supposed to be, use similar approach in return type, function, argument type and argument names.
|
||||
@
|
||||
|
||||
You'll see this pattern a lot in my code, being consistent makes you more productive and efficient when doing any kind of task, from programming and studying foreign languages, to
|
||||
cleaning your living room and washing your dishes. Be consistent no matter what you do.
|
||||
|
||||
Before continuing, lets describe some imporant types in C very simply:
|
||||
|
||||
Word: Bytes: Bits: Kind: Minimum: Maximum:
|
||||
@ -110,14 +102,12 @@ Before continuing, lets describe some imporant types in C very simply:
|
||||
float 4 32 Real (IEEE754) / /
|
||||
double 8 64 Real (IEEE754) / /
|
||||
|
||||
Note that you shouldn't care for now about 'void' and 'void *', because they're special cases, nor about their minimum and maximum. Also, the less types you use, the more type
|
||||
safe your code is. That's the reason why I use 'int', 'char *' and 'char', and sometimes 'void *' and 'int *'. You get less compiler and linter warnings about conversions, but you
|
||||
need to know exactly what you're doing and what your code will do in order to have no bugs. Again, think twice, write once. When I write programs in Ada language however, I make
|
||||
a lot of types, since it's completely different language and has different coding practices.
|
||||
Note that you shouldn't care for now about 'void' and 'void *', because they're special cases, nor about their minimum and maximum. Also, the less types you use, the more
|
||||
type-safe your code is. That's the reason why I use 'int', 'char *' and 'char', and sometimes 'void *' and 'int *'. You get less compiler and linter warnings about conversions,
|
||||
but you need to know exactly what you're doing and what your code will do in order to have no bugs. Again, think twice, write once.
|
||||
*/
|
||||
|
||||
enum {
|
||||
// We won't even cover all of those file formats, this is just an example of how to do similar task without hardcoding file extensions, we'll use it later.
|
||||
enum { // We won't even cover all of those, this is just an example of how to do similar task without hardcoding file extensions.
|
||||
FILE_TYPE_TEXT, FILE_TYPE_COMMON_ASSEMBLY, FILE_TYPE_FLAT_ASSEMBLY, FILE_TYPE_GNU_ASSEMBLY,
|
||||
FILE_TYPE_NETWIDE_ASSEMBLY, FILE_TYPE_YET_ANOTHER_ASSEMBLY, FILE_TYPE_C_SOURCE, FILE_TYPE_C_HEADER,
|
||||
FILE_TYPE_ADA_BODY, FILE_TYPE_ADA_SPECIFICATION, FILE_TYPE_CPP_SOURCE, FILE_TYPE_CPP_HEADER,
|
||||
@ -126,21 +116,16 @@ enum {
|
||||
|
||||
/*
|
||||
Here are some "utility" functions that we'll maybe use, reimplementation of those from standard library header file <ctype.h>. I prefer names like this, and this is for learning
|
||||
purposes, so it's nice that you can see it here instead of searching through folders such as "/usr/include/". But before that, lets talk about ASCII table now!
|
||||
purposes, so it's nice that you can see it here instead of searching through folders such as "/usr/include/". Lets talk about ASCII table now!
|
||||
|
||||
In functions 'character_is_uppercase', 'character_is_lowercase' and 'character_is_digit', we use characters that are in certain range on ASCII table, which we'll show just below.
|
||||
So, it's safe to use '>=' and '<=' operators, but in other cases, we want to compare them selectively, and for simplicity we use function 'character_compare_array'... Here's how
|
||||
ASCII table looks like, I don't like encodings like UTF-8 and others, so neither should you. We'll also write a subprogram that prints this to terminal or graphical window.
|
||||
|
||||
ASCII table:
|
||||
|
||||
- 0B: Binary representation.
|
||||
- 0O: Octal representation.
|
||||
- 0D: Decimal representation.
|
||||
- 0X: Hexadecimal representation.
|
||||
_______________________________________________________________________________________________________________________________________________________________
|
||||
|_0B______|_0O__|_0D__|_0X_|_SYM_|_Full_name____________________________________|_0B______|_0O__|_0D__|_0X_|_SYM_|_Full_name____________________________________|
|
||||
| | | | | | | | | | | | |
|
||||
| | |
|
||||
| 0000000 | 000 | 0 | 00 | NUL | Null | 0000001 | 001 | 1 | 01 | SOH | Start of heading |
|
||||
| 0000010 | 002 | 2 | 02 | STX | Start of text | 0000011 | 003 | 3 | 03 | ETX | End of text |
|
||||
| 0000100 | 004 | 4 | 04 | EOT | End of transmission | 0000101 | 005 | 5 | 05 | ENQ | Enquiry |
|
||||
@ -205,42 +190,9 @@ ASCII table:
|
||||
| 1111010 | 172 | 122 | 7A | z | Lowercase z | 1111011 | 173 | 123 | 7B | { | Opening brace |
|
||||
| 1111100 | 174 | 124 | 7C | | | Vertical bar | 1111101 | 175 | 125 | 7D | } | Closing brace |
|
||||
| 1111110 | 176 | 126 | 7E | ~ | Tilde | 1111111 | 177 | 127 | 7F | DEL | Delete |
|
||||
|_________|_____|_____|____|_____|______________________________________________|_________|_____|_____|____|_____|______________________________________________|
|
||||
|_______________________________________________________________________________|_______________________________________________________________________________|
|
||||
|
||||
You can see that values of 'A' ... 'Z', 'a' ... 'z' and '0' ... '9' are sequential, but symbols and "system" characters are mixed up. You can also look at it this way:
|
||||
|
||||
- 0 ... 7: Upper 3 bits (0B0---0000).
|
||||
- 0 ... F: Lower 4 bits (0B0000----).
|
||||
___________________________________________________
|
||||
|___|__0__|__1__|__2__|__3__|__4__|__5__|__6__|__7__|
|
||||
| | | | | | | | | |
|
||||
| 0 | NUL | DLE | | 0 | @ | P | ` | p |
|
||||
| 1 | SOH | DC1 | ! | 1 | A | Q | a | q |
|
||||
| 2 | STX | DC2 | " | 2 | B | R | b | r |
|
||||
| 3 | ETX | DC3 | # | 3 | C | S | c | s |
|
||||
| 4 | EOT | DC4 | $ | 4 | D | T | d | t |
|
||||
| 5 | ENQ | NAK | % | 5 | E | U | e | u |
|
||||
| 6 | ACK | SYN | & | 6 | F | V | f | v |
|
||||
| 7 | BEL | ETB | ' | 7 | G | W | g | w |
|
||||
| 8 | BS | CAN | ( | 8 | H | X | h | x |
|
||||
| 9 | HT | EM | ) | 9 | I | Y | i | y |
|
||||
| A | LF | SUB | * | : | J | Z | j | z |
|
||||
| B | VT | ESC | + | ; | K | [ | k | { |
|
||||
| C | FF | FS | , | < | L | \ | l | | |
|
||||
| D | CR | GS | - | = | M | ] | m | } |
|
||||
| E | SO | RS | . | > | N | ^ | n | ~ |
|
||||
| F | SI | US | / | ? | O | _ | o | DEL |
|
||||
|___|_____|_____|_____|_____|_____|_____|_____|_____|
|
||||
|
||||
You can notice that if you toggle 5th bit of alphabet characters, you can set them to lowercase or uppercase, since we're dealing with binaries in this case, when only 5th bit is
|
||||
1, and others are 0, that's 2**5 (2 to the power of 5), which is 32, which is again equal to space character. That table also works for hexadecimals, you can see that for example,
|
||||
character 'H' is in '4' column and '8' row, so hexadecimal value for character literal 'H' is 0X48. Of course, there's no need to memorize any of those, they can just be handy if
|
||||
you feel exceptionally smart and want to do some bit manipulation on strings.
|
||||
|
||||
Lets talk very shortly about C preprocessor, I don't like to use it, but sometimes you have to, and I'll show few useful examples in later chapters. Note that you can make a
|
||||
complete project in C programming language, without using the preprocessor even once, but since modern programs are large and often split into separate source files, and there's
|
||||
no truly good build system (in any language), people use them in order not to copy paste structures, unions, enumerations and function declarations, and global variables were
|
||||
villified because of multiple people working on the same project, and fucking things up. I felt the need to repeat this below too...
|
||||
You can see that values of 'A' ... 'Z', 'a' ... 'z' and '0' ... '9' are sequential, but symbols and "system" characters are mixed up.
|
||||
|
||||
In C language, we have C source files with the extension '.c', and C header files with the extension '.h'. Both of those are just plain text files, and please use 7-bit ASCII
|
||||
encoding, since it's common sense, UTF is cancer, and 8-bit ASCII is for enlightened people like Terrence Andrew Davis. C language is completely separate (on some C compilers)
|
||||
@ -264,74 +216,11 @@ Okay, that's all you really need to know about C preprocessor, since we won't us
|
||||
copying and pasting a lot of code, especially external function and variable declarations. Because of that we need '#include' directive, and because of it, we need header guards,
|
||||
so it's all C-hating in the end. However, we need to cover some simple macros, so you can deal with other peoples' code bases. Remember, the less "building blocks" you have, if
|
||||
you learn them well, you can make anything, and you should be proud of "reinventing the wheel". If wheels weren't reinvented over and over again, then some expensive BMW would've
|
||||
wooden wheels attached to it. You can also use '#define' to write entire functions, but that can lead to lot of compiler warnings for only forgetting to put one character, and it
|
||||
bloats the code, so we'll show how to write them, and never use them again.
|
||||
|
||||
Then, as you probably noticed, comments in older C standards (K&R and ANSI) begin with "/(merged)*" and end with "*(merged)/". I put merged there, because otherwise it'd end this
|
||||
comment there, and everything below it would be treated as C source code. Preprocessor modifies C source code before the compiler, so it would remove all comments, copy+paste the
|
||||
content of those files we included, conditionally if we used if / ifdef/ ifndef and more. Usually syntax highlighting will be weird if you do something like this, and you'll fix
|
||||
it easily, so don't worry much about it. In newer standards (C99 and forward), you can have single-line comments that beign with "//" and end with (you guessed it) new line, aka
|
||||
character literal '\n' aka line feed. Again, I don't write comments at all except licence notice in my projects, but this book project is an exception. You should express what
|
||||
your code does in writing it properly, not writing obfuscated code and add comments about what it does.
|
||||
|
||||
Otherwise, roughly speaking, you have constants, variables, functions and pointers. In the end, it all comes down to CPU instructions that use registers, immediate values and
|
||||
memory addresses (REG / IMM / MEM), which we'll mention way later.
|
||||
|
||||
Your constants can be internal, external, defined or enumerated:
|
||||
|
||||
@C
|
||||
static const char DELETE = '\177'; // Used in only one file, where's it declared and defined.
|
||||
|
||||
extern const char DELETE; // Used in C header file (.h).
|
||||
const char DELETE = (char) 127; // Used in C source file (.c).
|
||||
|
||||
#define DELETE (0X7F) // Used where file containing this line was '#include'-d.
|
||||
|
||||
// Note that I consider this bad practice, since you also need to include file containing this, and it values start from 0, so in this case we need to set it to 127.
|
||||
// With 'typedef'-ed example, we can provide a name for that enumeration, and use it as 'enum my_enumeration_verbose_or_same_name' or 'my_enumeration'.
|
||||
// Enumerations are only useful (from my experience) when you need to '#define' a lot of values in incremental order, from 0 to some number, and they are 'int' type by default.
|
||||
enum { DELETE = 127 };
|
||||
typedef enum my_enumeration_verbose_or_same_name { DELETE = 127 } my_enumeration;
|
||||
@
|
||||
|
||||
It's very similar for variables, but they can't be defined (nor should be!) or enumerated (unless you're thinking about arrays). You can have variables inside and outside
|
||||
functions, those inside are called local variables, and those outside are called global variables. Global variables can make the code shorter, simpler and easier to change, but if
|
||||
you or people you work with don't know what the hell they're doing, it can lead to messy code or difficult to track bugs. Just don't think that they are evil and should never be
|
||||
used, because that's not the case.
|
||||
|
||||
Local variables (that you'll see inside my functions) are by default declared with 'auto' instead of 'static' or 'extern', but you don't need to write 'auto' before them, since
|
||||
it's implicitly there. In old C language (K&R standard), they used to write code like this example below, which isn't something you should do nowdays. Also, local variables aren't
|
||||
accessable or modifiable after the function ends, you'll see examples of that later.
|
||||
|
||||
@C
|
||||
// K&R example (old C language), which would be something like following in newer standards:
|
||||
output (data, size) static int output (char * data, int size) {
|
||||
char * data; { (void) write (STDOUT_FILENO, (void *) data, (size_t) size * sizeof (* data));
|
||||
write (1, data, size); return (0);
|
||||
} }
|
||||
|
||||
static char * string_pointer = NULL; // Later we can modify them, use them in functions and much more, but only in file those were defined and declared.
|
||||
static char * string = NULL;
|
||||
// Somewhere later in the file, inside some function:
|
||||
// string = calloc (1024UL, sizeof (* string));
|
||||
// string = strncpy (string, "Heyo world!");
|
||||
// ...
|
||||
// free (string);
|
||||
|
||||
extern int subprogram_id; // Used in C header file (.h).
|
||||
int subprogram_id = 0; // Used in C source file (.c).
|
||||
// Later, any function can modify this variable with just:
|
||||
// subprogram_id = 144000;
|
||||
@
|
||||
|
||||
Functions simply are part of the program that modifies variables or execute some code that causes a side-effect, and then return a value or nothing (void). For example, our
|
||||
'character_*' functions below preform some operations on operand 'character' of type 'char', without modifying it, and return some value of type 'int', and they are declared as
|
||||
external with 'extern', because they are defined in another text file. In this case, our family of functions 'character_*' will return FALSE (0) or TRUE (1), depending on what
|
||||
they do in their definitions, and I like to put '_is_' in functions that return boolean value (but not always).
|
||||
wooden wheels attached to it.
|
||||
*/
|
||||
|
||||
extern int character_is_uppercase (char character); // Notice how we align those functions, I believe this improves the readability of any program, in any programming language.
|
||||
extern int character_is_lowercase (char character); // Some people would just use 'ischrlow' or 'islower', but I hate reading code written like that...
|
||||
extern int character_is_lowercase (char character); // Some people would just use 'ischrupp' or something, but I hate reading code written like that...
|
||||
extern int character_is_digit (char character); // Important note is also that a programming language is not, and it should be like natural language, why?
|
||||
extern int character_is_blank (char character); // Because we need strict rules in programming language, same like in mathematical languages, now now, don't be scared.
|
||||
extern int character_is_alpha (char character);
|
||||
@ -344,8 +233,6 @@ extern int character_is_hexadecimal (char character);
|
||||
|
||||
extern int character_compare_array (char character, char * character_array); // This function is singled out, because it's different from those above, and we use it internally.
|
||||
|
||||
extern int character_count (char * string, char character, int from, int to, char stop); // We'll use this to count characters in null-terminated strings.
|
||||
|
||||
/*
|
||||
And here are also utility functions that handle files, most of them are reimplemented using "system calls" from <fcntl.h> and <unistd.h>, but you also have access to <stdio.h>,
|
||||
which is probably the most used header file in C language. It handles the 'FILE *' type, not a file descriptors which are 'int', and has functions that are prefixed with character
|
||||
@ -359,12 +246,6 @@ extern void file_write (int file, void * data, int size); // We write from 'd
|
||||
extern int file_seek (int file, int whence); // We retrieve data about offsets in 'file'.
|
||||
extern int file_size (char * name); // We get the size of the file by its' 'name'.
|
||||
extern int file_type (char * name); // We get the type of the file by its' 'name' (by file name extension).
|
||||
extern char * file_record (char * name); // We store an entire file into some memory address.
|
||||
|
||||
// These will be useful in chapter three, where we'll learn about 'printf' function. It's too complex to cover it at this point.
|
||||
extern char * number_to_string (int number);
|
||||
extern char * format_to_string (int number, int sign, int base, int amount, char character);
|
||||
|
||||
extern int randomize (int minimum, int maximum);
|
||||
extern void * file_record (char * name); // We store an entire file into some memory address.
|
||||
|
||||
#endif
|
||||
|
@ -1,18 +1,16 @@
|
||||
/*
|
||||
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 <stdlib.h>
|
||||
#include <termios.h> // Terminal input, output and configuration.
|
||||
#include <sys/ioctl.h> // Rest is some Linux related cancer...
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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...
|
||||
|
||||
#define CURSES_FORMAT ((int) sizeof ("\033[-;3-m-\033[0m") - 1) // We'll use these three macros through-out cursed functions.
|
||||
#define CURSES_REVERT ((int) sizeof ("\033[H") - 1)
|
||||
#define CURSES_CURSOR ((int) sizeof ("\033[---;---H") - 1)
|
||||
|
||||
/*
|
||||
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'
|
||||
@ -116,8 +114,8 @@ goes for functions declared in "chapter_2.h", but there's another trick to this.
|
||||
|
||||
@C
|
||||
// Option A:
|
||||
static int my_function (char * name, int size);
|
||||
// ...
|
||||
static void * my_function (char * name, int size);
|
||||
|
||||
int my_function (char * name, int size) {
|
||||
int data = 0;
|
||||
|
||||
@ -138,7 +136,7 @@ static int my_function (char * name, int size) {
|
||||
|
||||
Okay, external variables you see lower (without 'static') are all integers. Lets briefly see what types are the internal variables.
|
||||
|
||||
- curses_format / curses_cursor: Array of characters (also known as string!), and their size is already known at the compile time, it's inside square braces.
|
||||
- curses_format / curses_format: Array of characters (also known as string!), and their size is already known at the compile time, it's inside square braces.
|
||||
- curses_screen: Pointer to character, we'll allocate memory for it later, so it'll be a dynamic array of characters, its' size can change.
|
||||
- curses_activator: Pointer to integer, we'll also allocate memory for it, so we can't use square braces for those. Remember that for later.
|
||||
- curses_action: Strictly speaking, pointer to another pointer to function of signature 'void SOMETHING (void);', but ignore it for now, don't get confused.
|
||||
@ -197,7 +195,7 @@ static void curses_exit (void) { curses_active = 0; } // And this is our main fu
|
||||
|
||||
static void curses_initialize (void) { // This function will be called when 'curses_configure' is called, automatically.
|
||||
struct winsize screen_dimension; // We need this ugly structure for our 'ioctl' function to get the dimensions.
|
||||
int screen_memory, lines; // And you can use local variables to shorten some lines of code if you want.
|
||||
int screen_memory; // And you can use local variables to shorten some lines of code if you want.
|
||||
|
||||
fatal_failure (ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension) == -1, // If function 'ioctl' failed, we immediately aborting the entire program.
|
||||
"ioctl: Failed to get terminal dimensions."); // I split those error messages, you can find your own formatting style.
|
||||
@ -222,32 +220,13 @@ static void curses_initialize (void) {
|
||||
"tcsetattr: Failed to set reverse terminal attributes.");
|
||||
|
||||
screen_memory = CURSES_FORMAT * curses_screen_width * curses_screen_height; // This is square area of our terminal, and multiplied by 12, size of FORMAT.
|
||||
lines = (curses_screen_height - 1) * 2; // This is size of line feed and carriage return to avoid word-wrapping.
|
||||
|
||||
curses_screen = allocate (CURSES_REVERT + screen_memory + lines + CURSES_CURSOR + 1); // We're requesting new memory for framebuffer.
|
||||
curses_screen = allocate (CURSES_REVERT + screen_memory + CURSES_CURSOR + 1); // We're requesting new memory for framebuffer.
|
||||
|
||||
curses_bind (SIGNAL_ESCAPE, curses_exit); // Binding universal exit key (signal).
|
||||
|
||||
string_copy (& curses_screen [0], "\033[H"); // ASCII black magic to always clear screen.
|
||||
|
||||
for (lines = 1; lines < curses_screen_height; ++lines) { // Now it's time to put forced line breaks in raw terminal, without the last one.
|
||||
int skip = CURSES_REVERT + 2 * (lines - 1); // We skip first 3 bytes and previously copied amount of line breaks.
|
||||
int next = lines * CURSES_FORMAT * curses_screen_width; // And now we offset full width of our terminal, this makes it faster...
|
||||
string_copy_limit (curses_screen + skip + next, "\r\n", string_length ("\r\n")); // And lastly, we copy those line breaks at this offset into our screen buffer.
|
||||
} // Keep in mind that word-wrapping is slow on some terminals, hence I use this.
|
||||
|
||||
// So, what's difference with using these two examples? Really, nothing.
|
||||
// string_copy (& string [offset], source);
|
||||
// string_copy ( string + offset , source);
|
||||
// Unary operator '&' references the variable, returning it's memory address (pointer of its' type).
|
||||
// Unary operator '*' (not multiplication!) dereferences the variable, returning value found at some memory address (with type).
|
||||
// Arrays in C are just pointers to the first element of that array, and since they lay next to each other in memory, we can access them by doing:
|
||||
// array [element] <=> * (array + sizeof (* array) * element)
|
||||
// So, to explain, we're adding pointer to the first element of that array with size of one element multiplied by index of wanted element, and dereferencing that.
|
||||
// Since referencing and then immediately dereferencing something does nothing, we can ommit that '& (* variable)' into just 'variable'.
|
||||
// & array [element] <=> array + sizeof (* array) * element
|
||||
// In the end, use whatever you like, compiler will make sure to optimize it, since this is a simple optimization process, it won't cause bugs.
|
||||
|
||||
terminal_clear ();
|
||||
}
|
||||
|
||||
@ -256,8 +235,8 @@ static void curses_deinitialize (void) {
|
||||
curses_activator = deallocate (curses_activator);
|
||||
curses_action = deallocate (curses_action);
|
||||
|
||||
curses_action_count = 0; // I just set everthing into default state, so we can use curses multiple times.
|
||||
curses_character = 0; // This way, it's all safe and clean, even with (de)initializing it twice.
|
||||
curses_action_count = 0;
|
||||
curses_character = 0;
|
||||
curses_signal = SIGNAL_NONE;
|
||||
curses_screen_width = 0;
|
||||
curses_screen_height = 0;
|
||||
@ -276,15 +255,13 @@ comes from top of my head is 'echo' function from chapter zero. It'd be boring t
|
||||
string literals to terminal in most cases. But that doesn't mean that 'out' is useless, as you'll see, we'll use it in 'curses_synchronize' funcion. Cons are, if you really have
|
||||
repeating code all over your program, if there's a bug in one of them, there's a bug in all of them. Also, if you'll use some extracted function (also refered to as refactored in
|
||||
some cases) only once, it's not a bad thing. We've extracted some code into 'curses_initialize' function, and we call it only once, inside 'curses_configure' function, but the
|
||||
intention behind what it does is clear. However, I still believe that procedural code, that's executed line by line, from top to bottom, is best. You can use 'curses_configure'
|
||||
and main loop with 'curses_synchronize' inside more functions, and it'll clean itself after the program exits.
|
||||
intention behind what it does is clear. However, I still believe that procedural code, that's executed line by line, from top to bottom, is best.
|
||||
|
||||
Now, we could also implement some error checking functions for 'curses_*' functions. Good idea, when something is wrong, like we want to render a character out of the screen, we
|
||||
just print an error message to terminal, right? Well, no. Remember, we're using terminal as a framebuffer (about which we'll talk about a lot more in ray tracing and rasterization
|
||||
chapters), so if we just print message there, it'll corrupt our framebuffer. So, we could just write them to some log file. How about binding them, whenever something is wrong,
|
||||
we don't just abort the program or stop rendering, but continue running it, while also writing error messages to that same file. Then, when we exit the program, it'll print all
|
||||
error messages (if any) normally. It could be done like something below, but since I know what I'm doing (for the most part), I'll just comment them out inside functions, and
|
||||
leave 'log_in' and 'log_out' unimplemented.
|
||||
error messages (if any) normally. Here's how it would look like:
|
||||
|
||||
@C
|
||||
// Enumeration for log type.
|
||||
@ -311,10 +288,10 @@ static char * curses_screen_offset (int x, int y) {
|
||||
// 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); // We're limiting the values of X and Y coordinates to screen dimensions.
|
||||
limit (& y, 0, curses_screen_height - 1); // Function 'limit' uses inclusive values for minimum and maximum.
|
||||
limit (& x, 0, curses_screen_width - 1); // We're limiting the values of X and Y coordinates to screen dimensions.
|
||||
limit (& y, 0, curses_screen_height - 1); // Function 'limit' uses inclusive values for minimum and maximum.
|
||||
|
||||
return (& curses_screen [CURSES_REVERT + 2 * y + CURSES_FORMAT * (y * curses_screen_width + x)]); // And returning the offset of the screen buffer.
|
||||
return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]); // And returning the offset of the screen buffer.
|
||||
}
|
||||
|
||||
static char * curses_format_character (char character, int colour, int effect) {
|
||||
@ -365,20 +342,18 @@ void curses_configure (void) {
|
||||
}
|
||||
|
||||
void curses_synchronize (void) {
|
||||
int signal, length;
|
||||
int signal;
|
||||
|
||||
curses_signal = curses_character = signal = 0; // Reassigning signals to 0.
|
||||
|
||||
length = CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + 2 * (curses_screen_height - 1) + CURSES_CURSOR;
|
||||
|
||||
out (curses_screen, length); // We output the entire framebuffer to terminal (present).
|
||||
out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR); // We output the entire framebuffer to terminal (present).
|
||||
|
||||
in (& signal, (int) sizeof (signal)); // We're now checking for user input to modify it.
|
||||
|
||||
curses_character = signal; // We need literal value of 'signal' for text input.
|
||||
|
||||
if (signal == '\033') { // And then we modify the actual 'curses_signal'.
|
||||
curses_signal = SIGNAL_ESCAPE;
|
||||
curses_signal |= SIGNAL_ESCAPE;
|
||||
} else if (character_is_digit ((char) signal) != 0) {
|
||||
curses_signal |= SIGNAL_0 + (int) (signal - '0');
|
||||
} else if (character_is_lowercase ((char) signal) != 0) {
|
||||
@ -386,8 +361,6 @@ void curses_synchronize (void) {
|
||||
} else if (character_is_uppercase ((char) signal) != 0) {
|
||||
curses_signal |= SIGNAL_A + (int) (signal - 'A');
|
||||
curses_signal |= SIGNAL_SHIFT;
|
||||
} else if ((signal == SIGNAL_ARROW_UP) || (signal == SIGNAL_ARROW_DOWN) || (signal == SIGNAL_ARROW_RIGHT) || (signal == SIGNAL_ARROW_LEFT)) {
|
||||
curses_signal = signal;
|
||||
} else {
|
||||
curses_signal = SIGNAL_NONE;
|
||||
}
|
||||
@ -395,7 +368,6 @@ void curses_synchronize (void) {
|
||||
for (signal = 0; signal != curses_action_count; ++signal) { // Now, it's time to loop over bound actions.
|
||||
if (curses_signal == curses_activator [signal]) { // If we have a bound signal, then:
|
||||
curses_action [signal] (); // We execute corresponding action (function).
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,20 +415,19 @@ void curses_render_cursor (int x, int y) {
|
||||
// We're adding one because ASCII uses 1 ... 1000, and we use 0 ... 999, so it doesn't causes hidden bugs, and realign them to be prefixed with '0' characters.
|
||||
// If we have set of values (x = 31, y = 37), then the copied string would look like "\033[031;037H". It's not complex as it may sound.
|
||||
// And remember our ASCII table, thing scary thing (\033) is just octal value for number 27, which is CHARACTER_ESCAPE, hence the escape sequences start with it.
|
||||
int offset;
|
||||
|
||||
x %= 1000; // I don't care for terminals bigger than this...
|
||||
x %= 1000;
|
||||
y %= 1000;
|
||||
|
||||
string_copy_limit (curses_cursor + 2, string_realign (number_to_string (y + 1), 3, '0'), 3); // We're copying 0...999 number as string, with 0s.
|
||||
string_copy_limit (curses_cursor + 6, string_realign (number_to_string (x + 1), 3, '0'), 3); // Those prefix 0s must be used with this.
|
||||
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);
|
||||
|
||||
offset = CURSES_REVERT + 2 * (curses_screen_height - 1) + CURSES_FORMAT * curses_screen_width * curses_screen_height; // We need to offset it at the end of our screen.
|
||||
|
||||
string_copy_limit (& curses_screen [offset], curses_cursor, CURSES_CURSOR); // And only then copy cursor data into screen.
|
||||
string_copy_limit (& curses_screen [CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height], curses_cursor, CURSES_CURSOR); // Actual rendering.
|
||||
}
|
||||
|
||||
void curses_render_character (char character, int colour, int effect, int x, int y) {
|
||||
if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) { // If any of these are true, we don't render.
|
||||
return;
|
||||
}
|
||||
// Again, lets show some code formatting examples:
|
||||
// if ((x < 0)
|
||||
// || (y < 0)
|
||||
@ -474,10 +445,6 @@ void curses_render_character (char character, int colour, int effect, int x, int
|
||||
// Or if you really hate adding 2 more lines of code and curly braces:
|
||||
// if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) return;
|
||||
|
||||
if ((x < 0) || (x > curses_screen_width - 1) || (y < 0) || (y > curses_screen_height - 1)) { // If any of these are true, we don't render.
|
||||
return;
|
||||
}
|
||||
|
||||
string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT); // Again, actual rendering, copying a value to offset.
|
||||
}
|
||||
|
||||
@ -491,26 +458,6 @@ void curses_render_background (char character, int colour, int effect) {
|
||||
}
|
||||
}
|
||||
|
||||
void curses_render_rectangle_fill (char character, int colour, int effect, int x, int y, int width, int height) {
|
||||
for (int j = 0; j < height; ++j) { // You can declare type of those iterators in for loops.
|
||||
for (int i = 0; i < width; ++i) { // This only works if you're not using ANSI C (C89 / C90) standard.
|
||||
curses_render_character (character, colour, effect, x + i, y + j); // Now, we render character by character again...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void curses_render_rectangle_line (char character, int colour, int effect, int x, int y, int width, int height) {
|
||||
for (int offset = 0; offset < width; ++offset) { // Now, we only want to render line, rectangle has 4 lines, so we need 2 loops.
|
||||
curses_render_character (character, colour, effect, x + offset, y); // First we're rendering horizontal lines, then vertical lines.
|
||||
curses_render_character (character, colour, effect, x + offset, y + height - 1); // We also need to offset X or Y, depending on rectangle width or height.
|
||||
}
|
||||
|
||||
for (int offset = 0; offset < height; ++offset) { // Now, we only want to render line, rectangle has 4 lines, so we need 2 loops.
|
||||
curses_render_character (character, colour, effect, x, y + offset); // I prefer to use 'offset' instead of 'i' and 'j', but I have no strict rule.
|
||||
curses_render_character (character, colour, effect, x + width - 1, y + offset); // I'm mixing them here, so you can see what you find more readable.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
We've mentioned before, in chapter zero, that you can implement 'string_*' functions by 'string_*_limit', and here's an example of that. Functions below are quite self
|
||||
explanatory, so you can do a "homework" of reading them and trying to understand what they'll do. Since I use very verbose naming style, I hope that won't be a problem...
|
||||
@ -529,7 +476,7 @@ int curses_render_string_limit (char * string, int limit, int colour, int effect
|
||||
|
||||
for (offset = 0; offset != limit; ++offset) {
|
||||
if (string [offset] == '\n') {
|
||||
x *= 0;
|
||||
x = 0;
|
||||
y += 1;
|
||||
} else if (string [offset] == '\t') {
|
||||
x += 8;
|
||||
|
@ -1,25 +1,23 @@
|
||||
/*
|
||||
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_HEADER
|
||||
#define CHAPTER_2_HEADER
|
||||
|
||||
#include <stdio.h> // We need this header file for functions 'puts' and 'printf' in 'hello_world' functions below.
|
||||
#include <stdio.h> // We need this header file for functions 'puts' and 'printf'.
|
||||
#include <unistd.h> // And in this header file we have write system call.
|
||||
|
||||
/*
|
||||
In this chapter, you'll learn about:
|
||||
|
||||
- Curses library (libncurses "clone")
|
||||
- More on function declarations
|
||||
- Escape sequences
|
||||
- General scope and design layout
|
||||
- Function and variable definitions
|
||||
- Terminal input and output
|
||||
|
||||
Lets talk about function declaration. In C, sometimes you need to declare functions that you'll use before they're called. I prefer to always declare functions except when I'm
|
||||
quickly prototyping something out. Function declarations are property of old programming languages, you don't have to use them if you're calling every function after it's defined
|
||||
(but not declared, there's a difference), but if that function is only used in one file, you should use 'static' keyword like in the example below. Keyword 'extern' tells the
|
||||
compiler that your function will be used in other files, or if you're compiling your files separately, it won't add the function address (in some cases). In any case, function
|
||||
declarations should be in C header file, and function definitions should be in C source file. You can see how I declare functions in '.h' files, and define them in '.c' files,
|
||||
unless I don't want to make some function external in some '.c' file, I use 'static' there.
|
||||
declarations should be in C header file, and function definitions should be in C source file.
|
||||
|
||||
@C
|
||||
// Function declaration: // # // Output: // Input:
|
||||
@ -57,8 +55,7 @@ Now, lets talk very briefly about what's wrong with 'PLEASE_NO':
|
||||
- Keep in mind that newer (non-ANSI) C standards allow some of the things that I'm not using here, but I don't personally like newer C standards, I'll mention that a lot.
|
||||
- Last one is tricky, you should name function agruments in function declarations, but some linters will warn you not to do it, since some compiler don't check them.
|
||||
|
||||
Very soon, you'll be able to write your own small C programs, so prepare for it. Also, the most important chemical element for organisms is, you guessed it, C (carbon). It's
|
||||
the same in programming too, C is the most important language that a good programmer should know. Other languages are just extra. Now:
|
||||
Very soon, you'll be able to write your own small C programs, so prepare for it.
|
||||
|
||||
This is probably the first program new programmers write in language they're learning. It simply prints text to standard output. As C is very old programming language, you have a
|
||||
lot of ways to do it, so we'll simply put all of them into array of function pointers, and call them sequentially in loop in our main function.
|
||||
@ -79,6 +76,11 @@ teletypewriters, terminal was obscure magic. Nowdays, parents scare their childr
|
||||
you won't be able to see icons, you won't pass the Login text on your screen, you'll never open your web browser again (lynx rules).
|
||||
*/
|
||||
|
||||
#include <termios.h> // Terminal input, output and configuration.
|
||||
#include <sys/ioctl.h> // Rest is some Linux related cancer...
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "chapter_0.h" // We use functions declared in these two header files, so in order to make them visible, we included those files here.
|
||||
#include "chapter_1.h"
|
||||
|
||||
@ -92,8 +94,11 @@ you won't be able to see icons, you won't pass the Login text on your screen, yo
|
||||
#define SIGNAL_ALTERNATE (0X4000000)
|
||||
#define SIGNAL_SYSTEM (0X8000000)
|
||||
|
||||
enum {
|
||||
// This enumeration will be used for signal processing, I put some special cases on their own line.
|
||||
#define CURSES_FORMAT ((int) sizeof ("\033[-;3-m-\033[0m") - 1) // We'll use these three macros through-out cursed functions.
|
||||
#define CURSES_REVERT ((int) sizeof ("\033[H") - 1)
|
||||
#define CURSES_CURSOR ((int) sizeof ("\033[---;---H") - 1)
|
||||
|
||||
enum { // This enumeration will be used for signal processing.
|
||||
SIGNAL_NONE,
|
||||
SIGNAL_ANY,
|
||||
SIGNAL_A, SIGNAL_B, SIGNAL_C, SIGNAL_D, SIGNAL_E, SIGNAL_F, SIGNAL_G, SIGNAL_H,
|
||||
@ -111,8 +116,7 @@ These below are external variables, they can be accessed and modified in any fil
|
||||
output object files (.o), and then we can link them together (most compilers are also linkers, this is nothing out of ordinary) into final executable. We can, for example, use
|
||||
functions declared in "chapter_2.h" and defined in "chapter_2.c" in completely separate file "chapter_3.c", if we have included it with '#include "chapter_2.h". Keep in mind that
|
||||
header files can be included recursively, that's why we use those header guards, '#ifndef SOMETHING', '#define SOMETHING' and '#endif' at the end. That way, they'll be included
|
||||
only once, so compiler won't be confused and spit out errors. You can see how I use those chapters to create new programs, and all that is in 'program/' folder. You can also just
|
||||
open some 'compile.sh' file or 'makefile' and see how I compile and link.
|
||||
only once, so compiler won't be confused and spit out errors.
|
||||
|
||||
Now, lets talk about ASCII escape sequences. Most terminals support some special combination of characters, that aren't printed like you'd normally expect. We call them escape
|
||||
sequences because they all start with character literal '\033', '\x1b' or '\e', which is same as decimal 27, and in our character enumeration 'CHARACTER_ESCAPE'. They are somewhat
|
||||
@ -143,7 +147,7 @@ Effects:
|
||||
- Cyan: 6
|
||||
- White: 7
|
||||
|
||||
Instead of hardcoding values yourself and remembering those, you can just use enumeration identifiers, like, COLOUR_BLUE and EFFECT_BOLD.
|
||||
Instead of hardcoding values yourself and remembering those, you can just use enumerations from "chapter_0.h", like, COLOUR_BLUE and EFFECT_BOLD.
|
||||
*/
|
||||
|
||||
extern int curses_character; // Literal character (integer) that user has pressed, some of them are more than single byte, like arrow keys defined above.
|
||||
@ -152,147 +156,6 @@ extern int curses_screen_width; // Width of terminal window in which we're rend
|
||||
extern int curses_screen_height; // Height of terminal window in which we're rendering.
|
||||
extern int curses_active; // As long as there's no termination signal, this variable will be different from zero. We use it to exit the program.
|
||||
|
||||
/*
|
||||
Now, lets talk about scope. Roughly speaking, every variable, constant, function, enumeration, structure, union, definition, declaration, file, anything really has some scope,
|
||||
where it is known to the compiler, visible. If, for example you're using 'printf' function from <stdio.h> header file, and you didn't write "#include <stdio.h>" before that line
|
||||
in which you're using it (and outside of function definition, that's K&R thing, I'll explain later), compiler will get scared and confused, and it'll just drop dead. You're left
|
||||
with no executable, compiler spat out some error messages, you don't know what they mean, it's over.
|
||||
|
||||
So, if you have something like this:
|
||||
|
||||
@C
|
||||
int F (int A) {
|
||||
int V = randomize (0, A);
|
||||
|
||||
return (A + V);
|
||||
}
|
||||
@
|
||||
|
||||
Variable 'V' is local to function 'F', and to no other function, you can only use variable 'V' inside function 'F', but you can name other local variable as 'V' too, no problems.
|
||||
|
||||
Inside file named "a.c", for example:
|
||||
|
||||
@C
|
||||
static int X = 100;
|
||||
static int Y = 200;
|
||||
static int Z = 300;
|
||||
@
|
||||
|
||||
Variables 'X', 'Y' and 'Z' are only visible in file "a.c", and no other file. You can modify them inside any function in that file, but don't name any local variables as that.
|
||||
|
||||
You want to use global variables? You should have declarations in header file, and definitions in source file:
|
||||
|
||||
@C
|
||||
// a.h
|
||||
extern char * A;
|
||||
extern char * B;
|
||||
extern char * C;
|
||||
@
|
||||
|
||||
@C
|
||||
// a.c
|
||||
#include "path/to/file/a.h"
|
||||
// ...
|
||||
char * A = "Heyo";
|
||||
char * B = "Cyaa";
|
||||
char * C = "Meme";
|
||||
@
|
||||
|
||||
In this case, you can modify variables 'A', 'B' and 'C' in any file that has included header file "a.h", not only "a.c", but you must define them only once, as shown here.
|
||||
|
||||
When you forget to include some header file, provide a variable, constant, function declaration or definition, compiler will warn you about it, because it can guess in simple
|
||||
cases where that function is declared, but it can't (shouldn't) scan your entire SSD or HDD to find it, because it would take too long and maybe you just mistyped the name of it.
|
||||
If you forget to write "#include <unistd.h>", and use write or read "functions", the compiler will kindly tell you to include it, but it would assume you want to use that very
|
||||
functions, and it will compile it anyway, giving you your executable in hope it's correct. It can't do that always...
|
||||
|
||||
In K&R C, aka old C, it was something like this (don't mind the stupid example):
|
||||
|
||||
@C
|
||||
// K&R C (with a lot of implcit state) // Sadly new C standards (verbose and "safe")
|
||||
putchar (c) { extern int putint (int file, int data, int size);
|
||||
extern putint;
|
||||
static int putchar (int c) {
|
||||
putint (1, c, 1); return (putint (1, c, 1));
|
||||
} }
|
||||
@
|
||||
|
||||
Essentially, you'll get the feeling for it after few compiler warnings, don't be afraid to make mistakes that compiler can catch.
|
||||
|
||||
About my preffered program layout, here's what I think about it:
|
||||
|
||||
@C
|
||||
// License notice if you want.
|
||||
|
||||
// Header guards or definitions.
|
||||
#ifndef BLA_BLA_HEADER
|
||||
#define BLA_BLA_HEADER
|
||||
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
// Standard library C header files.
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// User provided C source or header files.
|
||||
#include "heyo.h"
|
||||
#include "cyaa.c"
|
||||
|
||||
// Macros that you really want to have.
|
||||
#define UNUSED(x) (void) x
|
||||
|
||||
// Internal function declarations then variable definitions.
|
||||
static void uberheyo (void);
|
||||
// Or:
|
||||
static void ubercyaa (void) {
|
||||
// ...
|
||||
}
|
||||
|
||||
static int h = 0;
|
||||
static int c = 1;
|
||||
|
||||
// External function then variable declarations.
|
||||
extern void heyo (void);
|
||||
extern void cyaa (void);
|
||||
|
||||
extern int H;
|
||||
extern int c;
|
||||
|
||||
// Internal function definition, if not defined above...
|
||||
void uberheyo (void) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// External function then variable definition, if not in included C source file.
|
||||
void heyo (void) {
|
||||
// ...
|
||||
}
|
||||
|
||||
void cyaa (void) {
|
||||
// ...
|
||||
}
|
||||
|
||||
int H = 0;
|
||||
int C = 1;
|
||||
|
||||
// Main function.
|
||||
int main (void) {
|
||||
// ...
|
||||
}
|
||||
// Or:
|
||||
int main (int argc, char * * argv) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// You can also define internal or external functions here if you want to.
|
||||
|
||||
// End of header guards.
|
||||
#endif
|
||||
@
|
||||
|
||||
Find one style, and try to be consistent about it, I have to show-case more example of how things look like, so I use more approaches...
|
||||
*/
|
||||
|
||||
extern void curses_configure (void); // I like using the approach of "just do it" instead of "begin" and "end", due to that we'll see more internal functions and variables.
|
||||
extern void curses_synchronize (void); // This function needs to be explained in more details, that'll be done in file "chapter_2.c".
|
||||
|
||||
@ -304,9 +167,6 @@ extern void curses_render_cursor (int x, int y); // Render terminal cursor at po
|
||||
extern void curses_render_character (char character, int colour, int effect, int x, int y); // Render single character at position X and Y.
|
||||
extern void curses_render_background (char character, int colour, int effect); // Render entire buffer with the same character.
|
||||
|
||||
extern void curses_render_rectangle_fill (char character, int colour, int effect, int x, int y, int width, int height); // Guess what these functions do...?
|
||||
extern void curses_render_rectangle_line (char character, int colour, int effect, int x, int y, int width, int height);
|
||||
|
||||
// Remember that in chapter zero, I've separated 'string_*' and 'string_*_limit' functions. Now, there's always more ways to logically organize your code, for example, as below:
|
||||
extern int curses_render_string (char * string, int colour, int effect, int x, int y);
|
||||
extern int curses_render_number (int number, int colour, int effect, int x, int y);
|
||||
|
@ -1,9 +1,14 @@
|
||||
/*
|
||||
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_3_SOURCE
|
||||
#define CHAPTER_3_SOURCE
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "chapter_3.h"
|
||||
|
||||
/*
|
||||
@ -30,6 +35,12 @@ can fall-through, like in the example below. Sad truth is, people are dumb, and
|
||||
case(s) in your switch statement. Lets explain what 'echo_one_by_one' function would do.
|
||||
|
||||
@C
|
||||
// Print out some stuff, it'll look like this for these inputs (and it'll do nothing for input that isn't between 0 and 3 (inclusive)):
|
||||
// echo_one_by_one (0) => "Zero One Two Three"
|
||||
// echo_one_by_one (1) => "One Two Three"
|
||||
// echo_one_by_one (2) => "Two Three"
|
||||
// echo_one_by_one (3) => "Three"
|
||||
|
||||
static void echo_one_by_one (int number) {
|
||||
switch (number) {
|
||||
case 0: echo ("Zero ");
|
||||
@ -39,18 +50,13 @@ static void echo_one_by_one (int number) {
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Print out some stuff, it'll look like this for these inputs (and it'll do nothing for input that isn't between 0 and 3 (inclusive)):
|
||||
// echo_one_by_one (0) => "Zero One Two Three"
|
||||
// echo_one_by_one (1) => "One Two Three"
|
||||
// echo_one_by_one (2) => "Two Three"
|
||||
// echo_one_by_one (3) => "Three"
|
||||
@
|
||||
|
||||
You can find situations in which fall-through cases are good, for example, they can be very useful when encoding some CPU instructions into machine code, but guess what? The
|
||||
compiler will think you've made some kind of mistake, like that you forgot to break from those cases, and it'll warn you about it. I like to clean all compiler warnings (and some
|
||||
linter warnings, if they're not totally brain-dead), so I just don't use them. I know, sounds stupid, but there's usually some other way to do it, to get the same solution. Since
|
||||
we have several methods for printing text, they use standard output (terminal), file descriptor and a string respectively, we could implement them in separate functions, use
|
||||
linter warnings, if they're not totally brain-dead), so I just don't use them. I know, sounds stupid, but there's usually some other way to do it, to get the same solution.
|
||||
|
||||
Since we have several methods for printing text, they use standard output (terminal), file descriptor and a string respectively, we could implement them in separate functions, use
|
||||
function pointers or simply copy+paste bunch of code into lot of functions, and form a "function family". Lets do something very simple and straight forward. We'll end up with
|
||||
repeated code, but sometimes the simplicity can benefit us more than some smart solutions that are harder to understand. I'll explain as we progress...
|
||||
*/
|
||||
@ -62,8 +68,7 @@ static void to_string (char * data, int size, int file, char * string) { (void)
|
||||
/*
|
||||
Lets break down what's going on here, since it might be confusing for beginners. We've defined 3 internal functions, that'll only be used in this file and no other. They look
|
||||
similar, and they ignore some of their arguments by casting them to 'void', that's how you silence the compiler warnings about unused function agruments. But why are we passing
|
||||
those arguments if we won't use them? Because we can safely use one function pointer to any of those 3 functions. Now, before we proceed, variables can hold memory addresses of
|
||||
other variables, constants and functions. We use that fact now.
|
||||
those arguments if we won't use them? Because we can safely use one function pointer to any of those 3 functions.
|
||||
|
||||
Internal variable 'printing' is a function pointer to one of those 3 functions, and the default value for it is the memory address of function 'to_output'. So, if we just use it,
|
||||
by default it'll print to standard output. If we call functions below, they'll change the value of 'printing' to coresponding function memory address. I chose to use concatenation
|
||||
@ -179,9 +184,6 @@ void print (char * format, ...) {
|
||||
va_end (list); // Every variadic function needs to end with this macro... Pun intended.
|
||||
}
|
||||
@
|
||||
|
||||
Also, needless to say, I don't usually align nor write my programs like this, I did it so you can compare those 3 functions easier. You can see what's changed, what's ignored or
|
||||
what's same. If you're writing something serious, align it properly, not like this please.
|
||||
*/
|
||||
|
||||
void print ( char * format, ...) { va_list list; printing = to_output; va_start (list, format); print_select (format, list, 0, NULL); va_end (list); }
|
||||
|
@ -1,19 +1,20 @@
|
||||
/*
|
||||
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_3_HEADER
|
||||
#define CHAPTER_3_HEADER
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
|
||||
/*
|
||||
In this chapter, you'll learn about:
|
||||
|
||||
- Variadic argument functions
|
||||
- Basic 'printf' functionality
|
||||
- More on function pointers
|
||||
- Control flow switching
|
||||
- Escape characters
|
||||
- More on function definitions
|
||||
|
||||
Now, time has come to talk about (sadly) the most important standard library function in C programming language. Function 'printf' is like some semi-drunk old man, stumbling on
|
||||
the streets, talking to himself, talking to other people who try to ignore him, but if you stand and talk to him, he'll sometimes start talking about random things, and sometimes
|
||||
he'll stay on-topic. So, what does function 'printf' do? Essentially, it'll print formatted string to standard output, formatted in this case means that in the main string (first
|
||||
@ -44,132 +45,45 @@ something I don't like to do mostly. For example, if you pass a 'float' type var
|
||||
that 'va_arg' macro accepts only fully promoted types, which we'll explain below. All that aside, they can be very useful in specific cases!
|
||||
|
||||
You'd use it something like these few examples below, and it's a good practise not to mix 'printf' with 'write' functions (if you're using both of them), because they're
|
||||
synchronized differently. Because of how they're buffered, mixing them can (in some cases) end up with mixed output. So, what the hell are fully promoted types? In short, C really
|
||||
has 4 types, 'int' which is default type (K&R C, but damn compilers warn about that sadly), 'double' for IEEE754 uglyness, 'char *' for string literals and 'void *' for literal
|
||||
memory locations, which are implicit most of the time, as well as implicit labels. I really meant "in short", and ignore 'size_t', I dislike it.
|
||||
|
||||
I have very strict rules about how I write C, but I believe that K&R C was very good, with few minor adjustments...
|
||||
synchronized differently. Because of how they're buffered, mixing them can (in some cases) end up with mixed output.
|
||||
|
||||
@C
|
||||
// And ignore the alignment here, it's just an usage example. You can put all of this into 'main' function in some temporary file, compile it and run it.
|
||||
|
||||
int a = 1; printf ("Integer: %d\n", a); // %d - Format signed integer 'int' as decimal, same as '%i', if unsigned then '%u'.
|
||||
char b = 'A'; printf ("Character: %c\n", b); // %c - Format character (which 'int' implicitly).
|
||||
char * c = "Heyo"; printf ("%s world!\n", c); // %s - Format string (it must be null terminated).
|
||||
float d = 1.0F; printf ("%f\n", (double) d); // %f - Format float, but we're converting it to 'double' type.
|
||||
double e = 1.0; printf ("%F\n", e); // %F - Format double, but same as above, you can specify precision in both of them.
|
||||
uint32_t f = 0X0011AAU; printf ("0X%X\n", f); // %X - Format signed integer as hexadecimal, 'x' for lowercase and 'X' for uppercase.
|
||||
size_t g = sizeof (a); printf ("%lu\n", g); // %lu - Format 'size_t' or 'unsigned long int' on any hardware / OS that matters.
|
||||
int * h = & a; printf ("\t%p", (void *) h); // %p - Format pointer or memory address.
|
||||
printf ("\nCyaa %% world!\n" ); // %% - Output plain old '%' character.
|
||||
int a = 1; printf ("Integer: %d\n", a);
|
||||
char b = 'A'; printf ("Character: %c\n", b);
|
||||
char * c = "Heyo"; printf ("%s world!\n", c);
|
||||
float d = 1.0F; printf ("%f\n", (double) d);
|
||||
double e = 1.0; printf ("%f\n", e);
|
||||
uint32_t f = 0X0011AAU; printf ("0X%X\n", f);
|
||||
size_t g = sizeof (a); printf ("%ul\n", g);
|
||||
int * h = & a; printf ("\t%p", (void *) h);
|
||||
printf ("\nCyaa world!\n" );
|
||||
|
||||
// You can have more than one arguments, like this:
|
||||
|
||||
printf ("%s world!\n Our integer is %d.\n Our character is '%c'.\nCyaa world!\n", c, a, b);
|
||||
|
||||
// Never write this:
|
||||
// Never do this:
|
||||
char * message = "Heyo world!";
|
||||
printf (message);
|
||||
|
||||
// Instead write:
|
||||
// Instead:
|
||||
char * message = "Heyo world!";
|
||||
printf ("%s", message);
|
||||
// Or just:
|
||||
// Or:
|
||||
printf ("Heyo world!");
|
||||
@
|
||||
|
||||
So, the nice thing function 'printf' from <stdio.h> has, and our function 'print' doesn't, is alignment. Also, if you used Fortran programming language, then you'll value C more.
|
||||
You can align data and text to left or right, pad them with a whitespace or zeroes in case of numbers, here's how to do it:
|
||||
|
||||
@C
|
||||
printf ("[%10i]", 144000); // "[ 144000]"
|
||||
printf ("[%3.6f]", 3.14159235359); // "[ 3.141592]"
|
||||
@
|
||||
|
||||
I don't really use it that much, refer to manual pages for more information with '$ man 3 printf'.
|
||||
|
||||
Now, don't get scared, C is old language, and I'm aware that this looks like someone made a joke 50 years ago and nobody understood it, they took it serious. In the end, variadic
|
||||
argument list type 'va_list' is black magic, as well as other 'va_*' stuff. Since 'va_arg' takes only fully promoted types, we're left with types 'int', 'double', 'void *' and
|
||||
'char *'. Also, keep in mind that you don't need more arguments in 'printf' function, only that "const char * fmt" needs to be there always. It may take some time to get used to
|
||||
this function, but you can use it for very quick and easy debugging. If you're not sure what value your variables hold at some point, you can just print it there.
|
||||
argument list type 'va_list' is black magic, as well as other 'va_*' stuff. Since 'va_arg' takes only fully promoted types, we're left with types 'int', 'double' and 'char *'
|
||||
pretty much. Also, keep in mind that you don't need more arguments in 'printf' function, only that "const char * fmt" needs to be there always. It may take some time to get used
|
||||
to this function, but you can use it for very quick and easy debugging. If you're not sure what value your variables hold at some point, you can just print it there.
|
||||
|
||||
I'll show you how to implement variadic argument functions, and we'll use these in few places, but I'm still not a big fan of them. Of course, we won't implement everything that
|
||||
'printf' function from standard library has, or any more of its' alternatives, just these three below. However, we'll add few small additions, like colouring text with ASCII
|
||||
escape sequences and doing some basic formatting. Once you learn C better, you should take a look at manual pages for functions that you find interesting. Or even better, instead
|
||||
of reading stuff, try to implement them yourself, it's the best approach to learning anything.
|
||||
|
||||
If you wonder why did we cover curses in chapter two, and printing in chapter three, which is simpler and shorter essentially, it's because curses use few "black magic" functions
|
||||
from header file <termios.h>, while printing uses <stdarg.h>, which is more difficult to use. We'll use it more in later chapters, but only as a show-case.
|
||||
|
||||
You'll see a clear use-case of function pointers in chapter three, they simply hold the memory address of some function of specific type. In order to understand it better, here's
|
||||
what a naive function would look like in flat assembly (fasm):
|
||||
|
||||
I compiled this with '$ gcc -c -o temporary.o temporary.c', it outputs ELF64 object file, with no memory addresses, since it's not linked yet. Of course, this is very trivial and
|
||||
useless example, which is something I avoid in this "book", but it's too early to learn assembly now, just a small show-case.
|
||||
|
||||
@C
|
||||
void increment_by_4 (int * x) {
|
||||
* x += 4;
|
||||
}
|
||||
@
|
||||
|
||||
This is how I'd do it in flat assembly, without caring for ABI or anything else, pure assembly program, not linked with other object files. I use a lot of global state in my
|
||||
assembly programs, and I keep them clean, not linking it with anything compiled using GCC or other compilers.
|
||||
|
||||
@Flat
|
||||
increment_by_4:
|
||||
add [global_argument_0], 4
|
||||
ret
|
||||
@
|
||||
|
||||
This is how output of '$ objdump -Mintel -d temporary.o' looks like, aka how GCC does it, which is ABI-safe. ABI stands for application binary interface, and it's something people
|
||||
use because of how complex the software became, but on that note, it's a good thing. Naturally, I aligned that output a bit.
|
||||
|
||||
@Text
|
||||
0000000000000000 <increment_by_4>:
|
||||
0: 55 | push rbp
|
||||
1: 48 89 e5 | mov rbp, rsp
|
||||
4: 48 89 7d f8 | mov QWORD PTR [rbp-0x8], rdi
|
||||
8: 48 8b 7d f8 | mov rdi, QWORD PTR [rbp-0x8]
|
||||
C: 8b 07 | mov eax, DWORD PTR [rdi]
|
||||
E: 83 c0 04 | add eax, 0x4
|
||||
11: 89 07 | mov DWORD PTR [rdi], eax
|
||||
13: 5d | pop rbp
|
||||
14: c3 | ret
|
||||
@
|
||||
|
||||
So, as you can see, a function is just a memory address of some point in readable and executable part of binary file, our program (executable) or object file. It can be executed
|
||||
with 'call' instruction, take global variables (in assembly) as arguments, or push them and pop them from the stack, it all depends. C programming languages, and most compilers
|
||||
ensure that when you use function pointers, you know exactly at what kind of function they're pointing to, it'll generate compiler warnings if it expects a pointer to one type of
|
||||
function, and gets some other one. If this doesn't make any sense to you, maybe you should look into the source code, or even better, write it yourself.
|
||||
|
||||
And our last part of "core C" are more details about function definitions. You already saw them in C source files of previous chapters, and there isn't much to add, but think of
|
||||
them as core units of a program. They can't be nested, they are quirky from time to time, they can't be anonymous like some types, enumerations, structures and unions, which is
|
||||
really sad in my opinion, I'd use anonymous functions all the time. Also, functions can only operate on their arguments and their local variables, plus whatever type, enumeration,
|
||||
structure or union is visible to them, including other functions. You'll understand them better after few compiler warnings...
|
||||
|
||||
When you start writing your own little C programs, I advise you to compile and test it with:
|
||||
|
||||
@Shell
|
||||
# Easy mode:
|
||||
|
||||
gcc -Wall -Wpedantic -std=c99 -o my_c_program my_c_source_code.c
|
||||
clang -Wall -Wpedantic -std=c99 -o my_c_program my_c_source_code.c
|
||||
|
||||
valgrind my_c_program
|
||||
|
||||
# Hard mode:
|
||||
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -ansi -o my_c_program my_c_source_code.c
|
||||
clang -Weverything -Werror -ansi -o my_c_program my_c_source_code.c
|
||||
|
||||
splint -weak my_c_source_code.c
|
||||
|
||||
valgrind --show-leak-kinds=all --leak-check=full my_c_program
|
||||
@
|
||||
|
||||
Then again, even if everything compiles nicely, maybe you made a mistake in your algorithm. How to solve that? Well, the answer is same as to question "How to live forever?", it's
|
||||
"Just don't die."... Know what you're doing, and when you're doing it, and you'll be good to go, otherwise, use "printf" to debug it.
|
||||
*/
|
||||
|
||||
extern void print ( char * format, ...); // Notice the "...", which means it can accept any amount of arguments after that 'format'.
|
||||
|
@ -1,8 +1,14 @@
|
||||
/*
|
||||
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_4_SOURCE
|
||||
#define CHAPTER_4_SOURCE
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "chapter_4.h"
|
||||
|
||||
/*
|
||||
@ -48,14 +54,15 @@ about our character and string matching and 'syntax_select' to process our text
|
||||
'syntax_select', will return index of the syntax rule that matches to our offset in string and store size of the match in 'length' variable, we'll look into it.
|
||||
*/
|
||||
|
||||
static int syntax_count = 0; // Number of previously defined syntax rules.
|
||||
static int * syntax_enrange = NULL; // Syntax rule can start with any character from 'syntax_begin' if this value is TRUE.
|
||||
static int * syntax_derange = NULL; // Syntax rule can start with any character from 'syntax_end' if this value is TRUE.
|
||||
static char * * syntax_begin = NULL; // Strings containing valid character (sub)sequence for begining the scan.
|
||||
static char * * syntax_end = NULL; // Strings containing valid character (sub)sequence for ending the scan.
|
||||
static char * syntax_escape = NULL; // Escape sequence for the rule, useful for line-breaks in C macros and line-based languages.
|
||||
static int * syntax_colour = NULL; // Colour for our token, these two could be completely independent, but I like to keep them here.
|
||||
static int * syntax_effect = NULL; // Effect for our token.
|
||||
static int syntax_count = 0; // Number of previously defined syntax rules.
|
||||
static int syntax_active = FALSE; // Syntax "library" or subprogram was initialized if this value is TRUE.
|
||||
static int * syntax_enrange = NULL; // Syntax rule can start with any character from 'syntax_begin' if this value is TRUE.
|
||||
static int * syntax_derange = NULL; // Syntax rule can start with any character from 'syntax_end' if this value is TRUE.
|
||||
static char * * syntax_begin = NULL; // Strings containing valid character (sub)sequence for begining the scan.
|
||||
static char * * syntax_end = NULL; // Strings containing valid character (sub)sequence for ending the scan.
|
||||
static char * syntax_escape = NULL; // Escape sequence for the rule, useful for line-breaks in C macros and line-based languages.
|
||||
static int * syntax_colour = NULL; // Colour for our token, these two could be completely independent, but I like to keep them here.
|
||||
static int * syntax_effect = NULL; // Effect for our token.
|
||||
|
||||
/*
|
||||
Lets go in more details about how this function works. Standard library function 'atexit' will take as an argument function pointer of form 'extern void name (void)' that will,
|
||||
@ -69,10 +76,10 @@ when 'syntax_active' is FALSE, we'll change it to true, so 'atexit' won't be exe
|
||||
we're just deallocating (freeing) the memory, so we don't leak it and generate Valgrind warnings.
|
||||
*/
|
||||
|
||||
static void syntax_delete (void) { // We want to free used resources at certain point in our program, so we can declare this function as internal with 'static' keyword.
|
||||
static void syntax_delete (void) {
|
||||
int offset;
|
||||
|
||||
if (syntax_count == 0) { // If syntax "library" wasn't used, we don't want to deallocate memory, we just return.
|
||||
if (syntax_active == FALSE) { // If 'syntax' subprogram wasn't active, we don't want to deallocate memory, we just return.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -96,7 +103,8 @@ static void syntax_delete (void) { // We want to free used resources at certain
|
||||
syntax_colour = deallocate (syntax_colour);
|
||||
syntax_effect = deallocate (syntax_effect);
|
||||
|
||||
syntax_count = 0; // Lastly, I like to do this, but you don't have to. We'll use it in later chapter tho.
|
||||
syntax_active = FALSE; // Lastly, I like to do this, but you don't have to. We'll use it in chapter five tho.
|
||||
syntax_count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -104,21 +112,22 @@ In 'syntax_define' function we're reallocating (enlarging) memory, effectively a
|
||||
be used with 'syntax_select' function to make our syntax highlighting. Lets explain what those function arguments do:
|
||||
|
||||
@C
|
||||
// To see how I use it, look at 'syntax_highlight_*' functions below.
|
||||
int syntax_define (int enrange, // Strict matching of string 'begin' in buffer range if FALSE, any character matching if TRUE.
|
||||
int derange, // Strict matching of string 'end' in buffer range if FALSE, and again, any character matching if TRUE.
|
||||
char * begin, // String of array of characters to begin matching.
|
||||
char * end, // String of array of characters to end matching, I don't know why I explain these...
|
||||
char escape, // Escape character, useful for C preprocessor.
|
||||
int colour, // Colour.
|
||||
int effect); // Effect, I hate explaining the code when the identifiers are descriptive.
|
||||
static int syntax_define (int enrange, // Strict matching of string 'begin' in buffer range if FALSE, any character matching if TRUE.
|
||||
int derange, // Strict matching of string 'end' in buffer range if FALSE, and again, any character matching if TRUE.
|
||||
char * begin, // String of array of characters to begin matching.
|
||||
char * end, // String of array of characters to end matching, I don't know why I explain these...
|
||||
char escape, // Escape character, useful for C preprocessor.
|
||||
int colour, // Colour.
|
||||
int effect); // Effect, I hate explaining the code when the identifiers are descriptive.
|
||||
@
|
||||
*/
|
||||
|
||||
int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect) {
|
||||
if (syntax_count == 0) { // If our syntax isn't active, we'll execute this only once.
|
||||
static int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect) {
|
||||
if (syntax_active == FALSE) { // If our syntax data isn't active, we'll execute this once.
|
||||
syntax_active = TRUE; // Now we set it to active state, and:
|
||||
|
||||
atexit (syntax_delete); // Mark this function to be executed at program exit point.
|
||||
} // It's same if we use more 'syntax_highlight_*' functions.
|
||||
}
|
||||
|
||||
fatal_failure (begin == NULL, "syntax_define: Begin string is null pointer."); // I don't like checking for errors, but here, voila.
|
||||
fatal_failure (end == NULL, "syntax_define: End string is null pointer.");
|
||||
@ -149,54 +158,44 @@ int syntax_define (int enrange, int derange, char * begin, char * end, char esca
|
||||
}
|
||||
|
||||
/*
|
||||
This is more complex, but if you use your eyes to look, your brain to comprehend and your heart to love, I'm sure that you'll understand it. Essentially, we are at some point in
|
||||
our text file (we're using pointer to type 'char' to indicate where that is), we try to match characters from that point onward with our previously defined syntax rules, either
|
||||
strictly, but using entire string (for keywords) or any character inside it (for numbers, identifiers, etc.). If we found it, we check next character depending on 'syntax_derange'
|
||||
and either finish first loop, or continue. After, we need to know the length of our matched syntax rule, that's it.
|
||||
This is more complex, but if you use your eyes to look, your brain to comprehend and your heart to love, I'm sure that you'll understand it.
|
||||
*/
|
||||
|
||||
int syntax_select (char * string, int * length) {
|
||||
static int syntax_select (char * string, int * length) {
|
||||
int offset, select;
|
||||
|
||||
if (syntax_count == 0) { // Don't select without rules, return!
|
||||
return (syntax_count);
|
||||
}
|
||||
|
||||
fatal_failure (string == NULL, "syntax_select: String is null.");
|
||||
fatal_failure (length == NULL, "syntax_select: Length is null.");
|
||||
fatal_failure (syntax_active == FALSE, "syntax_select: Syntax is not active."); // Don't select without rules, abort!
|
||||
fatal_failure (string == NULL, "syntax_select: String is null.");
|
||||
fatal_failure (length == NULL, "syntax_select: Length is null.");
|
||||
|
||||
// In this first part of the function, we need to check if our syntax rule has been detected at the string offset we've provided. We're looping defined syntax rules and
|
||||
// choosing whether to compare any of the characters, or full string, depending on 'syntax_enrange' value which is essentially boolean, true or false, which I express with
|
||||
// 'int' type for "type-safety simplicity". Keep in mind that we're not returning or modifying the string we provided, so it won't be null-terminated, instead I think
|
||||
// it's best to modify only variable 'length', hence we check with 'string_compare_limit' function.
|
||||
|
||||
for (select = offset = 0; select != syntax_count; ++select) { // We're looping defined syntax rules:
|
||||
int begin = string_length (syntax_begin [select]);
|
||||
|
||||
if (syntax_enrange [select] == FALSE) { // Choosing the comparisson based on 'syntax_enrange':
|
||||
if (syntax_derange [select] == FALSE) { // Either full string, or any character in it.
|
||||
if (string_compare_limit (string, syntax_begin [select], begin) == TRUE) { // Limiting our string comparisson.
|
||||
break; // If strings are same, we exit the loop.
|
||||
for (select = offset = 0; select != syntax_count; ++select) { // We're looping defined syntax rules:
|
||||
if (syntax_enrange [select] == FALSE) { // Choosing the comparisson:
|
||||
if (syntax_derange [select] == FALSE) {
|
||||
if (string_compare_limit (string, syntax_begin [select], string_length (syntax_begin [select])) == TRUE) { // Limiting our string comparisson.
|
||||
break; // If strings are same, we exit the loop.
|
||||
}
|
||||
} else {
|
||||
if ((string_compare_limit (string, syntax_begin [select], begin) == TRUE) // We need to see if we found our string, and:
|
||||
&& (character_compare_array (string [offset + begin], syntax_end [select]) == TRUE)) { // If next character, after the string is in 'syntax_end'.
|
||||
if ((string_compare_limit (string, syntax_begin [select], string_length (syntax_begin [select])) == TRUE)
|
||||
&& (character_compare_array (string [offset + string_length (syntax_begin [select])], syntax_end [select]) == TRUE)) {
|
||||
break;
|
||||
} // Otherwise, we implcitly continue the loop.
|
||||
}
|
||||
}
|
||||
} else { // Else, we compare any character.
|
||||
if (character_compare_array (string [offset], syntax_begin [select]) == TRUE) { // With our obviously named function...
|
||||
break; // We found it, exit the loop!
|
||||
} // If we didn't, just continue.
|
||||
} else { // Else, we compare any character.
|
||||
if (character_compare_array (string [offset], syntax_begin [select]) == TRUE) { // With our obviously named function...
|
||||
break; // We found it, exit the loop!
|
||||
} // If we didn't, just continue.
|
||||
}
|
||||
} // And now we have our 'select' value.
|
||||
} // And now we have our 'select' value.
|
||||
|
||||
// If there was no syntax rule detected, we need to return from a function, and increment the offset by setting variable 'length' to 1. If we don't increment it, at the
|
||||
// first unrecognized character, our second nested-loop inside function 'program_curses_view_file' would use uninitialized or zero value, depending on how we structured
|
||||
// our code before that. We also return 'syntax_count' as the syntax rule index, which is invalid, and would produce Valgrind warning if we didn't handle it. In my
|
||||
// unimportant opinion, this if statement is the ugliest part of the function.
|
||||
|
||||
if (select >= syntax_count) { // If we didn't found our string, return.
|
||||
// first unrecognized character, our second nested-loop inside function 'preview_c_file' would use uninitialized or zero value, depending on how we structured our code
|
||||
// before that. We also return 'syntax_count' as the syntax rule index, which is invalid, and would produce Valgrind warning if we didn't handle it. In my unimportant
|
||||
// opinion, this if statement is the ugliest part of the function.
|
||||
if (select >= syntax_count) {
|
||||
* length = 1;
|
||||
return (syntax_count);
|
||||
}
|
||||
@ -205,48 +204,42 @@ int syntax_select (char * string, int * length) {
|
||||
// to again, separate two cases for matching any character or full string, except that we use it to determine its' match-length. Important difference is also that there's
|
||||
// special case where we have escape character matching, and where 'syntax_end' string is empty (but not NULL), so in that case we match only one character. We could have
|
||||
// nested loop there, and second loop would need goto statement to exit it, so we only use one loop.
|
||||
|
||||
for (offset = 1; string [offset - 1] != '\0'; ++offset) { // Now, offset must be 1, and we loop...
|
||||
int end = string_length (syntax_end [select]);
|
||||
|
||||
if (string [offset] == syntax_escape [select]) { // Here's our escape exception.
|
||||
for (offset = 1; string [offset - 1] != '\0'; ++offset) { // Now, offset must be 1, and we loop...
|
||||
if (string [offset] == syntax_escape [select]) { // Here's our escape exception.
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (syntax_derange [select] == FALSE) { // Choosing what to compare, yet again...
|
||||
if (string_compare_limit (& string [offset], syntax_end [select], end) == TRUE) { // Again, we're comparing full string.
|
||||
* length = offset + end; // We found it, yaay!
|
||||
if (syntax_derange [select] == FALSE) { // Choosing what to compare, yet again...
|
||||
if (string_compare_limit (& string [offset], syntax_end [select], string_length (syntax_end [select])) == TRUE) { // Again, we're comparing full string.
|
||||
* length = offset + string_length (syntax_end [select]); // We found it, yaay!
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (syntax_end [select] [0] == CHARACTER_NULL) { // And here's our empty string exception.
|
||||
* length = offset; // On that case, we break from loop.
|
||||
break;
|
||||
}
|
||||
if (character_compare_array (string [offset], syntax_end [select]) == TRUE) { // Otherwise, we compare to see if the end is near!
|
||||
if (syntax_end [select] [0] == CHARACTER_NULL) { // And here's our empty string exception.
|
||||
* length = offset;
|
||||
break;
|
||||
}
|
||||
} // These two loops look similar, but no!
|
||||
} // And now we have our 'length' value.
|
||||
if (character_compare_array (string [offset], syntax_end [select]) == TRUE) {
|
||||
* length = offset;
|
||||
break;
|
||||
}
|
||||
} // These two loops look similar, but no!
|
||||
} // And now we have our 'length' value.
|
||||
|
||||
return (select); // Lastly, return syntax rule index.
|
||||
return (select); // Lastly, return syntax rule index.
|
||||
}
|
||||
|
||||
/*
|
||||
Imagine my shock, we can now print coloured text, without regular expressions. Nothing much, we can print it without using 'curses_*' functions, but if we want to preview large,
|
||||
well more than 24 line of code, we'd want to scroll it or modify it if we're making a text editor, hence, using curses is good. Lets see how our "mini-main" subprogram-like
|
||||
function does its' work, and how we use 'syntax_*' functions in them, and I also want to make few syntax highlighting abstractions. We can call multiple 'syntax_highlight_*'
|
||||
functions, but it would mix the highlighting of those languages in that case, so we use 'syntax_delete' to reset it.
|
||||
|
||||
Before we begin (Ada pun intended, remove this in final version), I won't (re)align 'separators' and 'keywords', because they fuck-up my comments, which I never write in my
|
||||
"official" programs. I write comments only here, to explain stuff in more details. Have fun... Oh, and type of variable 'keywords' an array of string pointers of automatic length,
|
||||
which we get with "sizeof (keywords) / sizeof (keywords [0])" part, for those keywords, it would be 32UL, and we cast it to integer. I use "long" comments outside of functions,
|
||||
and "short" comments inside them, while aligning them to the longest line of code, or current indentation level.
|
||||
function does its' work, and how we use 'syntax_*' functions in them.
|
||||
*/
|
||||
|
||||
void syntax_highlight_c (void) {
|
||||
void preview_c_file (char * text_file, int width, int height, int x, int y) {
|
||||
// Before we begin (Ada pun intended, remove this in final version), I won't (re)align 'separators' and 'keywords', because they fuck-up my comments, which I never write
|
||||
// in my "official" programs. I write comments only here, to explain stuff in more details. Have fun... Oh, and type of variable 'keywords' an array of string pointers of
|
||||
// automatic length, which we get with "sizeof (keywords) / sizeof (keywords [0])" part, for those keywords, it would be 32UL, and we cast it to integer.
|
||||
char * separators = ".,:;<=>+*-/%!&~^?|()[]{}'\" \t\r\n";
|
||||
|
||||
char * keywords [] = {
|
||||
@ -256,17 +249,18 @@ void syntax_highlight_c (void) {
|
||||
"char", "short", "int", "long", "signed", "unsigned", "float", "double"
|
||||
};
|
||||
|
||||
int word;
|
||||
char * text_data;
|
||||
|
||||
if (syntax_count != 0) { // If syntax was used, free it, then we can redefine them.
|
||||
syntax_delete (); // This way, we won't mix syntaces if we use this multiple times.
|
||||
}
|
||||
int word, reset_x, reset_y;
|
||||
|
||||
syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD); // Below, we're simply using our 'syntax_define' function.
|
||||
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD); // I really don't think I need to explain those, so...
|
||||
syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
(void) width;
|
||||
(void) height;
|
||||
|
||||
syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD); // Below, we're simply using our 'syntax_define' function.
|
||||
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
@ -279,124 +273,33 @@ void syntax_highlight_c (void) {
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
/*
|
||||
Now, why the hell am I talking about Ada programming language again? Well, you see...
|
||||
|
||||
Ada programming language was made in 1980, it was designed around the ideas of procedural programming, strict type safety and readability. As I mentioned before, the way I write C
|
||||
comes from the way I write Ada (in some cases, not always, as they are quite different languages). For example, you can see that Ada has more keywords and less operators than C,
|
||||
and is very strict language, while C is loose. Why is that interesting? Because Ada can interface with libraries written in C, and we'll use that in later chapters. You can spot
|
||||
how different they are, and still, you can write your project mixing those languages. That's the true power of C.
|
||||
|
||||
I'll very briefly comment out what we're using in Ada, so you can see and compare syntax of C and Ada, and here's an example below:
|
||||
|
||||
@Ada
|
||||
with ada.text_io; -- Our <stdio.h> in Ada basically.
|
||||
use ada.text_io; -- With this, we're automatically enabling a namespace.
|
||||
|
||||
procedure hello_world is -- In C, this is 'extern void main (void)' so to say...
|
||||
begin -- This is Ada's way of saying '{'.
|
||||
put_line ("Hello " & "world!"); -- We're concatenating 2 strings and printing them.
|
||||
end hello_world; -- And this is Ada's way of saying '}'.
|
||||
@
|
||||
*/
|
||||
|
||||
void syntax_highlight_ada (void) {
|
||||
char * separators = ".,:;<=>#+*-/&|()\" \t\r\n";
|
||||
|
||||
char * keywords [] = {
|
||||
"abort", "else", "new", "return", "abs", "elsif", "not", "reverse",
|
||||
"abstract", "end", "null", "accept", "entry", "select", "access", "of",
|
||||
"separate", "aliased", "exit", "or", "some", "all", "others", "subtype",
|
||||
"and", "for", "out", "array", "function", "at", "tagged", "generic",
|
||||
"package", "task", "begin", "goto", "pragma", "body", "private", "then",
|
||||
"type", "case", "in", "constant", "until", "is", "raise", "use",
|
||||
"if", "declare", "range", "delay", "limited", "record", "when", "delta",
|
||||
"loop", "rem", "while", "digits", "renames", "with", "do", "mod",
|
||||
"requeue", "xor", "procedure", "protected", "interface", "synchronized", "exception", "overriding",
|
||||
"terminate"
|
||||
};
|
||||
|
||||
int word;
|
||||
|
||||
if (syntax_count != 0) {
|
||||
syntax_delete ();
|
||||
}
|
||||
|
||||
syntax_define (FALSE, FALSE, "--", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
syntax_define (TRUE, FALSE, "()#", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,:;<=>+*-/&|'", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
void program_curses_view_file (char * text_file, int x, int y) {
|
||||
char * text_data; // This local variable will hold our data.
|
||||
|
||||
int reset_x, reset_y, from, to; // Since we're using curses, we want to reset the offset.
|
||||
|
||||
curses_configure (); // Curses configuration, aka printing ugly text.
|
||||
|
||||
switch (file_type (text_file)) { // Depending on our file extension, we select highlighting.
|
||||
case FILE_TYPE_C_SOURCE:
|
||||
case FILE_TYPE_C_HEADER:
|
||||
syntax_highlight_c ();
|
||||
break;
|
||||
case FILE_TYPE_ADA_BODY:
|
||||
case FILE_TYPE_ADA_SPECIFICATION:
|
||||
syntax_highlight_ada ();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
text_data = file_record (text_file); // And, imagine, importing our file data into a buffer!
|
||||
|
||||
reset_x = x;
|
||||
reset_y = y;
|
||||
from = 0;
|
||||
to = string_length (text_data);
|
||||
|
||||
while (curses_active == TRUE) { // We enter our main subprogram loop.
|
||||
for (curses_active = 1; curses_active != 0; ) { // We enter our main subprogram loop.
|
||||
int offset, select, length;
|
||||
|
||||
curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL); // We need to clear the screen buffer before rendering.
|
||||
|
||||
x = reset_x;
|
||||
y = reset_y;
|
||||
select = syntax_count; // I intentionally set this to an invalid value.
|
||||
select = syntax_count;
|
||||
length = 0;
|
||||
|
||||
if (curses_character == 'w') { // WORK IN PROGRESS
|
||||
from -= character_count (text_data, '\0', from, to, '\n');
|
||||
} else if (curses_character == 's') {
|
||||
from += character_count (text_data, '\0', from, to, '\n') + 1;
|
||||
}
|
||||
|
||||
for (offset = from; offset < to; offset += length) { // And it's time to start rendering our file.
|
||||
for (offset = 0; offset < string_length (text_data); offset += length) { // And it's time to start rendering our C file.
|
||||
int suboffset, colour, effect;
|
||||
|
||||
select = syntax_select (& text_data [offset], & length); // Here we're evaluating variables 'select' and 'length'.
|
||||
|
||||
// We can do the same thing in 2 lines of code, but it's less readable in my opinion, I prefer more verbose way below...
|
||||
// We can do the same thing in 2 lines of code, but it's less readable in my opinion, I prefer longer verbose way below...
|
||||
// colour = (select >= syntax_count) ? COLOUR_WHITE : syntax_colour [select];
|
||||
// effect = (select >= syntax_count) ? EFFECT_NORMAL : syntax_effect [select];
|
||||
// Or, if you find this more intuitive:
|
||||
// colour = (select < syntax_count) ? syntax_colour [select] : COLOUR_WHITE;
|
||||
// effect = (select < syntax_count) ? syntax_effect [select] : EFFECT_NORMAL;
|
||||
// And I don't really care for negative values, but feel free to check for them, as they aren't even returned normally.
|
||||
|
||||
if (select >= syntax_count) { // Here, we're handling error value of 'syntax_select'.
|
||||
colour = COLOUR_WHITE;
|
||||
effect = EFFECT_NORMAL;
|
||||
@ -416,10 +319,6 @@ void program_curses_view_file (char * text_file, int x, int y) {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (y >= curses_screen_height) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
curses_synchronize (); // Lastly, we synchronize our terminal.
|
||||
|
@ -1,3 +1,11 @@
|
||||
/*
|
||||
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_4_HEADER
|
||||
#define CHAPTER_4_HEADER
|
||||
|
||||
@ -7,168 +15,24 @@
|
||||
#include "chapter_3.h"
|
||||
|
||||
/*
|
||||
In this chapter, you'll learn about:
|
||||
|
||||
- Programming languages
|
||||
- Syntax highlighting
|
||||
- Importance of readability
|
||||
- Differences between languages
|
||||
- More on memory management
|
||||
- Using curses
|
||||
|
||||
I believe that this chapter should be a breakpoint for you to write a simple C program. So far, we've learned in:
|
||||
|
||||
- chapter 0: To format our code properly in order to increase readability and we've implemented some core functions for memory management, strings and input / output.
|
||||
- chapter 1: To declare and define functions, and we've covered character and file descriptor related functions, as well as ASCII table and discussed C keywords.
|
||||
- chapter 2: To use external variables, function pointers and minor part of 'libncurses' reimplementation that doesn't care about portability.
|
||||
- chapter 3: To use standard library 'printf' function, and to implement variadic argument functions, while also covering functions and switch statement in more depth.
|
||||
- chapter 3: To use standard library 'printf' function, and to implement variadic argument functions, while also covering switch statement in more depth.
|
||||
|
||||
From this moment onwards, some chapters will have few functions called 'program_*', which we can use to build even larger programs. They'll each have their own dependencies, for
|
||||
From this moment onwards, some chapters will have few functions called 'program_*', which we can use to forge even larger programs. They'll each have their own dependencies, for
|
||||
example, some of them will require functions from some or all previous chapter source and header files, but I'll make sure not to use in them functions that'll be in future
|
||||
chapters. Instead of that, we'll (re)implement newer stuff with different approach if necessary. That way, you can be sure that if you're reading chapter four, for example, it'll
|
||||
only use functions and variables defined in chapters zero to three. Lets begin.
|
||||
|
||||
I'll write this huge 'program_curses_view_file' function in somewhat procedural style of programming, so to say, and in the next chapter, we'll use more modular way, using many
|
||||
more functions. Learning anything, including the C programming language, is like a journey. Maybe you think it won't last long, and it ends up being quite long journey, or maybe
|
||||
you think it'll be very long, that you'll walk miles and miles, and it ends up being short (you rage-quit). The final destination you're going towards always depends on where you
|
||||
left-off and where you're coming from. For example, if you wrote Ada, you'll like chapter four, if you wrote C++, you'll like chapter five.
|
||||
|
||||
I'll also list a few "traps" right here, where most programmers get caught in:
|
||||
|
||||
- My program needs to be cross-platform, fully portable, to run on Windblows, Machos, Leenoocks, Raspberries and on Commodore 64.
|
||||
- My program needs to be huge, multiple files and folders, everything is abstracted out, even the wrappers for some library.
|
||||
- My program doesn't need to free used memory, my operating system will do it for me, I don't care about memory leaks, only nerds do.
|
||||
- My compiler warns about stupid things, I don't want to fix all compiler warnings, it'll make the code look bad.
|
||||
|
||||
First of all, there are a lot of standards, people who don't have more important work to do make those. There are a lot of CPU architectures, x86-64 being used a lot, then ISA
|
||||
(instruction set architecture) such as CISC, RISC, MISC, OISC, and even more things that should't matter for you like SIMD, AVX, ST, MMX, XMM, YMM, ZMM, et fucking cetera. Then,
|
||||
we have many many GPU hardware, they each have some part of their own ISA, writing direct code for one GPU won't work on other GPUs, so we need to use OpenGL, Vulkan or Direct3D.
|
||||
Do you see where this is going, adding complexity on top of complexity, abstracting the abstractions, due to standardization. Then again, we have many programming languages, some
|
||||
of them have multiple standards, like C, C++, Ada, Fortran, basically, popular programming languages. For some of those languages, there are multiple compilers, and sometimes they
|
||||
support language extensions that aren't the part of the core language. That's why nothing is truly portable.
|
||||
|
||||
If every company make their own standard, thinking they're smartest, there's no standardization. Just look at the mirror, at your PC, laptop, whatever, then take a look outside
|
||||
the window, and say out loud "My program will be written in C, it will run on 64-bit CPUs, it will depend only on Vulkan API, it will use XCB for display.". Take a deep breath,
|
||||
you're not writing some part of the program for the company, you're having fun, you're sane. Then again, pray to Khronos, your OS maintainers or developers and your GPU vendor
|
||||
that your GPU supports Vulkan, that someone there, out in the big white world wrote a driver for it.
|
||||
|
||||
Keep in mind that I don't work for any programming related company and I want to, also I don't have college, I learned C by writing it a lot and reading it from time to time. Now,
|
||||
hear me out, if 1000 people with some CS degree wrote a simple C program, all of those would look very similar. That's because they've been programmed into that line of thinking,
|
||||
which is dangerous in my opinion for one reason: They think they're always right. I learned a lot from talking with smart people, some of them have CS degree, some not, so I don't
|
||||
own what I know, no one owns anyones' knowledge, but they weren't always right. So, if you don't have a CS degree, you can learn C easier, that's my point.
|
||||
|
||||
C is good programming language to learn first because it shows you what can you create with very simple tools. Now, lets get to syntax highlighting:
|
||||
|
||||
I'll say it as many times as I need, readability is the key to good software. If your code is readable, yet simple, without structure-hell or function-hell, but you don't have the
|
||||
knowledge to optimize it currently, if you share it under some permissive license and show it to other people who (probably) know more about optimizing the program than you, they
|
||||
might have less trouble "fixing" it. Again, this depends on more factors, how many global variables you have, because without them compiler will (maybe / probably) be able to
|
||||
optimize it for you. But the source code being readable is a good start.
|
||||
|
||||
However, no matter how readable and nicely formatted it is, it would be harder to read if everything is printed in white text on black background (like it's with ed text editor).
|
||||
When you open your text editor of choice, as soon as you see certain colour, you know it's a keyword, not an identifier, you notice comments and probably ignore them, it's a good
|
||||
thing, right? So, there are more than one ways to parse a text file and determine what's a keyword, a number, a string, etc. I'm using simple approach from my older program, which
|
||||
was made to replace me constantly using '$ cat text_file.c' in terminal.
|
||||
|
||||
Imagine this:
|
||||
|
||||
This is an array of characters, in C type 'char', and only the last one is '\0', aka CHARACTER_NULL in our enumeration.
|
||||
|
||||
[.................................................................................................................................................................................]
|
||||
|
||||
This is how we parse it, determine where some syntax rule begins, where it ends, how long it is, without changing that array of characters, aka string.
|
||||
|
||||
[.................................................................................................................................................................................]
|
||||
^ - at the start, we're pointing to first character, whose index is 0.
|
||||
^^^ - then we try to match (for example) first 3 characters, if there's a syntax rule like that, with strict comparison.
|
||||
^ - if we matched it, then we need to check if next character, 4th one (with index 3), an ending character or string.
|
||||
^ - if it is, we return index of that rule (with function 'syntax_select') and length is 3, without changing the offset.
|
||||
|
||||
And we repeat that process until we reach the end of the string, that null termination, it's not a fast algorithm, but it's simple. Easy.
|
||||
|
||||
With all that said, learning C is great and all, but lets see some other programming languages, some minimal program in few of them, hello world for example.
|
||||
|
||||
@C
|
||||
// I didn't use preprocessor in this example or real C comments with asterix.
|
||||
extern int puts (const char *);
|
||||
|
||||
int main (void) {
|
||||
puts ("Hello world!");
|
||||
|
||||
return (0);
|
||||
}
|
||||
@
|
||||
|
||||
@Ada
|
||||
-- Also, 'use' keyword would apply the namespace, more on that later.
|
||||
with ada.text_io;
|
||||
|
||||
procedure hello_world is
|
||||
begin
|
||||
ada.text_io.put_line ("Hello world!");
|
||||
end hello_world;
|
||||
@
|
||||
|
||||
@C++
|
||||
// This is C++23 standard, they like new stuff anyway, but it can look more like C if you want.
|
||||
import module std;
|
||||
|
||||
int main () {
|
||||
std::print ("Hello world!\n");
|
||||
}
|
||||
@
|
||||
|
||||
@HolyC
|
||||
// Masterpiece.
|
||||
"Hello world!\n";
|
||||
@
|
||||
|
||||
@Flat
|
||||
; This will run only on 64-bit GNU/Linux OS without issues.
|
||||
format ELF64 executable 3
|
||||
|
||||
segment readable executable
|
||||
mov rax, 1
|
||||
mov rdi, 1
|
||||
mov rsi, string
|
||||
mov rdx, [length]
|
||||
syscall
|
||||
mov rax, 60
|
||||
mov rdi, 0
|
||||
syscall
|
||||
|
||||
segment readable writable
|
||||
string db 'Hello world!', 10, 0
|
||||
length dq $-string
|
||||
@
|
||||
|
||||
@Fortran
|
||||
! Fortran is great programming language, but it can be autistic at times.
|
||||
|
||||
program hello_world
|
||||
print *, 'Hello world!'
|
||||
end program hello_world
|
||||
@
|
||||
|
||||
@Python
|
||||
# Well, they call it "second best language for everything" for some reason...
|
||||
|
||||
print ("Hello world!")
|
||||
@
|
||||
|
||||
Learning C well will make learning those other programming languages, except assembly languages, a walk in the park.
|
||||
I'll write this huge 'preview_c_file' function in procedural style, so to say, and in the next chapter, we'll reimplement it in more modular way, using other functions. Learning
|
||||
anything, including the C programming language, is like a journey. Maybe you think it won't last long, and it ends up being quite long journey, or maybe you think it'll be very
|
||||
long, that you'll walk miles and miles, and it ends up being short (you rage-quit). The final destination you're going towards always depends on where you left-off, where you're
|
||||
coming from. For example, if you wrote Ada, you'll like chapter four, if you wrote C++, you'll like chapter five.
|
||||
*/
|
||||
|
||||
extern int syntax_define (int enrange, int derange, char * begin, char * end, char escape, int colour, int effect); // This must be called before 'syntax_select' function.
|
||||
extern int syntax_select (char * string, int * length); // And we're not dealing with null-terminated strings here.
|
||||
|
||||
// Internally use 'syntax_define' to make C (and Ada below) syntax highlighting, it will use global variables internal to 'chapter_4.c' file.
|
||||
// Worth noting:
|
||||
// - We'll only have one "predefined syntax highlighting" per file type, because we don't want them to clash with each other.
|
||||
// - We could use several syntax highlighting rules if we made one more level of arrays for all those global variables, and a rule to switch between them.
|
||||
// - If you want to add support for some other language, you can try to make, for example 'syntax_highlight_python', it'll be a good exercise.
|
||||
extern void syntax_highlight_c (void);
|
||||
extern void syntax_highlight_ada (void);
|
||||
|
||||
extern void program_curses_view_file (char * text_file, int x, int y); // This is our subprogram that'll view some textual file in terminal, using our curses "library".
|
||||
extern void preview_c_file (char * text_file, int width, int height, int x, int y);
|
||||
|
||||
#endif
|
||||
|
@ -1,264 +1,209 @@
|
||||
/*
|
||||
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_5_SOURCE
|
||||
#define CHAPTER_5_SOURCE
|
||||
|
||||
#include "chapter_5.h"
|
||||
|
||||
#define BLESSES_FONT_WIDTH (8)
|
||||
#define BLESSES_FONT_HEIGHT (8)
|
||||
#define BLESSES_FONT_TABULATOR (4)
|
||||
/*
|
||||
So, what are actually getters and setters, and why you should never use them? Lets explain.
|
||||
|
||||
#define BLESSES_SIGNAL_COUNT (144)
|
||||
@C
|
||||
static number_t game_get_screen_width (game_t * game) { return (game->screen_width); }
|
||||
static number_t game_get_screen_height (game_t * game) { return (game->screen_height); }
|
||||
static number_t game_set_screen_width (game_t * game, number_t width) { return (game->screen_width = width); }
|
||||
static number_t game_set_screen_height (game_t * game, number_t height) { return (game->screen_height = height); }
|
||||
@
|
||||
*/
|
||||
|
||||
static char * blesses_window_title = "xolatile";
|
||||
static int blesses_window_width = 1800;
|
||||
static int blesses_window_height = 900;
|
||||
static int * blesses_sub_framebuffer = NULL;
|
||||
static int * blesses_framebuffer = NULL;
|
||||
#define MEMORY_LIMIT (1024 * 1024)
|
||||
|
||||
static void (* blesses_action [BLESSES_SIGNAL_COUNT]) (void) = { NULL };
|
||||
static string_t memory_store [MEMORY_LIMIT];
|
||||
|
||||
static xcb_window_t blesses_window = 0;
|
||||
static xcb_gcontext_t blesses_context = 0;
|
||||
static xcb_pixmap_t blesses_pixmap = 0;
|
||||
static xcb_connection_t * blesses_connection = NULL;
|
||||
static xcb_screen_t * blesses_screen = NULL;
|
||||
static xcb_image_t * blesses_image = NULL;
|
||||
static memory_t memorize (number_t size) {
|
||||
static number_t memory_count = 0;
|
||||
|
||||
static void blesses_initialize (void) {
|
||||
unsigned int window_flags [2] = {
|
||||
0,
|
||||
XCB_EVENT_MASK_NO_EVENT |
|
||||
XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_RESIZE_REDIRECT |
|
||||
XCB_EVENT_MASK_KEY_RELEASE |
|
||||
XCB_EVENT_MASK_KEY_PRESS |
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS
|
||||
};
|
||||
fatal_failure (memory_count + size >= MEMORY_LIMIT, "memorize: You have reached the 1 MiB memory limit.");
|
||||
|
||||
unsigned short int window_width = (unsigned short int) blesses_window_width;
|
||||
unsigned short int window_height = (unsigned short int) blesses_window_height;
|
||||
memory_count += size;
|
||||
|
||||
blesses_connection = xcb_connect (NULL, NULL);
|
||||
|
||||
fatal_failure (blesses_connection == NULL, "blesses : blesses_initialize : XCB connection is null pointer.");
|
||||
|
||||
print ("[/4XCB/-] /0Connected to X11./-\n");
|
||||
|
||||
blesses_screen = xcb_setup_roots_iterator (xcb_get_setup (blesses_connection)).data;
|
||||
|
||||
blesses_window = xcb_generate_id (blesses_connection);
|
||||
blesses_context = xcb_generate_id (blesses_connection);
|
||||
blesses_pixmap = xcb_generate_id (blesses_connection);
|
||||
|
||||
window_flags [0] = blesses_screen->black_pixel;
|
||||
|
||||
xcb_create_window (blesses_connection, blesses_screen->root_depth, blesses_window, blesses_screen->root, 0, 0, window_width, window_height, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
blesses_screen->root_visual, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, window_flags);
|
||||
|
||||
xcb_map_window (blesses_connection, blesses_window);
|
||||
|
||||
xcb_change_property (blesses_connection, XCB_PROP_MODE_REPLACE, blesses_window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, (unsigned int) string_length (blesses_window_title), blesses_window_title);
|
||||
|
||||
print ("[/4XCB/-] /0Created window./-\n");
|
||||
|
||||
xcb_create_pixmap (blesses_connection, blesses_screen->root_depth, blesses_pixmap, blesses_window, window_width, window_height);
|
||||
|
||||
print ("[/4XCB/-] /0Created pixmap./-\n");
|
||||
|
||||
xcb_create_gc (blesses_connection, blesses_context, blesses_pixmap, 0, NULL);
|
||||
|
||||
print ("[/4XCB/-] /0Created graphic context./-\n");
|
||||
|
||||
xcb_flush (blesses_connection);
|
||||
|
||||
if (blesses_zoom == TRUE) {
|
||||
blesses_sub_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_sub_framebuffer) / 4);
|
||||
} else {
|
||||
blesses_sub_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_sub_framebuffer));
|
||||
}
|
||||
|
||||
blesses_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_framebuffer));
|
||||
return ((memory_t) ((string_t) memory_store + memory_count - size));
|
||||
}
|
||||
|
||||
static void blesses_deinitialize (void) {
|
||||
blesses_sub_framebuffer = deallocate (blesses_sub_framebuffer);
|
||||
blesses_framebuffer = deallocate (blesses_framebuffer);
|
||||
static void move_player (void) { ++player.x; }
|
||||
|
||||
xcb_free_gc (blesses_connection, blesses_context);
|
||||
static void game_configure (void) {
|
||||
curses_configure ();
|
||||
|
||||
print ("[/4XCB/-] /0Deallocated graphic context./-\n");
|
||||
curses_bind (SIGNAL_W, move_player);
|
||||
curses_bind (SIGNAL_S, move_player);
|
||||
curses_bind (SIGNAL_A, move_player);
|
||||
curses_bind (SIGNAL_D, move_player);
|
||||
|
||||
xcb_free_pixmap (blesses_connection, blesses_pixmap);
|
||||
game.active = curses_active;
|
||||
game.screen_width = curses_screen_width;
|
||||
game.screen_height = curses_screen_height;
|
||||
|
||||
print ("[/4XCB/-] /0Deallocated pixmap./-\n");
|
||||
|
||||
xcb_destroy_window (blesses_connection, blesses_window);
|
||||
|
||||
print ("[/4XCB/-] /0Deleted window./-\n");
|
||||
|
||||
xcb_disconnect (blesses_connection);
|
||||
|
||||
print ("[/4XCB/-] /0Disconnected from X11./-\n");
|
||||
player.x = 0;
|
||||
player.y = 0;
|
||||
}
|
||||
|
||||
int blesses_active = FALSE;
|
||||
int blesses_cursor_x = 0;
|
||||
int blesses_cursor_y = 0;
|
||||
int blesses_signal = 0;
|
||||
int blesses_zoom = TRUE;
|
||||
|
||||
void blesses_configure (void) {
|
||||
blesses_active = TRUE;
|
||||
|
||||
blesses_initialize ();
|
||||
static void game_synchronize (void) {
|
||||
return;
|
||||
}
|
||||
|
||||
void blesses_synchronize (void) {
|
||||
int i = 0;
|
||||
game_t game;
|
||||
player_t player;
|
||||
|
||||
unsigned short int window_width = (unsigned short int) blesses_window_width;
|
||||
unsigned short int window_height = (unsigned short int) blesses_window_height;
|
||||
skill_t * game_skill (string_t name, bundle_t * points, ...) {
|
||||
skill_t * skill;
|
||||
va_list list;
|
||||
number_t action;
|
||||
|
||||
xcb_generic_event_t * generic_event = NULL;
|
||||
va_start (list, points);
|
||||
|
||||
if (blesses_zoom == TRUE) {
|
||||
for (i = 0; i != blesses_window_width * blesses_window_height; i++) {
|
||||
blesses_framebuffer [i] = blesses_sub_framebuffer [((i / blesses_window_width) / 2) * (blesses_window_width / 2) + (i % blesses_window_width) / 2];
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i != blesses_window_width * blesses_window_height; i++) {
|
||||
blesses_framebuffer [i] = blesses_sub_framebuffer [i];
|
||||
}
|
||||
skill = memorize ((int) sizeof (* skill));
|
||||
|
||||
string_copy ((skill->name = memorize (string_length (name) + 1)), name);
|
||||
memory_copy ((skill->points = memorize ((int) sizeof (* skill->points))), points, (int) sizeof (* points));
|
||||
|
||||
for (;;) {
|
||||
action = (number_t) va_arg (list, int);
|
||||
|
||||
if (action > 0) {
|
||||
skill->positive_count += 1;
|
||||
skill->positive = memorize ((int) sizeof (action));
|
||||
skill->positive [skill->positive_count - 1] = (action_t) action;
|
||||
} else break;
|
||||
}
|
||||
|
||||
blesses_sub_framebuffer = deallocate (blesses_sub_framebuffer);
|
||||
va_end (list);
|
||||
|
||||
blesses_image = xcb_image_create_native (blesses_connection, window_width, window_height, XCB_IMAGE_FORMAT_Z_PIXMAP, blesses_screen->root_depth,
|
||||
blesses_framebuffer, (unsigned int) (window_width * window_height * (int) sizeof (* blesses_framebuffer)),
|
||||
(unsigned char *) blesses_framebuffer);
|
||||
return (skill);
|
||||
}
|
||||
|
||||
xcb_image_put (blesses_connection, blesses_pixmap, blesses_context, blesses_image, 0, 0, 0);
|
||||
attribute_t * game_attribute (string_t name, bundle_t * points, ...) {
|
||||
attribute_t * attribute;
|
||||
va_list list;
|
||||
number_t action;
|
||||
|
||||
xcb_image_destroy (blesses_image);
|
||||
va_start (list, points);
|
||||
|
||||
xcb_copy_area (blesses_connection, blesses_pixmap, blesses_window, blesses_context, 0, 0, 0, 0, window_width, window_height);
|
||||
attribute = memorize ((int) sizeof (* attribute));
|
||||
|
||||
blesses_framebuffer = NULL;
|
||||
|
||||
generic_event = xcb_wait_for_event (blesses_connection);
|
||||
string_copy ((attribute->name = memorize (string_length (name) + 1)), name);
|
||||
memory_copy ((attribute->points = memorize ((int) sizeof (* attribute->points))), points, (int) sizeof (* points));
|
||||
|
||||
switch (generic_event->response_type & 127) {
|
||||
case XCB_EXPOSE: {
|
||||
xcb_flush (blesses_connection);
|
||||
} break;
|
||||
case XCB_KEY_PRESS: {
|
||||
blesses_signal = (int) ((xcb_key_press_event_t *) generic_event)->detail;
|
||||
if (blesses_signal == 24) {
|
||||
blesses_active = FALSE;
|
||||
}
|
||||
if (blesses_signal == 25) {
|
||||
blesses_zoom = ! blesses_zoom;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
for (;;) {
|
||||
action = (number_t) va_arg (list, int);
|
||||
|
||||
generic_event = deallocate (generic_event);
|
||||
|
||||
if ((blesses_sub_framebuffer == NULL) && (blesses_framebuffer == NULL)) {
|
||||
if (blesses_zoom == TRUE) {
|
||||
blesses_sub_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_sub_framebuffer) / 4);
|
||||
if (action > 0) {
|
||||
attribute->positive_count += 1;
|
||||
attribute->positive = memorize ((int) sizeof (action));
|
||||
attribute->positive [attribute->positive_count - 1] = (action_t) action;
|
||||
} else if (action < 0) {
|
||||
attribute->negative_count += 1;
|
||||
attribute->negative = memorize ((int) sizeof (action));
|
||||
attribute->negative [attribute->negative_count - 1] = (action_t) action;
|
||||
} else {
|
||||
blesses_sub_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_sub_framebuffer));
|
||||
}
|
||||
|
||||
// blesses_sub_framebuffer = allocate ((blesses_window_width * blesses_window_height * (int) sizeof (* blesses_sub_framebuffer)) / ((blesses_zoom == TRUE) ? 4 : 1));
|
||||
|
||||
blesses_framebuffer = allocate (blesses_window_width * blesses_window_height * (int) sizeof (* blesses_framebuffer));
|
||||
}
|
||||
|
||||
if (blesses_active == FALSE) {
|
||||
blesses_deinitialize ();
|
||||
}
|
||||
}
|
||||
|
||||
void blesses_render_background_colour (int background) {
|
||||
int x, y;
|
||||
|
||||
for (y = 0; y != blesses_window_height / ((blesses_zoom == TRUE) ? 2 : 1); ++y) {
|
||||
for (x = 0; x != blesses_window_width / ((blesses_zoom == TRUE) ? 2 : 1); ++x) {
|
||||
blesses_sub_framebuffer [y * (blesses_window_width / ((blesses_zoom == TRUE) ? 2 : 1)) + x] = (int) (0XFF000000 | (unsigned int) background);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
va_end (list);
|
||||
|
||||
return (attribute);
|
||||
}
|
||||
|
||||
void blesses_render_character (char character, int foreground, int background, int x, int y) {
|
||||
const unsigned long int font_code ['~' - ' ' + 2] = {
|
||||
0X0000000000000000, 0X00180018183C3C18, 0X0000000000363636, 0X006C6CFE6CFE6C6C, 0X00187ED07C16FC30, 0X0060660C18306606, 0X00DC66B61C36361C, 0X0000000000181818,
|
||||
0X0030180C0C0C1830, 0X000C18303030180C, 0X0000187E3C7E1800, 0X000018187E181800, 0X0C18180000000000, 0X000000007E000000, 0X0018180000000000, 0X0000060C18306000,
|
||||
0X003C666E7E76663C, 0X007E181818181C18, 0X007E0C183060663C, 0X003C66603860663C, 0X0030307E363C3830, 0X003C6660603E067E, 0X003C66663E060C38, 0X000C0C0C1830607E,
|
||||
0X003C66663C66663C, 0X001C30607C66663C, 0X0018180018180000, 0X0C18180018180000, 0X0030180C060C1830, 0X0000007E007E0000, 0X000C18306030180C, 0X001800181830663C,
|
||||
0X003C06765676663C, 0X006666667E66663C, 0X003E66663E66663E, 0X003C66060606663C, 0X001E36666666361E, 0X007E06063E06067E, 0X000606063E06067E, 0X003C66667606663C,
|
||||
0X006666667E666666, 0X007E18181818187E, 0X001C36303030307C, 0X0066361E0E1E3666, 0X007E060606060606, 0X00C6C6D6D6FEEEC6, 0X006666767E6E6666, 0X003C66666666663C,
|
||||
0X000606063E66663E, 0X006C36566666663C, 0X006666363E66663E, 0X003C66603C06663C, 0X001818181818187E, 0X003C666666666666, 0X00183C6666666666, 0X00C6EEFED6D6C6C6,
|
||||
0X0066663C183C6666, 0X001818183C666666, 0X007E060C1830607E, 0X003E06060606063E, 0X00006030180C0600, 0X007C60606060607C, 0X000000000000663C, 0XFFFF000000000000,
|
||||
0X000000000030180C, 0X007C667C603C0000, 0X003E6666663E0606, 0X003C6606663C0000, 0X007C6666667C6060, 0X003C067E663C0000, 0X000C0C0C3E0C0C38, 0X3C607C66667C0000,
|
||||
0X00666666663E0606, 0X003C1818181C0018, 0X0E181818181C0018, 0X0066361E36660606, 0X003C18181818181C, 0X00C6D6D6FE6C0000, 0X00666666663E0000, 0X003C6666663C0000,
|
||||
0X06063E66663E0000, 0XE0607C66667C0000, 0X000606066E360000, 0X003E603C067C0000, 0X00380C0C0C3E0C0C, 0X007C666666660000, 0X00183C6666660000, 0X006CFED6D6C60000,
|
||||
0X00663C183C660000, 0X3C607C6666660000, 0X007E0C18307E0000, 0X003018180E181830, 0X0018181818181818, 0X000C18187018180C, 0X000000000062D68C, 0X0000000000000000
|
||||
};
|
||||
void game_render_skill (skill_t * skill, number_t x, number_t y) {
|
||||
curses_render_string (skill->name, COLOUR_CYAN, EFFECT_NORMAL, x, y);
|
||||
|
||||
int offset;
|
||||
curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
|
||||
curses_render_string (format_to_string (skill->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x = 22, y);
|
||||
curses_render_string (format_to_string (skill->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x = 26, y);
|
||||
curses_render_string (format_to_string (skill->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x = 30, y);
|
||||
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x = 34, y);
|
||||
}
|
||||
|
||||
if ((x <= -1) || (x >= blesses_window_width / ((blesses_zoom == TRUE) ? 2 : 1) - BLESSES_FONT_WIDTH)) return;
|
||||
if ((y <= -1) || (y >= blesses_window_height / ((blesses_zoom == TRUE) ? 2 : 1) - BLESSES_FONT_HEIGHT)) return;
|
||||
void game_render_attribute (attribute_t * attribute, number_t x, number_t y) {
|
||||
curses_render_string (attribute->name, COLOUR_CYAN, EFFECT_NORMAL, x, y);
|
||||
|
||||
fatal_failure (blesses_sub_framebuffer == NULL, "blesses : render_character : Sub framebuffer was not allocated.");
|
||||
fatal_failure (blesses_framebuffer == NULL, "blesses : render_character : Framebuffer was not allocated.");
|
||||
fatal_failure (character_is_invisible (character), "blesses : render_character : Character is not in visible ASCII range.");
|
||||
fatal_failure (x <= -1, "blesses : render_character : X position is under lower limit.");
|
||||
fatal_failure (y <= -1, "blesses : render_character : Y position is under lower limit.");
|
||||
fatal_failure (x >= blesses_window_width - BLESSES_FONT_WIDTH, "blesses : render_character : X position is above upper limit.");
|
||||
fatal_failure (y >= blesses_window_height - BLESSES_FONT_HEIGHT, "blesses : render_character : Y position is above upper limit.");
|
||||
curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
|
||||
curses_render_string (format_to_string (attribute->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x = 22, y);
|
||||
curses_render_string (format_to_string (attribute->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x = 26, y);
|
||||
curses_render_string (format_to_string (attribute->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x = 30, y);
|
||||
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x = 34, y);
|
||||
}
|
||||
|
||||
for (offset = 0; offset != BLESSES_FONT_WIDTH * BLESSES_FONT_HEIGHT; ++offset) {
|
||||
int u = offset / BLESSES_FONT_WIDTH + y;
|
||||
int v = offset % BLESSES_FONT_WIDTH + x;
|
||||
void play_game (void) {
|
||||
bundle_t skill_points = { 10, 120, 0, 0 };
|
||||
bundle_t attribute_points = { 1, 12, 0, 0 };
|
||||
|
||||
blesses_sub_framebuffer [u * (blesses_window_width / ((blesses_zoom == TRUE) ? 2 : 1)) + v] = ((font_code [(int) (character - ' ')] >> offset) % 2)
|
||||
? foreground
|
||||
: background;
|
||||
skill_t * blades, * axes, * bows, * spears, * puppet_magic, * nature_magic, * rune_magic, * charm_magic;
|
||||
attribute_t * strength, * edurance, * wisdom, * agility;
|
||||
|
||||
strength = game_attribute ("Strength", & attribute_points, GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, -GAME_ACTION_CAMP, 0);
|
||||
edurance = game_attribute ("Edurance", & attribute_points, GAME_ACTION_WALK, GAME_ACTION_CAMP, -GAME_ACTION_REST, 0);
|
||||
wisdom = game_attribute ("Wisdom", & attribute_points, GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM, -GAME_ACTION_WALK, 0);
|
||||
agility = game_attribute ("Agility", & attribute_points, GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR, -GAME_ACTION_WAIT, 0);
|
||||
|
||||
blades = game_skill ("Blades", & skill_points, GAME_ACTION_SWING_BLADE, 0);
|
||||
axes = game_skill ("Axes", & skill_points, GAME_ACTION_SWING_AXE, 0);
|
||||
bows = game_skill ("Bows", & skill_points, GAME_ACTION_SHOOT_ARROW, 0);
|
||||
spears = game_skill ("Spears", & skill_points, GAME_ACTION_THROW_SPEAR, 0);
|
||||
puppet_magic = game_skill ("Puppet Magic", & skill_points, GAME_ACTION_SUMMON_PUPPET, 0);
|
||||
nature_magic = game_skill ("Nature Magic", & skill_points, GAME_ACTION_CALL_NATURE, 0);
|
||||
rune_magic = game_skill ("Rune Magic", & skill_points, GAME_ACTION_CITE_RUNE, 0);
|
||||
charm_magic = game_skill ("Charm Magic", & skill_points, GAME_ACTION_CAST_CHARM, 0);
|
||||
|
||||
game_configure ();
|
||||
|
||||
while (curses_active) {
|
||||
curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
|
||||
curses_render_string ("Attributes:", COLOUR_WHITE, EFFECT_BOLD, 0, 0);
|
||||
|
||||
game_render_attribute (strength, 2, 1);
|
||||
game_render_attribute (edurance, 2, 2);
|
||||
game_render_attribute (wisdom, 2, 3);
|
||||
game_render_attribute (agility, 2, 4);
|
||||
|
||||
curses_render_string ("Skills:", COLOUR_WHITE, EFFECT_BOLD, 0, 5);
|
||||
|
||||
game_render_skill (blades, 2, 6);
|
||||
game_render_skill (axes, 2, 7);
|
||||
game_render_skill (bows, 2, 8);
|
||||
game_render_skill (spears, 2, 9);
|
||||
game_render_skill (puppet_magic, 2, 10);
|
||||
game_render_skill (nature_magic, 2, 11);
|
||||
game_render_skill (rune_magic, 2, 12);
|
||||
game_render_skill (charm_magic, 2, 13);
|
||||
|
||||
curses_render_character ('@', COLOUR_CYAN, EFFECT_BOLD, player.x, player.y);
|
||||
|
||||
curses_synchronize ();
|
||||
|
||||
game_synchronize ();
|
||||
}
|
||||
}
|
||||
|
||||
void blesses_render_string (char * string, int foreground, int background, int x, int y) {
|
||||
blesses_render_string_limit (string, string_length (string), foreground, background, x, y);
|
||||
}
|
||||
|
||||
void blesses_render_number (int number, int foreground, int background, int x, int y) {
|
||||
blesses_render_string (number_to_string (number), foreground, background, x, y);
|
||||
}
|
||||
|
||||
void blesses_render_string_limit (char * string, int limit, int foreground, int background, int x, int y) {
|
||||
int offset;
|
||||
|
||||
for (offset = 0; offset != limit; ++offset) {
|
||||
if (string [offset] == '\n') {
|
||||
x *= 0;
|
||||
y += BLESSES_FONT_HEIGHT;
|
||||
} else if (string [offset] == '\t') {
|
||||
x += BLESSES_FONT_WIDTH * BLESSES_FONT_TABULATOR;
|
||||
} else {
|
||||
blesses_render_character (string [offset], foreground, background, x, y);
|
||||
x += BLESSES_FONT_WIDTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void blesses_render_number_limit (int number, int limit, int foreground, int background, int x, int y) {
|
||||
blesses_render_string_limit (number_to_string (number), limit, foreground, background, x, y);
|
||||
//~char*a,*b,*c;
|
||||
//~a=memorize(12); string_copy(a,"heyo world\n"); terminal_colour(1,1); echo(a); terminal_cancel();
|
||||
//~b=memorize( 3); string_copy(b,"!\n"); terminal_colour(2,1); echo(b); terminal_cancel();
|
||||
//~c=memorize(12); string_copy(c,"cyaa world\n"); terminal_colour(3,1); echo(c); terminal_cancel();
|
||||
//~out(memory_store,512);
|
||||
//~terminal_colour(1,1); echo(number_to_string((int)a)); terminal_cancel();
|
||||
//~terminal_colour(2,1); echo(number_to_string((int)b)); terminal_cancel();
|
||||
//~terminal_colour(3,1); echo(number_to_string((int)c)); terminal_cancel(); echo("\n");
|
||||
//~terminal_colour(1,1); echo(a); terminal_cancel();
|
||||
//~terminal_colour(2,1); echo(b); terminal_cancel();
|
||||
//~terminal_colour(3,1); echo(c); terminal_cancel();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,32 +1,83 @@
|
||||
/*
|
||||
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_5_HEADER
|
||||
#define CHAPTER_5_HEADER
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <xcb/xcb_image.h>
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
#include "chapter_2.h"
|
||||
#include "chapter_3.h"
|
||||
#include "chapter_4.h"
|
||||
|
||||
extern int blesses_active;
|
||||
extern int blesses_cursor_x;
|
||||
extern int blesses_cursor_y;
|
||||
extern int blesses_signal;
|
||||
extern int blesses_zoom;
|
||||
/*
|
||||
Okay, we're finally on chapter five, and now we'll write something fun, not serious and boring. Before the Great Flood, when our ancestors were riding dinosaurs, building
|
||||
pyramids, killing African men and mating with Asian women, people didn't have dedicated nor integrated graphical processing units, called GPUs. They only had their terminals,
|
||||
built inside some of their spaceships. And what did they do with them? They played terminal rogue-like games, similar to those that archeologists discovered in ancient Egypt,
|
||||
Syria, Sumeria, Greece and Atlantis. They were reconstructed around 50 years ago by some guy that made the game Rogue. So, all those myths, sagas and legends about Anubis,
|
||||
Gilgamesh, Achilles, Inana, Gaea, they were just playable characters or main characters in those games, with different statistics, skills, attributes and back-story. Now, lets
|
||||
make a simple terminal rogue-like game using what we wrote in previous chapters.
|
||||
|
||||
extern void blesses_configure (void);
|
||||
extern void blesses_synchronize (void);
|
||||
First of all, lets talk briefly about keyword 'typedef' and why I hate to use it.
|
||||
*/
|
||||
|
||||
extern void blesses_render_background_colour (int background);
|
||||
typedef int number_t;
|
||||
typedef char * string_t;
|
||||
typedef void * memory_t;
|
||||
|
||||
extern void blesses_render_character (char character, int foreground, int background, int x, int y);
|
||||
typedef enum action_t {
|
||||
GAME_ACTION_NONE,
|
||||
GAME_ACTION_WAIT, GAME_ACTION_WALK, GAME_ACTION_REST, GAME_ACTION_CAMP,
|
||||
GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR,
|
||||
GAME_ACTION_SUMMON_PUPPET, GAME_ACTION_CALL_NATURE, GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM,
|
||||
GAME_ACTION_COUNT
|
||||
} action_t;
|
||||
|
||||
extern void blesses_render_string (char * string, int foreground, int background, int x, int y);
|
||||
extern void blesses_render_number (int number, int foreground, int background, int x, int y);
|
||||
extern void blesses_render_string_limit (char * string, int limit, int foreground, int background, int x, int y);
|
||||
extern void blesses_render_number_limit (int number, int limit, int foreground, int background, int x, int y);
|
||||
typedef struct game_t {
|
||||
number_t active, screen_width, screen_height;
|
||||
} game_t;
|
||||
|
||||
typedef struct bundle_t {
|
||||
number_t minimum, maximum, current, booster;
|
||||
} bundle_t;
|
||||
|
||||
typedef struct skill_t {
|
||||
string_t name;
|
||||
number_t positive_count;
|
||||
number_t learning_rate;
|
||||
bundle_t * points;
|
||||
action_t * positive;
|
||||
} skill_t;
|
||||
|
||||
typedef struct attribute_t {
|
||||
string_t name;
|
||||
number_t positive_count, negative_count;
|
||||
bundle_t * points;
|
||||
action_t * positive, * negative;
|
||||
} attribute_t;
|
||||
|
||||
typedef struct player_t {
|
||||
string_t name;
|
||||
number_t x, y;
|
||||
bundle_t health, armour, mana, stamina;
|
||||
attribute_t strength, edurance, intelligence, agility;
|
||||
skill_t blades, axes, bows, spears;
|
||||
skill_t puppet_magic, nature_magic, rune_magic, charm_magic;
|
||||
} player_t;
|
||||
|
||||
extern game_t game;
|
||||
extern player_t player;
|
||||
|
||||
extern skill_t * game_skill (string_t name, bundle_t * points, ...);
|
||||
extern attribute_t * game_attribute (string_t name, bundle_t * points, ...);
|
||||
|
||||
extern void game_render_skill (skill_t * skill, number_t x, number_t y);
|
||||
extern void game_render_attribute (attribute_t * attribute, number_t x, number_t y);
|
||||
|
||||
extern void play_game (void);
|
||||
|
||||
#endif
|
||||
|
@ -1,6 +0,0 @@
|
||||
#ifndef CHAPTER_6_SOURCE
|
||||
#define CHAPTER_6_SOURCE
|
||||
|
||||
#include "chapter_6.h"
|
||||
|
||||
#endif
|
@ -1,11 +0,0 @@
|
||||
#ifndef CHAPTER_6_HEADER
|
||||
#define CHAPTER_6_HEADER
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
#include "chapter_2.h"
|
||||
#include "chapter_3.h"
|
||||
#include "chapter_4.h"
|
||||
#include "chapter_5.h"
|
||||
|
||||
#endif
|
@ -1,325 +0,0 @@
|
||||
#ifndef CHAPTER_X_SOURCE
|
||||
#define CHAPTER_X_SOURCE
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "chapter_x.h"
|
||||
|
||||
/*
|
||||
So, what are actually getters and setters, and why you should never use them? Lets explain.
|
||||
*/
|
||||
|
||||
#define MEMORY_LIMIT (1024 * 1024)
|
||||
|
||||
static number_t game_memory_length = 0;
|
||||
static memory_t * game_memory = NULL;
|
||||
|
||||
static procedure_t game_free_memory (memory_t memory) {
|
||||
++game_memory_length;
|
||||
|
||||
game_memory = reallocate (game_memory, game_memory_length * (number_t) sizeof (* game_memory));
|
||||
|
||||
game_memory [game_memory_length - 1] = memory;
|
||||
}
|
||||
|
||||
procedure_t game_clean_up (procedure_t) {
|
||||
for (number_t pointer = 0; pointer < game_memory_length; ++pointer) {
|
||||
game_memory [pointer] = deallocate (game_memory [pointer]);
|
||||
}
|
||||
|
||||
game_memory = deallocate (game_memory);
|
||||
}
|
||||
|
||||
bundle_t * game_bundle (number_t minimum, number_t maximum, number_t current) {
|
||||
bundle_t * bundle = allocate ((number_t) sizeof (* bundle));
|
||||
|
||||
bundle->minimum = minimum;
|
||||
bundle->maximum = maximum;
|
||||
bundle->current = current;
|
||||
|
||||
game_free_memory (bundle);
|
||||
|
||||
return (bundle);
|
||||
}
|
||||
|
||||
symbol_t * game_symbol (number_t character, number_t colour, number_t effect) {
|
||||
symbol_t * symbol = allocate ((number_t) sizeof (* symbol));
|
||||
|
||||
symbol->character = character;
|
||||
symbol->colour = colour;
|
||||
symbol->effect = effect;
|
||||
|
||||
game_free_memory (symbol);
|
||||
|
||||
return (symbol);
|
||||
}
|
||||
|
||||
sprite_t * game_sprite (number_t width, number_t height, memory_t data) {
|
||||
sprite_t * sprite = allocate ((number_t) sizeof (* sprite));
|
||||
|
||||
sprite->width = width;
|
||||
sprite->height = height;
|
||||
sprite->data = data;
|
||||
|
||||
game_free_memory (sprite);
|
||||
|
||||
return (sprite);
|
||||
}
|
||||
|
||||
generator_t * game_generator (generate_t function) {
|
||||
generator_t * generator = allocate ((number_t) sizeof (* generator));
|
||||
|
||||
generator->generate = function;
|
||||
|
||||
game_free_memory (generator);
|
||||
|
||||
return (generator);
|
||||
}
|
||||
|
||||
attribute_t * game_attribute (string_t name, bundle_t * points, ...) {
|
||||
va_list list;
|
||||
|
||||
attribute_t * attribute = allocate ((number_t) sizeof (* attribute));
|
||||
|
||||
string_copy ((attribute->name = allocate (string_length (name) + 1)), name);
|
||||
|
||||
memory_copy ((attribute->points = allocate ((number_t) sizeof (* attribute->points))), points, (number_t) sizeof (* points));
|
||||
|
||||
va_start (list, points);
|
||||
|
||||
for (;;) {
|
||||
number_t action = (number_t) va_arg (list, int);
|
||||
|
||||
if (action > 0) {
|
||||
attribute->positive_count += 1;
|
||||
attribute->positive = reallocate (attribute->positive, attribute->positive_count * (number_t) sizeof (action));
|
||||
attribute->positive [attribute->positive_count - 1] = (action_t) action;
|
||||
} else if (action < 0) {
|
||||
attribute->negative_count += 1;
|
||||
attribute->negative = reallocate (attribute->negative, attribute->negative_count * (number_t) sizeof (action));
|
||||
attribute->negative [attribute->negative_count - 1] = (action_t) -action;
|
||||
} else break;
|
||||
}
|
||||
|
||||
va_end (list);
|
||||
|
||||
game_free_memory (attribute->name);
|
||||
game_free_memory (attribute->points);
|
||||
game_free_memory (attribute->positive);
|
||||
game_free_memory (attribute->negative);
|
||||
game_free_memory (attribute);
|
||||
|
||||
return (attribute);
|
||||
}
|
||||
|
||||
skill_t * game_skill (string_t name, bundle_t * points, ...) {
|
||||
va_list list;
|
||||
|
||||
skill_t * skill = allocate ((number_t) sizeof (* skill));
|
||||
|
||||
string_copy ((skill->name = allocate (string_length (name) + 1)), name);
|
||||
|
||||
memory_copy ((skill->points = allocate ((number_t) sizeof (* skill->points))), points, (number_t) sizeof (* points));
|
||||
|
||||
va_start (list, points);
|
||||
|
||||
for (;;) {
|
||||
action_t action = (action_t) va_arg (list, int);
|
||||
|
||||
if (action > 0) {
|
||||
skill->positive_count += 1;
|
||||
skill->positive = reallocate (skill->positive, skill->positive_count * (number_t) sizeof (action));
|
||||
skill->positive [skill->positive_count - 1] = action;
|
||||
} else break;
|
||||
}
|
||||
|
||||
va_end (list);
|
||||
|
||||
game_free_memory (skill->name);
|
||||
game_free_memory (skill->points);
|
||||
game_free_memory (skill->positive);
|
||||
game_free_memory (skill);
|
||||
|
||||
return (skill);
|
||||
}
|
||||
|
||||
bot_t * game_bot (string_t name, symbol_t * symbol, sprite_t * sprite, bundle_t * health, bundle_t * mana, bundle_t * stamina) {
|
||||
bot_t * bot = allocate ((number_t) sizeof (* bot));
|
||||
|
||||
string_copy ((bot->name = allocate (string_length (name) + 1)), name);
|
||||
|
||||
bot->x = 0;
|
||||
bot->y = 0;
|
||||
|
||||
memory_copy ((bot->symbol = allocate ((number_t) sizeof (* bot->symbol))), symbol, (number_t) sizeof (* symbol));
|
||||
memory_copy ((bot->sprite = allocate ((number_t) sizeof (* bot->sprite))), sprite, (number_t) sizeof (* sprite));
|
||||
memory_copy ((bot->health = allocate ((number_t) sizeof (* bot->health))), health, (number_t) sizeof (* health));
|
||||
memory_copy ((bot->mana = allocate ((number_t) sizeof (* bot->mana))), mana, (number_t) sizeof (* mana));
|
||||
memory_copy ((bot->stamina = allocate ((number_t) sizeof (* bot->stamina))), stamina, (number_t) sizeof (* stamina));
|
||||
|
||||
game_free_memory (bot->name);
|
||||
game_free_memory (bot->symbol);
|
||||
game_free_memory (bot->sprite);
|
||||
game_free_memory (bot->health);
|
||||
game_free_memory (bot->mana);
|
||||
game_free_memory (bot->stamina);
|
||||
game_free_memory (bot);
|
||||
|
||||
return (bot);
|
||||
}
|
||||
|
||||
player_t * game_player (string_t name, symbol_t * symbol, sprite_t * sprite, bundle_t * health, bundle_t * mana, bundle_t * stamina, ...) {
|
||||
va_list list;
|
||||
|
||||
player_t * player = allocate ((number_t) sizeof (* player));
|
||||
|
||||
string_copy ((player->name = allocate (string_length (name) + 1)), name);
|
||||
|
||||
player->x = 3;
|
||||
player->y = 3;
|
||||
|
||||
memory_copy ((player->symbol = allocate ((number_t) sizeof (* player->symbol))), symbol, (number_t) sizeof (* symbol));
|
||||
memory_copy ((player->sprite = allocate ((number_t) sizeof (* player->sprite))), sprite, (number_t) sizeof (* sprite));
|
||||
memory_copy ((player->health = allocate ((number_t) sizeof (* player->health))), health, (number_t) sizeof (* health));
|
||||
memory_copy ((player->mana = allocate ((number_t) sizeof (* player->mana))), mana, (number_t) sizeof (* mana));
|
||||
memory_copy ((player->stamina = allocate ((number_t) sizeof (* player->stamina))), stamina, (number_t) sizeof (* stamina));
|
||||
|
||||
player->attribute_count = 0;
|
||||
player->skill_count = 0;
|
||||
|
||||
va_start (list, stamina);
|
||||
|
||||
for (;;) {
|
||||
attribute_t * attribute = (attribute_t *) va_arg (list, void *);
|
||||
|
||||
if (attribute != NULL) {
|
||||
player->attribute_count += 1;
|
||||
player->attributes = reallocate (player->attributes, player->attribute_count * (number_t) sizeof (* player->attributes));
|
||||
player->attributes [player->attribute_count - 1] = attribute;
|
||||
} else break;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
skill_t * skill = (skill_t *) va_arg (list, void *);
|
||||
|
||||
if (skill != NULL) {
|
||||
player->skill_count += 1;
|
||||
player->skills = reallocate (player->skills, player->skill_count * (number_t) sizeof (* player->skills));
|
||||
player->skills [player->skill_count - 1] = skill;
|
||||
} else break;
|
||||
}
|
||||
|
||||
va_end (list);
|
||||
|
||||
game_free_memory (player->name);
|
||||
game_free_memory (player->symbol);
|
||||
game_free_memory (player->sprite);
|
||||
game_free_memory (player->health);
|
||||
game_free_memory (player->mana);
|
||||
game_free_memory (player->stamina);
|
||||
game_free_memory (player->attributes);
|
||||
game_free_memory (player->skills);
|
||||
game_free_memory (player);
|
||||
|
||||
return (player);
|
||||
}
|
||||
|
||||
block_t * game_block (string_t name, symbol_t * symbol, sprite_t * sprite, number_t collision, number_t override) {
|
||||
block_t * block = allocate ((number_t) sizeof (* block));
|
||||
|
||||
string_copy ((block->name = allocate (string_length (name) + 1)), name);
|
||||
|
||||
memory_copy ((block->symbol = allocate ((number_t) sizeof (* block->symbol))), symbol, (number_t) sizeof (* symbol));
|
||||
memory_copy ((block->sprite = allocate ((number_t) sizeof (* block->sprite))), sprite, (number_t) sizeof (* sprite));
|
||||
|
||||
block->collision = collision;
|
||||
block->override = override;
|
||||
|
||||
game_free_memory (block->name);
|
||||
game_free_memory (block->symbol);
|
||||
game_free_memory (block->sprite);
|
||||
game_free_memory (block);
|
||||
|
||||
return (block);
|
||||
}
|
||||
|
||||
world_t * game_world (number_t width, number_t height, ...) {
|
||||
va_list list;
|
||||
|
||||
world_t * world = allocate ((number_t) sizeof (* world));
|
||||
|
||||
world->width = width;
|
||||
world->height = height;
|
||||
|
||||
world->block = allocate (width * height * (number_t) sizeof (* world->block));
|
||||
|
||||
va_start (list, height);
|
||||
|
||||
for (;;) {
|
||||
generator_t * generator = (generator_t *) va_arg (list, void *);
|
||||
|
||||
if (generator != NULL) {
|
||||
memory_t data = (memory_t) va_arg (list, void *);
|
||||
number_t w = (number_t) va_arg (list, int);
|
||||
number_t h = (number_t) va_arg (list, int);
|
||||
number_t x = (number_t) va_arg (list, int);
|
||||
number_t y = (number_t) va_arg (list, int);
|
||||
|
||||
generator->generate (world, data, w, h, x, y);
|
||||
} else break;
|
||||
}
|
||||
|
||||
va_end (list);
|
||||
|
||||
game_free_memory (world->bots);
|
||||
game_free_memory (world->block);
|
||||
game_free_memory (world);
|
||||
|
||||
return (world);
|
||||
}
|
||||
|
||||
procedure_t game_render_attribute (attribute_t * attribute, number_t x, number_t y) {
|
||||
curses_render_string (attribute->name, COLOUR_CYAN, EFFECT_NORMAL, x + 0, y);
|
||||
curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
|
||||
curses_render_string (format_to_string (attribute->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x + 20, y);
|
||||
curses_render_string (format_to_string (attribute->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x + 24, y);
|
||||
curses_render_string (format_to_string (attribute->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x + 28, y);
|
||||
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x + 32, y);
|
||||
}
|
||||
|
||||
procedure_t game_render_skill (skill_t * skill, number_t x, number_t y) {
|
||||
curses_render_string (skill->name, COLOUR_CYAN, EFFECT_NORMAL, x + 0, y);
|
||||
curses_render_string ("[ ", COLOUR_GREY, EFFECT_BOLD, x + 18, y);
|
||||
curses_render_string (format_to_string (skill->points->minimum, 0, 10, 4, ' '), COLOUR_RED, EFFECT_NORMAL, x + 20, y);
|
||||
curses_render_string (format_to_string (skill->points->current, 0, 10, 4, ' '), COLOUR_WHITE, EFFECT_NORMAL, x + 24, y);
|
||||
curses_render_string (format_to_string (skill->points->maximum, 0, 10, 4, ' '), COLOUR_GREEN, EFFECT_NORMAL, x + 28, y);
|
||||
curses_render_string (" ]", COLOUR_GREY, EFFECT_BOLD, x + 32, y);
|
||||
|
||||
}
|
||||
|
||||
procedure_t game_render_bot (bot_t * bot) {
|
||||
curses_render_character ((char) bot->symbol->character, bot->symbol->colour, bot->symbol->effect, bot->x, bot->y);
|
||||
}
|
||||
|
||||
procedure_t game_render_player (player_t * player) {
|
||||
curses_render_character ((char) player->symbol->character, player->symbol->colour, player->symbol->effect, player->x, player->y);
|
||||
}
|
||||
|
||||
procedure_t game_render_block (block_t * block, number_t x, number_t y) {
|
||||
curses_render_character ((char) block->symbol->character, block->symbol->colour, block->symbol->effect, x, y);
|
||||
}
|
||||
|
||||
procedure_t game_render_world (world_t * world, number_t x, number_t y, number_t width, number_t height) {
|
||||
for (number_t j = 0; (j < height) && (j < world->height); ++j) {
|
||||
for (number_t i = 0; (i < width) && (i < world->width); ++i) {
|
||||
game_render_block (world->block [j * world->width + i], i + x, j + y);
|
||||
}
|
||||
}
|
||||
|
||||
for (number_t index = 0; index < world->bot_count; ++index) {
|
||||
game_render_bot (& world->bots [index]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,146 +0,0 @@
|
||||
#ifndef CHAPTER_X_HEADER
|
||||
#define CHAPTER_X_HEADER
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
#include "chapter_2.h"
|
||||
#include "chapter_3.h"
|
||||
|
||||
/*
|
||||
Okay, we're finally on chapter five, and now we'll write something fun, not serious and boring. Before the Great Flood, when our ancestors were riding dinosaurs, building
|
||||
pyramids, killing African men and mating with Asian women, people didn't have dedicated nor integrated graphical processing units, called GPUs. They only had their terminals,
|
||||
built inside some of their spaceships. And what did they do with them? They played terminal rogue-like games, similar to those that archeologists discovered in ancient Egypt,
|
||||
Syria, Sumeria, Greece and Atlantis. They were reconstructed around 50 years ago by some guy that made the game Rogue. So, all those myths, sagas and legends about Anubis,
|
||||
Gilgamesh, Achilles, Inana, Gaea, they were just playable characters or main characters in those games, with different statistics, skills, attributes and back-story. Now, lets
|
||||
make a simple terminal rogue-like game using what we wrote in previous chapters.
|
||||
|
||||
First of all, lets talk briefly about keyword 'typedef' and why I hate to use it.
|
||||
*/
|
||||
|
||||
typedef int number_t;
|
||||
typedef void procedure_t;
|
||||
typedef char * string_t;
|
||||
typedef void * memory_t;
|
||||
|
||||
typedef enum action_t {
|
||||
GAME_ACTION_NONE,
|
||||
GAME_ACTION_WAIT, GAME_ACTION_WALK, GAME_ACTION_REST, GAME_ACTION_CAMP,
|
||||
GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR,
|
||||
GAME_ACTION_SUMMON_PUPPET, GAME_ACTION_CALL_NATURE, GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM,
|
||||
GAME_ACTION_COUNT
|
||||
} action_t;
|
||||
|
||||
typedef struct bundle_t {
|
||||
number_t minimum;
|
||||
number_t maximum;
|
||||
number_t current;
|
||||
} bundle_t;
|
||||
|
||||
typedef struct symbol_t {
|
||||
number_t character;
|
||||
number_t colour;
|
||||
number_t effect;
|
||||
} symbol_t;
|
||||
|
||||
typedef struct sprite_t {
|
||||
number_t width;
|
||||
number_t height;
|
||||
memory_t data;
|
||||
} sprite_t;
|
||||
|
||||
typedef struct attribute_t {
|
||||
string_t name;
|
||||
number_t positive_count;
|
||||
number_t negative_count;
|
||||
bundle_t * points;
|
||||
action_t * positive;
|
||||
action_t * negative;
|
||||
} attribute_t;
|
||||
|
||||
typedef struct skill_t {
|
||||
string_t name;
|
||||
number_t positive_count;
|
||||
number_t learning_rate;
|
||||
bundle_t * points;
|
||||
action_t * positive;
|
||||
} skill_t;
|
||||
|
||||
typedef struct bot_t {
|
||||
string_t name;
|
||||
number_t x;
|
||||
number_t y;
|
||||
symbol_t * symbol;
|
||||
sprite_t * sprite;
|
||||
bundle_t * health;
|
||||
bundle_t * mana;
|
||||
bundle_t * stamina;
|
||||
} bot_t;
|
||||
|
||||
typedef struct player_t {
|
||||
string_t name;
|
||||
number_t x;
|
||||
number_t y;
|
||||
symbol_t * symbol;
|
||||
sprite_t * sprite;
|
||||
bundle_t * health;
|
||||
bundle_t * mana;
|
||||
bundle_t * stamina;
|
||||
number_t attribute_count;
|
||||
number_t skill_count;
|
||||
attribute_t * * attributes;
|
||||
skill_t * * skills;
|
||||
} player_t;
|
||||
|
||||
typedef struct block_t {
|
||||
string_t name;
|
||||
number_t collision;
|
||||
number_t override;
|
||||
symbol_t * symbol;
|
||||
sprite_t * sprite;
|
||||
} block_t;
|
||||
|
||||
typedef struct world_t {
|
||||
number_t width;
|
||||
number_t height;
|
||||
number_t item_count;
|
||||
number_t bot_count;
|
||||
block_t * * block;
|
||||
bot_t * bots;
|
||||
player_t * player;
|
||||
} world_t;
|
||||
|
||||
typedef procedure_t (* generate_t) (world_t *, memory_t, number_t, number_t, number_t, number_t);
|
||||
|
||||
typedef struct generator_t {
|
||||
generate_t generate;
|
||||
} generator_t;
|
||||
|
||||
extern procedure_t game_clean_up (procedure_t);
|
||||
|
||||
extern bundle_t * game_bundle (number_t minimum, number_t maximum, number_t current);
|
||||
extern symbol_t * game_symbol (number_t character, number_t colour, number_t effect);
|
||||
extern sprite_t * game_sprite (number_t width, number_t height, memory_t data);
|
||||
|
||||
extern generator_t * game_generator (generate_t generator);
|
||||
|
||||
extern attribute_t * game_attribute (string_t name, bundle_t * points, ...);
|
||||
extern skill_t * game_skill (string_t name, bundle_t * points, ...);
|
||||
|
||||
extern bot_t * game_bot (string_t name, symbol_t * symbol, sprite_t * sprite, bundle_t * health, bundle_t * mana, bundle_t * stamina);
|
||||
extern player_t * game_player (string_t name, symbol_t * symbol, sprite_t * sprite, bundle_t * health, bundle_t * mana, bundle_t * stamina, ...);
|
||||
|
||||
extern block_t * game_block (string_t name, symbol_t * symbol, sprite_t * sprite, number_t collision, number_t override);
|
||||
|
||||
extern world_t * game_world (number_t width, number_t height, ...);
|
||||
|
||||
extern procedure_t game_render_attribute (attribute_t * attribute, number_t x, number_t y);
|
||||
extern procedure_t game_render_skill (skill_t * skill, number_t x, number_t y);
|
||||
|
||||
extern procedure_t game_render_bot (bot_t * bot);
|
||||
extern procedure_t game_render_player (player_t * player);
|
||||
|
||||
extern procedure_t game_render_block (block_t * block, number_t x, number_t y);
|
||||
|
||||
extern procedure_t game_render_world (world_t * world, number_t x, number_t y, number_t width, number_t height);
|
||||
|
||||
#endif
|
@ -1,146 +0,0 @@
|
||||
#ifndef CHAPTER_Y_SOURCE
|
||||
#define CHAPTER_Y_SOURCE
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "chapter_y.h"
|
||||
|
||||
#define UNUSED(variable) (void) variable
|
||||
|
||||
static procedure_t generate_full_fill_function (world_t * world, memory_t data, number_t width, number_t height, number_t x, number_t y) {
|
||||
UNUSED (width); UNUSED (height); UNUSED (x); UNUSED (y);
|
||||
|
||||
for (number_t j = 0; j < world->height; ++j) {
|
||||
for (number_t i = 0; i < world->width; ++i) {
|
||||
world->block [j * world->width + i] = (block_t *) data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static procedure_t generate_rectangle_fill_function (world_t * world, memory_t data, number_t width, number_t height, number_t x, number_t y) {
|
||||
for (number_t j = 0; j < height; ++j) {
|
||||
for (number_t i = 0; i < width; ++i) {
|
||||
world->block [(j + y) * world->width + (i + x)] = (block_t *) data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static procedure_t generate_rectangle_line_function (world_t * world, memory_t data, number_t width, number_t height, number_t x, number_t y) {
|
||||
for (number_t offset = 0; offset < width; ++offset) {
|
||||
world->block [world->width * y + offset + x] = (block_t *) data;
|
||||
world->block [world->width * (height - 1 + y) + offset + x] = (block_t *) data;
|
||||
}
|
||||
|
||||
for (number_t offset = 0; offset < height; ++offset) {
|
||||
world->block [world->width * (y + offset) + x] = (block_t *) data;
|
||||
world->block [world->width * (y + offset) + width - 1 + x] = (block_t *) data;
|
||||
}
|
||||
}
|
||||
|
||||
static procedure_t generate_create_bots_function (world_t * world, memory_t data, number_t width, number_t height, number_t x, number_t y) {
|
||||
UNUSED (height); UNUSED (x); UNUSED (y);
|
||||
|
||||
bot_t * bot = (bot_t *) data;
|
||||
number_t count = width;
|
||||
|
||||
for (number_t index = 0; index < count; ++index) {
|
||||
world->bot_count += 1;
|
||||
world->bots = reallocate (world->bots, world->bot_count * (number_t) sizeof (* world->bots));
|
||||
memory_copy (& world->bots [world->bot_count - 1], game_bot (bot->name, bot->symbol, NULL, bot->health, bot->mana, bot->stamina), (number_t) sizeof (* world->bots));
|
||||
world->bots [world->bot_count - 1].x = randomize (1, 40);
|
||||
world->bots [world->bot_count - 1].y = randomize (1, 40);
|
||||
}
|
||||
}
|
||||
|
||||
procedure_t play_game (procedure_t) {
|
||||
generator_t * full_fill = game_generator (generate_full_fill_function);
|
||||
generator_t * rectangle_fill = game_generator (generate_rectangle_fill_function);
|
||||
generator_t * rectangle_line = game_generator (generate_rectangle_line_function);
|
||||
generator_t * create_bots = game_generator (generate_create_bots_function);
|
||||
|
||||
bot_t * goblin = game_bot ("Goblin", game_symbol ('g', COLOUR_RED, EFFECT_NORMAL), NULL, game_bundle (0, 11, 11), game_bundle (0, 3, 3), game_bundle (0, 23, 23));
|
||||
bot_t * hob_goblin = game_bot ("Hob Goblin", game_symbol ('g', COLOUR_RED, EFFECT_BOLD), NULL, game_bundle (0, 17, 17), game_bundle (0, 7, 7), game_bundle (0, 31, 31));
|
||||
bot_t * orc = game_bot ("Orc", game_symbol ('G', COLOUR_RED, EFFECT_NORMAL), NULL, game_bundle (0, 23, 23), game_bundle (0, 5, 5), game_bundle (0, 47, 47));
|
||||
bot_t * ogre = game_bot ("Ogre", game_symbol ('G', COLOUR_RED, EFFECT_BOLD), NULL, game_bundle (0, 37, 37), game_bundle (0, 2, 2), game_bundle (0, 83, 83));
|
||||
|
||||
player_t * player = game_player ("Riri", game_symbol ('@', COLOUR_CYAN, EFFECT_BOLD), NULL, game_bundle (0, 29, 29), game_bundle (0, 3, 3), game_bundle (0, 37, 37),
|
||||
game_attribute ("Strength", game_bundle (1, 12, 0), GAME_ACTION_SWING_BLADE, GAME_ACTION_SWING_AXE, -GAME_ACTION_CAMP, 0),
|
||||
game_attribute ("Edurance", game_bundle (1, 12, 0), GAME_ACTION_WALK, GAME_ACTION_CAMP, -GAME_ACTION_REST, 0),
|
||||
game_attribute ("Wisdom", game_bundle (1, 12, 0), GAME_ACTION_CITE_RUNE, GAME_ACTION_CAST_CHARM, -GAME_ACTION_WALK, 0),
|
||||
game_attribute ("Agility", game_bundle (1, 12, 0), GAME_ACTION_SHOOT_ARROW, GAME_ACTION_THROW_SPEAR, -GAME_ACTION_WAIT, 0),
|
||||
NULL,
|
||||
game_skill ("Blades", game_bundle (10, 120, 0), GAME_ACTION_SWING_BLADE, 0),
|
||||
game_skill ("Axes", game_bundle (10, 120, 0), GAME_ACTION_SWING_AXE, 0),
|
||||
game_skill ("Bows", game_bundle (10, 120, 0), GAME_ACTION_SHOOT_ARROW, 0),
|
||||
game_skill ("Spears", game_bundle (10, 120, 0), GAME_ACTION_THROW_SPEAR, 0),
|
||||
game_skill ("Puppet Magic", game_bundle (10, 120, 0), GAME_ACTION_SUMMON_PUPPET, 0),
|
||||
game_skill ("Nature Magic", game_bundle (10, 120, 0), GAME_ACTION_CALL_NATURE, 0),
|
||||
game_skill ("Rune Magic", game_bundle (10, 120, 0), GAME_ACTION_CITE_RUNE, 0),
|
||||
game_skill ("Charm Magic", game_bundle (10, 120, 0), GAME_ACTION_CAST_CHARM, 0),
|
||||
NULL);
|
||||
|
||||
block_t * grass = game_block ("Grass", game_symbol (',', COLOUR_GREEN, EFFECT_BOLD), NULL, FALSE, FALSE);
|
||||
block_t * stone_floor = game_block ("Stone Floor", game_symbol ('.', COLOUR_GREY, EFFECT_BOLD), NULL, FALSE, FALSE);
|
||||
block_t * stone_wall = game_block ("Stone Wall", game_symbol ('#', COLOUR_GREY, EFFECT_BOLD), NULL, TRUE, FALSE);
|
||||
|
||||
world_t * world = game_world (300, 100, full_fill, grass, 0, 0, 0, 0,
|
||||
rectangle_fill, stone_floor, 5, 9, 2, 4,
|
||||
rectangle_line, stone_wall, 5, 9, 2, 4,
|
||||
create_bots, goblin, 3, 0, 0, 0,
|
||||
create_bots, hob_goblin, 3, 0, 0, 0,
|
||||
create_bots, orc, 3, 0, 0, 0,
|
||||
create_bots, ogre, 3, 0, 0, 0,
|
||||
NULL);
|
||||
|
||||
player->attributes [0]->points->current = 7;
|
||||
player->attributes [1]->points->current = 3;
|
||||
player->attributes [2]->points->current = 5;
|
||||
player->attributes [3]->points->current = 3;
|
||||
|
||||
player->skills [0]->points->current = randomize (1, 10);
|
||||
player->skills [1]->points->current = randomize (1, 10);
|
||||
player->skills [2]->points->current = randomize (1, 10);
|
||||
player->skills [3]->points->current = randomize (1, 10);
|
||||
player->skills [4]->points->current = randomize (1, 10);
|
||||
player->skills [5]->points->current = randomize (1, 10);
|
||||
player->skills [6]->points->current = randomize (1, 10);
|
||||
player->skills [7]->points->current = randomize (1, 10);
|
||||
|
||||
curses_configure ();
|
||||
|
||||
terminal_show_cursor (FALSE);
|
||||
|
||||
while (curses_active) {
|
||||
number_t game_screen_offset = curses_screen_width - 40;
|
||||
|
||||
curses_render_background (' ', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
|
||||
game_render_world (world, 0, 0, game_screen_offset, curses_screen_height);
|
||||
|
||||
game_render_player (player);
|
||||
|
||||
for (number_t attribute = 0; attribute < player->attribute_count; ++attribute) {
|
||||
game_render_attribute (player->attributes [attribute], game_screen_offset, 1 + attribute);
|
||||
}
|
||||
|
||||
for (number_t skill = 0; skill < player->skill_count; ++skill) {
|
||||
game_render_skill (player->skills [skill], game_screen_offset, 6 + skill);
|
||||
}
|
||||
|
||||
switch (curses_character) {
|
||||
case SIGNAL_ARROW_UP: player->y -= 1; limit (& player->y, 0, world->height - 1); break;
|
||||
case SIGNAL_ARROW_DOWN: player->y += 1; limit (& player->y, 0, world->height - 1); break;
|
||||
case SIGNAL_ARROW_LEFT: player->x -= 1; limit (& player->x, 0, world->width - 1); break;
|
||||
case SIGNAL_ARROW_RIGHT: player->x += 1; limit (& player->x, 0, world->width - 1); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
curses_synchronize ();
|
||||
}
|
||||
|
||||
terminal_show_cursor (TRUE);
|
||||
|
||||
game_clean_up ();
|
||||
}
|
||||
|
||||
#endif
|
@ -1,12 +0,0 @@
|
||||
#ifndef CHAPTER_Y_HEADER
|
||||
#define CHAPTER_Y_HEADER
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_1.h"
|
||||
#include "chapter_2.h"
|
||||
#include "chapter_3.h"
|
||||
#include "chapter_5.h"
|
||||
|
||||
extern procedure_t play_game (procedure_t);
|
||||
|
||||
#endif
|
19
compile.sh
19
compile.sh
@ -2,7 +2,7 @@
|
||||
|
||||
set -xe
|
||||
|
||||
clang -DSTATIC_SOURCE -g -Weverything -O0 -o xhartae xhartae.c -lxcb -lxcb-image
|
||||
clang -DSTATIC_SOURCE -g -Weverything -O0 -o xhartae xhartae.c
|
||||
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_0.o chapter/chapter_0.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_1.o chapter/chapter_1.c
|
||||
@ -10,19 +10,10 @@ gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_2.o chapter/chapter_2.
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_3.o chapter/chapter_3.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_4.o chapter/chapter_4.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_5.o chapter/chapter_5.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o chapter/chapter_6.o chapter/chapter_6.c
|
||||
|
||||
gcc -g -Wall -Wextra -Wpedantic -O0 -c -o xhartae.o xhartae.c
|
||||
|
||||
gcc -o xhartae xhartae.o chapter/chapter_0.o chapter/chapter_1.o chapter/chapter_2.o chapter/chapter_3.o chapter/chapter_4.o chapter/chapter_5.o chapter/chapter_6.o -lxcb -lxcb-image
|
||||
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o program/program_0 program/program_0.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o program/program_1 program/program_1.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o program/program_2 program/program_2.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o program/program_4 program/program_4.c
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o program/program_z program/program_z.c
|
||||
|
||||
gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o example/xcb_window example/xcb_window.c -lxcb
|
||||
gcc -o xhartae xhartae.o chapter/chapter_0.o chapter/chapter_1.o chapter/chapter_2.o chapter/chapter_3.o chapter/chapter_4.o chapter/chapter_5.o
|
||||
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_0.h
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_0.c
|
||||
@ -36,12 +27,8 @@ gcc -g -Wall -Wextra -Wpedantic -Werror -Ofast -o example/xcb_window example/xcb
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_4.c
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_5.h
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_5.c
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_6.h
|
||||
#~splint -weak -warnposix -retvalother -syntax -type chapter/chapter_6.c
|
||||
#~splint -weak -warnposix -retvalother -syntax -type xhartae.c
|
||||
|
||||
#~valgrind --show-leak-kinds=all --leak-check=full --log-file=log.txt ./xhartae
|
||||
|
||||
#~xighlight -V < log.txt
|
||||
#~valgrind --show-leak-kinds=all --leak-check=full ./xhartae
|
||||
|
||||
exit
|
||||
|
@ -1,98 +0,0 @@
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2023 - Ognjen 'xolatile' Milan Robovic
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Xabina is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either
|
||||
-- version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
||||
-- implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
with core;
|
||||
|
||||
package magic is
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
type list is (
|
||||
ignite, illuminate, bladecharm, battlecry
|
||||
);
|
||||
|
||||
type mark is mod 72;
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
type constant_type is new core.constant_type with
|
||||
record
|
||||
self : boolean := false;
|
||||
health : integer := 0;
|
||||
armour : integer := 0;
|
||||
mana : integer := 0;
|
||||
stamina : integer := 0;
|
||||
distance : integer := 0;
|
||||
tribute : natural := 0;
|
||||
end record;
|
||||
|
||||
type variable_type is new core.variable_type with
|
||||
record
|
||||
enchantment : natural := 0;
|
||||
end record;
|
||||
|
||||
type constant_list is array (list) of constant_type;
|
||||
type variable_list is array (mark) of variable_type;
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
constant_data : constant constant_list := (
|
||||
(core.magic, "Ignite ", '*', core.colour.yellow, core.effect.italic, false, -3, 0, 0, -1, 7, 1),
|
||||
(core.magic, "Illuminate ", '*', core.colour.yellow, core.effect.italic, false, 0, 0, 0, -1, 13, 1),
|
||||
(core.magic, "Bladecharm ", '*', core.colour.red, core.effect.italic, true, 0, -3, 0, -1, 7, 3),
|
||||
(core.magic, "Battlecry ", '*', core.colour.red, core.effect.italic, true, -1, -1, 0, -1, 7, 2)
|
||||
);
|
||||
|
||||
variable_data : variable_list;
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
procedure generate;
|
||||
procedure render;
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
end magic;
|
||||
|
||||
with core, map;
|
||||
|
||||
package body magic is
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
procedure generate is
|
||||
y : natural := 0;
|
||||
x : natural := 0;
|
||||
identifier : natural := 0;
|
||||
begin
|
||||
for this in map.mark
|
||||
loop
|
||||
y := core.randomize (0, natural (map.height'last));
|
||||
x := core.randomize (0, natural (map.width'last));
|
||||
identifier := core.randomize (0, natural (list'size));
|
||||
map.variable_data (this) := (integer (y), integer (x), core.magic, identifier);
|
||||
end loop;
|
||||
end generate;
|
||||
|
||||
procedure render is
|
||||
symbol : character := ' ';
|
||||
colour : character := core.colour.white;
|
||||
effect : character := core.effect.normal;
|
||||
begin
|
||||
for this in map.mark
|
||||
loop
|
||||
symbol := constant_data (list'val (map.variable_data (this).identifier)).symbol;
|
||||
colour := constant_data (list'val (map.variable_data (this).identifier)).colour;
|
||||
effect := constant_data (list'val (map.variable_data (this).identifier)).effect;
|
||||
core.render_character (symbol, colour, effect, core.screen_height (map.variable_data (this).y), core.screen_width (map.variable_data (this).x));
|
||||
end loop;
|
||||
end render;
|
||||
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
end magic;
|
@ -1,66 +0,0 @@
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int main (void) {
|
||||
int active = 1;
|
||||
|
||||
unsigned short int window_width = 640;
|
||||
unsigned short int window_height = 480;
|
||||
|
||||
xcb_window_t window = 0;
|
||||
xcb_connection_t * connection = NULL;
|
||||
xcb_screen_t * screen = NULL;
|
||||
xcb_generic_event_t * event = NULL;
|
||||
|
||||
unsigned int window_flags [2] = {
|
||||
0,
|
||||
XCB_EVENT_MASK_NO_EVENT |
|
||||
XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_RESIZE_REDIRECT |
|
||||
XCB_EVENT_MASK_KEY_RELEASE |
|
||||
XCB_EVENT_MASK_KEY_PRESS |
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS
|
||||
};
|
||||
|
||||
connection = xcb_connect (NULL, NULL);
|
||||
|
||||
screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data;
|
||||
|
||||
window = xcb_generate_id (connection);
|
||||
|
||||
window_flags [0] = screen->black_pixel;
|
||||
|
||||
xcb_create_window (connection, screen->root_depth, window, screen->root, 0, 0, window_width, window_height, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
|
||||
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, window_flags);
|
||||
|
||||
xcb_map_window (connection, window);
|
||||
|
||||
xcb_flush (connection);
|
||||
|
||||
while (active) {
|
||||
event = xcb_wait_for_event (connection);
|
||||
|
||||
switch (event->response_type & 127) {
|
||||
case XCB_EXPOSE:
|
||||
xcb_flush (connection);
|
||||
break;
|
||||
case XCB_KEY_PRESS:
|
||||
if ((int) ((xcb_key_press_event_t *) event)->detail == 24) {
|
||||
active = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
free (event);
|
||||
}
|
||||
|
||||
xcb_destroy_window (connection, window);
|
||||
|
||||
xcb_disconnect (connection);
|
||||
|
||||
return (0);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
#include "../chapter/chapter_0.c"
|
||||
|
||||
static void cool_echo (char * text, int colour, int effect);
|
||||
|
||||
static void heyo (void) { cool_echo ("Heyo world!\n", COLOUR_GREEN, EFFECT_BOLD); }
|
||||
static void cyaa (void) { cool_echo ("Cyaa world!\n", COLOUR_RED, EFFECT_BOLD); }
|
||||
|
||||
int main (void) {
|
||||
heyo ();
|
||||
|
||||
cyaa ();
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void cool_echo (char * text, int colour, int effect) {
|
||||
terminal_colour (colour, effect);
|
||||
echo (text);
|
||||
terminal_cancel ();
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
#include "../chapter/chapter_0.c"
|
||||
#include "../chapter/chapter_1.c"
|
||||
|
||||
static void echo_separator (void) {
|
||||
terminal_colour (COLOUR_GREY, EFFECT_BOLD);
|
||||
|
||||
echo (" | ");
|
||||
|
||||
terminal_cancel ();
|
||||
}
|
||||
|
||||
static void echo_base (int character, int base, int colour, int effect) {
|
||||
echo_separator ();
|
||||
|
||||
terminal_colour (colour, effect);
|
||||
|
||||
if (base == 2) {
|
||||
echo (format_to_string (character, 0, base, 7, '0'));
|
||||
} else if (base == 8) {
|
||||
echo (format_to_string (character, 0, base, 3, '0'));
|
||||
} else if (base == 16) {
|
||||
echo (format_to_string (character, 0, base, 2, '0'));
|
||||
} else {
|
||||
echo (format_to_string (character, 0, base, 3, ' '));
|
||||
}
|
||||
|
||||
terminal_cancel ();
|
||||
}
|
||||
|
||||
static void echo_code (int character, int colour, int effect) {
|
||||
char * code [32] = {
|
||||
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
|
||||
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
|
||||
};
|
||||
|
||||
echo_separator ();
|
||||
|
||||
terminal_colour (colour, effect);
|
||||
|
||||
if (character == 0X7F) {
|
||||
echo ("DEL");
|
||||
} else if (character_is_invisible (character) != 0) {
|
||||
echo (code [character]);
|
||||
if (string_length (code [character]) == 2) {
|
||||
echo (" ");
|
||||
}
|
||||
} else {
|
||||
out (& character, 1);
|
||||
echo (" ");
|
||||
}
|
||||
|
||||
terminal_cancel ();
|
||||
}
|
||||
|
||||
static void echo_name (int character, int colour, int effect) {
|
||||
char * name [128] = {
|
||||
"Null", "Start of heading", "Start of text", "End of text",
|
||||
"End of transmission", "Enquiry", "Acknowledge", "Bell",
|
||||
"Backspace", "Horizontal tab", "Line feed", "Vertical tab",
|
||||
"Form feed", "Carriage return", "Shift out", "Shift in",
|
||||
"Data link escape", "Device control 1", "Device control 2", "Device control 3",
|
||||
"Device control 4", "Negative acknowledge", "Synchronous idle", "End transmission block",
|
||||
"Cancel", "End of medium", "Substitute", "Escape",
|
||||
"File separator", "Group separator", "Record separator", "Unit separator",
|
||||
"Space", "Exclamation mark", "Speech mark", "Number sign",
|
||||
"Dollar sign", "Percent", "Ampersand", "Quote",
|
||||
"Open parenthesis", "Close parenthesis", "Asterisk", "Plus",
|
||||
"Comma", "Minus", "Period", "Slash",
|
||||
"Zero", "One", "Two", "Three",
|
||||
"Four", "Five", "Six", "Seven",
|
||||
"Eight", "Nine", "Colon", "Semicolon",
|
||||
"Open angled bracket", "Equal", "Close angled bracket", "Question mark",
|
||||
"At sign", "Uppercase A", "Uppercase B", "Uppercase C",
|
||||
"Uppercase D", "Uppercase E", "Uppercase F", "Uppercase G",
|
||||
"Uppercase H", "Uppercase I", "Uppercase J", "Uppercase K",
|
||||
"Uppercase L", "Uppercase M", "Uppercase N", "Uppercase O",
|
||||
"Uppercase P", "Uppercase Q", "Uppercase R", "Uppercase S",
|
||||
"Uppercase T", "Uppercase U", "Uppercase V", "Uppercase W",
|
||||
"Uppercase X", "Uppercase Y", "Uppercase Z", "Opening bracket",
|
||||
"Backslash", "Closing bracket", "Caret", "Underscore",
|
||||
"Grave", "Lowercase a", "Lowercase b", "Lowercase c",
|
||||
"Lowercase d", "Lowercase e", "Lowercase f", "Lowercase g",
|
||||
"Lowercase h", "Lowercase i", "Lowercase j", "Lowercase k",
|
||||
"Lowercase l", "Lowercase m", "Lowercase n", "Lowercase o",
|
||||
"Lowercase p", "Lowercase q", "Lowercase r", "Lowercase s",
|
||||
"Lowercase t", "Lowercase u", "Lowercase v", "Lowercase w",
|
||||
"Lowercase x", "Lowercase y", "Lowercase z", "Opening brace",
|
||||
"Vertical bar", "Closing brace", "Tilde", "Delete"
|
||||
};
|
||||
|
||||
echo_separator ();
|
||||
|
||||
terminal_colour (colour, effect);
|
||||
|
||||
echo (name [character]);
|
||||
|
||||
out (" ", 40 - string_length (name [character]));
|
||||
|
||||
terminal_cancel ();
|
||||
}
|
||||
|
||||
int main (void) {
|
||||
int character;
|
||||
|
||||
for (character = 0; character != 128; ++character) {
|
||||
echo_base (character, 2, COLOUR_BLUE, EFFECT_NORMAL);
|
||||
echo_base (character, 8, COLOUR_CYAN, EFFECT_NORMAL);
|
||||
echo_base (character, 10, COLOUR_CYAN, EFFECT_ITALIC);
|
||||
echo_base (character, 16, COLOUR_CYAN, EFFECT_BOLD);
|
||||
echo_code (character, COLOUR_PINK, EFFECT_BOLD);
|
||||
echo_name (character, COLOUR_WHITE, EFFECT_NORMAL);
|
||||
|
||||
if ((character % 2) != 0) {
|
||||
echo ("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
#include "../chapter/chapter_0.c"
|
||||
#include "../chapter/chapter_1.c"
|
||||
#include "../chapter/chapter_2.c"
|
||||
|
||||
static int entity_define (char * name, int character, int colour, int effect, int value, int logic, void (* trigger) (void));
|
||||
static void entity_create (int entity, int amount);
|
||||
static void entity_render (int entity);
|
||||
|
||||
static void entity_clean_up (void);
|
||||
static void entity_update_logic (void);
|
||||
|
||||
static int player_x = 0;
|
||||
static int player_y = 0;
|
||||
static int player_coins = 0;
|
||||
|
||||
static int entity_count = 0;
|
||||
static char * * entity_name = NULL;
|
||||
static char * entity_character = '\0';
|
||||
static int * entity_colour = 0;
|
||||
static int * entity_effect = 0;
|
||||
static int * entity_value = 0;
|
||||
static int * entity_logic = 0;
|
||||
|
||||
static void (* * entity_trigger) (void) = NULL;
|
||||
|
||||
static int entity_usage = 0;
|
||||
static int * entity_index = 0;
|
||||
static int * entity_x = 0;
|
||||
static int * entity_y = 0;
|
||||
|
||||
static void player_move_up (void) { player_y -= 1; limit (& player_y, 0, curses_screen_height - 1); }
|
||||
static void player_move_down (void) { player_y += 1; limit (& player_y, 0, curses_screen_height - 1); }
|
||||
static void player_move_left (void) { player_x -= 1; limit (& player_x, 0, curses_screen_width - 1); }
|
||||
static void player_move_right (void) { player_x += 1; limit (& player_x, 0, curses_screen_width - 1); }
|
||||
static void player_take_coin (void) { ++player_coins; }
|
||||
static void player_died (void) { curses_active = 0; }
|
||||
|
||||
int main (void) {
|
||||
terminal_show_cursor (FALSE);
|
||||
|
||||
curses_configure ();
|
||||
|
||||
curses_bind (SIGNAL_W, player_move_up);
|
||||
curses_bind (SIGNAL_S, player_move_down);
|
||||
curses_bind (SIGNAL_A, player_move_left);
|
||||
curses_bind (SIGNAL_D, player_move_right);
|
||||
|
||||
int coin = entity_define ("Coin", '+', COLOUR_YELLOW, EFFECT_BOLD, 1, FALSE, player_take_coin);
|
||||
int goblin = entity_define ("Goblin", 'g', COLOUR_RED, EFFECT_NORMAL, 3, TRUE, player_died);
|
||||
int hob_goblin = entity_define ("Hob Goblin", 'G', COLOUR_RED, EFFECT_BOLD, 7, TRUE, player_died);
|
||||
|
||||
entity_create (coin, 11);
|
||||
entity_create (goblin, 7);
|
||||
entity_create (hob_goblin, 3);
|
||||
|
||||
while (curses_active) {
|
||||
curses_render_background ('.', COLOUR_GREY, EFFECT_BOLD);
|
||||
|
||||
curses_render_rectangle_fill (',', COLOUR_GREEN, EFFECT_NORMAL, 10, 10, 80, 24);
|
||||
curses_render_rectangle_line ('#', COLOUR_WHITE, EFFECT_NORMAL, 10, 10, 80, 24);
|
||||
|
||||
for (int entity = 0; entity < entity_usage; ++entity) {
|
||||
entity_render (entity);
|
||||
}
|
||||
|
||||
curses_render_character ('@', COLOUR_CYAN, EFFECT_BOLD, player_x, player_y);
|
||||
|
||||
curses_render_number (player_coins, COLOUR_YELLOW, EFFECT_BOLD, 0, 0);
|
||||
|
||||
entity_update_logic ();
|
||||
|
||||
curses_synchronize ();
|
||||
}
|
||||
|
||||
entity_clean_up ();
|
||||
|
||||
terminal_show_cursor (TRUE);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static int entity_define (char * name, int character, int colour, int effect, int value, int logic, void (* trigger) (void)) {
|
||||
int entity = entity_count;
|
||||
|
||||
++entity_count;
|
||||
|
||||
entity_name = reallocate (entity_name, entity_count * (int) sizeof (* entity_name));
|
||||
entity_character = reallocate (entity_character, entity_count * (int) sizeof (* entity_character));
|
||||
entity_colour = reallocate (entity_colour, entity_count * (int) sizeof (* entity_colour));
|
||||
entity_effect = reallocate (entity_effect, entity_count * (int) sizeof (* entity_effect));
|
||||
entity_value = reallocate (entity_value, entity_count * (int) sizeof (* entity_value));
|
||||
entity_logic = reallocate (entity_logic, entity_count * (int) sizeof (* entity_logic));
|
||||
entity_trigger = reallocate (entity_trigger, entity_count * (int) sizeof (* entity_trigger));
|
||||
|
||||
string_copy ((entity_name [entity] = allocate (string_length (name) + 1)), name);
|
||||
|
||||
entity_character [entity] = character;
|
||||
entity_colour [entity] = colour;
|
||||
entity_effect [entity] = effect;
|
||||
entity_value [entity] = value;
|
||||
entity_logic [entity] = logic;
|
||||
entity_trigger [entity] = trigger;
|
||||
|
||||
return (entity);
|
||||
}
|
||||
|
||||
static void entity_create (int entity, int amount) {
|
||||
int index;
|
||||
|
||||
for (index = 0; index < amount; ++index) {
|
||||
++entity_usage;
|
||||
|
||||
entity_index = reallocate (entity_index, entity_usage * (int) sizeof (* entity_index));
|
||||
entity_x = reallocate (entity_x, entity_usage * (int) sizeof (* entity_x));
|
||||
entity_y = reallocate (entity_y, entity_usage * (int) sizeof (* entity_y));
|
||||
|
||||
entity_index [entity_usage - 1] = entity;
|
||||
entity_x [entity_usage - 1] = randomize (0, curses_screen_width - 1);
|
||||
entity_y [entity_usage - 1] = randomize (0, curses_screen_height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void entity_render (int entity) {
|
||||
if (entity_index [entity] == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
curses_render_character (entity_character [entity_index [entity]],
|
||||
entity_colour [entity_index [entity]],
|
||||
entity_effect [entity_index [entity]],
|
||||
entity_x [entity],
|
||||
entity_y [entity]);
|
||||
}
|
||||
|
||||
void entity_clean_up (void) {
|
||||
for (int entity = 0; entity < entity_count; ++entity) {
|
||||
entity_name [entity] = deallocate (entity_name [entity]);
|
||||
}
|
||||
|
||||
entity_name = deallocate (entity_name);
|
||||
entity_character = deallocate (entity_character);
|
||||
entity_colour = deallocate (entity_colour);
|
||||
entity_effect = deallocate (entity_effect);
|
||||
entity_value = deallocate (entity_value);
|
||||
entity_logic = deallocate (entity_logic);
|
||||
entity_trigger = deallocate (entity_trigger);
|
||||
entity_index = deallocate (entity_index);
|
||||
entity_x = deallocate (entity_x);
|
||||
entity_y = deallocate (entity_y);
|
||||
|
||||
entity_count = 0;
|
||||
entity_usage = 0;
|
||||
}
|
||||
|
||||
void entity_update_logic (void) {
|
||||
for (int entity = 0; entity < entity_usage; ++entity) {
|
||||
if (entity_index [entity] == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity_logic [entity_index [entity]] == TRUE) {
|
||||
entity_x [entity] += randomize (0, 1) * ((randomize (0, 1) == 0) ? +1 : -1);
|
||||
entity_y [entity] += randomize (0, 1) * ((randomize (0, 1) == 0) ? +1 : -1);
|
||||
|
||||
limit (& entity_x [entity], 0, curses_screen_width - 1);
|
||||
limit (& entity_y [entity], 0, curses_screen_height - 1);
|
||||
}
|
||||
|
||||
if ((player_x == entity_x [entity]) && (player_y == entity_y [entity])) {
|
||||
if (entity_trigger [entity_index [entity]] != NULL) {
|
||||
entity_trigger [entity_index [entity]] ();
|
||||
}
|
||||
|
||||
entity_index [entity] = -1;
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -1,235 +0,0 @@
|
||||
#include "../chapter/chapter_0.c"
|
||||
#include "../chapter/chapter_1.c"
|
||||
#include "../chapter/chapter_2.c"
|
||||
#include "../chapter/chapter_3.c"
|
||||
#include "../chapter/chapter_4.c"
|
||||
|
||||
static void highlight_common (void) {
|
||||
char * separators = ".,:;<=>+-*/%!&~^?|()[]{}'\"@#$` \t\r\n";
|
||||
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,:;<=>+*-/%!&~^?|@#$`", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
static void highlight_c (void) {
|
||||
char * separators = ".,:;<=>+-*/%!&~^?|()[]{}'\" \t\r\n";
|
||||
|
||||
char * keywords [] = {
|
||||
"register", "volatile", "auto", "const", "static", "extern", "if", "else",
|
||||
"do", "while", "for", "continue", "switch", "case", "default", "break",
|
||||
"enum", "union", "struct", "typedef", "goto", "void", "return", "sizeof",
|
||||
"char", "short", "int", "long", "signed", "unsigned", "float", "double"
|
||||
};
|
||||
|
||||
int word;
|
||||
|
||||
syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,:;<=>+*-/%!&~^?|", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
static void highlight_ada (void) {
|
||||
char * separators = ".,:;<=>+-*/&|()\" \t\r\n";
|
||||
|
||||
char * keywords [] = {
|
||||
"abort", "else", "new", "return", "abs", "elsif", "not", "reverse",
|
||||
"abstract", "end", "null", "accept", "entry", "select", "access", "of",
|
||||
"separate", "aliased", "exit", "or", "some", "all", "others", "subtype",
|
||||
"and", "for", "out", "array", "function", "at", "tagged", "generic",
|
||||
"package", "task", "begin", "goto", "pragma", "body", "private", "then",
|
||||
"type", "case", "in", "constant", "until", "is", "raise", "use",
|
||||
"if", "declare", "range", "delay", "limited", "record", "when", "delta",
|
||||
"loop", "rem", "while", "digits", "renames", "with", "do", "mod",
|
||||
"requeue", "xor", "procedure", "protected", "interface", "synchronized", "exception", "overriding",
|
||||
"terminate"
|
||||
};
|
||||
|
||||
int word;
|
||||
|
||||
syntax_define (FALSE, FALSE, "--", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
syntax_define (TRUE, FALSE, "()", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,:;<=>+-*/&|'", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
static void highlight_cpp (void) {
|
||||
char * separators = ".,:;<=>+-*/%!&~^?|()[]{}'\" \t\r\n";
|
||||
|
||||
char * keywords [] = {
|
||||
"alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept",
|
||||
"auto", "bitand", "bitor", "bool", "break", "case", "catch", "char",
|
||||
"char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const", "consteval",
|
||||
"constexpr", "constinit", "const_cast", "continue", "co_await", "co_return", "co_yield", "decltype",
|
||||
"default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit",
|
||||
"export", "extern", "false", "float", "for", "friend", "goto", "if",
|
||||
"inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not",
|
||||
"not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public",
|
||||
"reflexpr", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof",
|
||||
"static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this",
|
||||
"thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union",
|
||||
"unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor",
|
||||
"xor_eq", "final", "override", "import", "module", "transaction_safe"
|
||||
};
|
||||
|
||||
int word;
|
||||
|
||||
syntax_define (FALSE, FALSE, "/*", "*/", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "//", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "#", "\n", '\\', COLOUR_YELLOW, EFFECT_ITALIC);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,:;<=>+*-/%!&~^?|", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
static void highlight_flat_assembly (void) {
|
||||
char * separators = ".,+-=:;(){}[]%$<> \t\r\n";
|
||||
|
||||
char * instructions [] = {
|
||||
"mov", "movabs", "movapd", "movaps", "movebe", "movsd", "movsx", "movzx",
|
||||
"movsxd", "movd", "movq", "movs", "movsb", "movsw", "movsd", "movsq",
|
||||
"cmovmp", "cmovrcxz", "cmovc", "cmovnc", "cmove", "cmovne", "cmovz", "cmovnz",
|
||||
"cmovg", "cmovng", "cmovge", "cmovnge", "cmovl", "cmovnl", "cmovle", "cmovnle",
|
||||
"cmova", "cmovna", "cmovae", "cmovnae", "cmovb", "cmovnb", "cmovbe", "cmovnbe",
|
||||
"cmovs", "cmovns", "cmovo", "cmovno", "cmovp", "cmovnp", "cmovpo", "cmovpe",
|
||||
"cmp", "cmps", "cmpsb", "cmpsw", "cmpsd", "cmpsq", "cmpxchg", "lea",
|
||||
"monitor", "cpuid", "in", "out", "syscall", "sysenter", "sysret", "sysexit",
|
||||
"swap", "bswap", "pop", "push", "call", "ret", "enter", "leave",
|
||||
"and", "or", "not", "neg", "sal", "sar", "shl", "shr",
|
||||
"inc", "dec", "add", "sub", "mul", "div", "imul", "idiv",
|
||||
"nop", "fnop", "adc", "sbb", "aaa", "aas", "aam", "aad",
|
||||
"jmp", "jrcxz", "jc", "jnc", "je", "jne", "jz", "jnz",
|
||||
"jg", "jng", "jge", "jnge", "jl", "jnl", "jle", "jnle",
|
||||
"ja", "jna", "jae", "jnae", "jb", "jnb", "jbe", "jnbe",
|
||||
"js", "jns", "jo", "jno", "jp", "jnp", "jpo", "jpe",
|
||||
"rep", "repe", "repz", "repne", "repnz", "loop", "loope", "loopne"
|
||||
};
|
||||
|
||||
char * registers [] = {
|
||||
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
|
||||
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
||||
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
|
||||
"r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d",
|
||||
"ax", "cx", "dx", "bx", "sp", "bp", "si", "di",
|
||||
"r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w",
|
||||
"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil",
|
||||
"r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b",
|
||||
"ah", "ch", "dh", "bh"
|
||||
};
|
||||
|
||||
char * keywords [] = {
|
||||
"format", "executable", "readable", "writable", "segment", "sector", "entry", "macro",
|
||||
"db", "dw", "dd", "dq", "rb", "rw", "rd", "rq"
|
||||
};
|
||||
|
||||
int word;
|
||||
|
||||
syntax_define (FALSE, FALSE, ";", "\n", '\0', COLOUR_GREY, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "'", "'", '\\', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (FALSE, FALSE, "\"", "\"", '\\', COLOUR_PINK, EFFECT_NORMAL);
|
||||
|
||||
for (word = 0; word != (int) (sizeof (instructions) / sizeof (instructions [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, instructions [word], separators, '\0', COLOUR_YELLOW, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
for (word = 0; word != (int) (sizeof (registers) / sizeof (registers [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, registers [word], separators, '\0', COLOUR_CYAN, EFFECT_BOLD);
|
||||
}
|
||||
|
||||
for (word = 0; word != (int) (sizeof (keywords) / sizeof (keywords [0])); ++word) {
|
||||
syntax_define (FALSE, TRUE, keywords [word], separators, '\0', COLOUR_YELLOW, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
syntax_define (TRUE, FALSE, "()[]{}", "", '\0', COLOUR_BLUE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, FALSE, ".,+-=:;%$<>", "", '\0', COLOUR_CYAN, EFFECT_NORMAL);
|
||||
|
||||
syntax_define (TRUE, TRUE, "0123456789", separators, '\0', COLOUR_PINK, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "abcdefghijklmnopqrstuvwxyz", separators, '\0', COLOUR_WHITE, EFFECT_NORMAL);
|
||||
syntax_define (TRUE, TRUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", separators, '\0', COLOUR_WHITE, EFFECT_BOLD);
|
||||
syntax_define (TRUE, TRUE, "_", separators, '\0', COLOUR_WHITE, EFFECT_ITALIC);
|
||||
}
|
||||
|
||||
int main (int argc, char * * argv) {
|
||||
int offset = 0;
|
||||
int select = 0;
|
||||
int length = 0;
|
||||
char * buffer = NULL;
|
||||
|
||||
if (argc < 2) {
|
||||
print ("Usage: /1.//program_4 [text_file]/-\n");
|
||||
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
select = file_type (argv [1]);
|
||||
buffer = file_record (argv [1]);
|
||||
|
||||
if ((select == FILE_TYPE_C_SOURCE) || (select == FILE_TYPE_C_HEADER)) {
|
||||
highlight_c ();
|
||||
} else if ((select == FILE_TYPE_ADA_BODY) || (select == FILE_TYPE_ADA_SPECIFICATION)) {
|
||||
highlight_ada ();
|
||||
} else if ((select == FILE_TYPE_CPP_SOURCE) || (select == FILE_TYPE_CPP_HEADER)) {
|
||||
highlight_cpp ();
|
||||
} else if (select == FILE_TYPE_FLAT_ASSEMBLY) {
|
||||
highlight_flat_assembly ();
|
||||
} else {
|
||||
highlight_common ();
|
||||
}
|
||||
|
||||
for (offset = 0; buffer [offset] != '\0'; offset += length) {
|
||||
select = syntax_select (& buffer [offset], & length);
|
||||
|
||||
if (select >= syntax_count) {
|
||||
terminal_colour (COLOUR_WHITE, EFFECT_NORMAL);
|
||||
} else {
|
||||
terminal_colour (syntax_colour [select], syntax_effect [select]);
|
||||
}
|
||||
|
||||
out (& buffer [offset], length);
|
||||
|
||||
terminal_cancel ();
|
||||
}
|
||||
|
||||
buffer = deallocate (buffer);
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,11 +0,0 @@
|
||||
#include "../chapter/chapter_0.c"
|
||||
#include "../chapter/chapter_1.c"
|
||||
#include "../chapter/chapter_2.c"
|
||||
#include "../chapter/chapter_x.c"
|
||||
#include "../chapter/chapter_y.c"
|
||||
|
||||
int main (void) { // EXPERIMENTAL (WILL BE DELETED)...
|
||||
play_game ();
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
55
xhartae.c
55
xhartae.c
@ -1,4 +1,10 @@
|
||||
#include <stdlib.h>
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifdef STATIC_SOURCE
|
||||
#include "chapter/chapter_0.c"
|
||||
@ -7,7 +13,6 @@
|
||||
#include "chapter/chapter_3.c"
|
||||
#include "chapter/chapter_4.c"
|
||||
#include "chapter/chapter_5.c"
|
||||
//~#include "chapter/chapter_6.c"
|
||||
#else
|
||||
#include "chapter/chapter_0.h"
|
||||
#include "chapter/chapter_1.h"
|
||||
@ -15,7 +20,6 @@
|
||||
#include "chapter/chapter_3.h"
|
||||
#include "chapter/chapter_4.h"
|
||||
#include "chapter/chapter_5.h"
|
||||
//~#include "chapter/chapter_6.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -60,13 +64,13 @@ Why should you learn or use C programming language in 2023?
|
||||
Goal of this so-called book?
|
||||
|
||||
- You'll be able to write your own compiler or interpretter, for your own language after reading this, and many more programs.
|
||||
- You might learn something new. My target audience is people who don't know C language, but maybe there's something new for you.
|
||||
- You might learn something new. My target audience is people who don't know C languages, but maybe there's something new for you.
|
||||
- You won't (hopefully) buy into big ideas and paradigms about programming, you'll just create programs, and have fun doing so.
|
||||
|
||||
Before we get into it, there are some requirements that this book is based on. Thou shalt use GNU/systemd/Linux operating system, commonly and wrongly refered to as Linux OS or
|
||||
just simply distro (distribution). They come in many flavours, so if you're new to this, you could just install BunsenLabs, LXLE or Mint on some old laptop, if you want to finally
|
||||
switch totally from spyware known as Windows (niche OS made for games). You'll revive your old laptop that was useless, and you support free software by doing so, making yourself
|
||||
a part of the revolution. Beside that, you'll need a C compiler such as TCC, GCC (which is preinstalled on most distros) or Clang, and lastly Valgrind and Splint, to verify the
|
||||
just simply distro (distribution). They come in many flavours, so if you're new to this, you could just install BunsenLabs, LXLE or Mint on some old laptop, if you don't want to
|
||||
switch totally from spyware known as Windows (niche OS made for games). You'll revive your old laptop that was useless, and you support free software by doing so, making you a
|
||||
part of the revolution. Beside that, you'll need a C compiler such as TCC, GCC (which is preinstalled on most distros) or Clang, and lastly Valgrind and Splint, to verify the
|
||||
quality of your C source code. Text editor or IDE is a trivial thing at the begining, but it'll shape your coding style in the long run. Lets talk about them.
|
||||
|
||||
GNU/Linux operating systems:
|
||||
@ -100,14 +104,14 @@ Text editors or IDEs (integrated development environments):
|
||||
- ed: It's the STANDARD text editor for UNIX-based operating systems. Nothing else can be said, it's literally the best tool I've ever seen, and I use it sometimes.
|
||||
- kilo: Terminal text editor, written in C language, less than 1000 lines of code. It's nice, simple and usable, and you can extend it once you learn C.
|
||||
- nano: Also terminal text editor, very simple to use, but some prefer micro over it. You gotta admit, people are very creative when naming their software...
|
||||
- Vim: Beast of an editor, once you learn how to exit it, you'll be like a Gott to other non-Vim users. I won't tell you how to quit Vim. I don't know.
|
||||
- Emacs: Not a text editor, but an entire operating system, your pinky finger will be twice stronger because of it. It has many extensions and all PROs use it.
|
||||
- Vim: Beast of an editor, once you learn how to exit it, you'll be like a Gott to other non-Vim users. I won't tell you how to exit it. I don't know.
|
||||
- Emacs: Yet another beast of an editor, your pinky finger will be twice stronger because of it. It has many extensions and all PROs use it.
|
||||
- Mousepad: State of the art text editor, it works same as Notepad, but it's more based. You really don't need anything more complex than this for editing a text file.
|
||||
- Geany: I use it most of the time, but I switch to nano, Emacs, my own text editor whenever I feel like it, in order not to limit myself to just one tool.
|
||||
|
||||
Jokes aside, don't obesess over any distro (as long as it's some kind of Linux, BSD is cucked), any kind of compiler and any kind of text editor! You only want to edit text file,
|
||||
insert and delete few lines of code, nothing more. You don't need some IDE (bloated text editor) that uses 2 GiB or RAM, opens up after 10s of blank window, or works slow overall.
|
||||
Just don't use any of the following for reasons you'll understand once your grow up: Windows, MacOS, MSVC, ICC, VS Code, Visual Studio, Atom, Helix, Kakoune, Eclipse, Sublime.
|
||||
insert and delete few lines of code, nothing more. You don't need some IDE (bloated text editor) that uses 2 GiB or RAM, opens up after 10s of blank window, and work slow overall.
|
||||
Just don't use any of the following for reasons you'll understand once your grow up: Windows, MSVC, VS Code, Visual Studio, Atom, Helix, Kakoune, Eclipse, Sublime.
|
||||
|
||||
One sane C program should have the following structure (please keep in mind that this is a book, not a sane program, thanks...):
|
||||
|
||||
@ -115,41 +119,22 @@ One sane C program should have the following structure (please keep in mind that
|
||||
1) Header guards and implementation definitions.
|
||||
2) System header files then project header files.
|
||||
3) Macro definitions (please avoid them).
|
||||
4) Internal function declarations then variable definitions.
|
||||
4) Internal function then variable declarations.
|
||||
5) External function then variable declarations.
|
||||
6) Internal function definition.
|
||||
6) Internal function then variable definition.
|
||||
7) External function then variable definition.
|
||||
8) Main function.
|
||||
9) You can also define functions here if you want to.
|
||||
|
||||
I can't bother to explain what's a compilation process right now, essentially, you want to translate one array of bytes into another array of bytes. You know what's a terminal,
|
||||
file manager, file, folder, and you can find out (in the world wide web) how to navigate in your terminal using 'cd', 'ls' and other commands (aka programs). I'll assume that you
|
||||
have opened terminal in your working directory, that your wrote some C code in your "main.c" file and that you want to compile it. In order to compile you source code into an
|
||||
executable program, these are the steps:
|
||||
|
||||
@Shell
|
||||
gcc -o program main.c # Compile your source code.
|
||||
./program # Run your program.
|
||||
@
|
||||
|
||||
That's enough to know for now, feel free to continue reading and enjoy writing.
|
||||
*/
|
||||
|
||||
int main (int argc, char * * argv) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
program_curses_view_file ("program/program_1.c", 0, 0);
|
||||
curses_configure ();
|
||||
|
||||
blesses_configure ();
|
||||
preview_c_file ("program/example.c", curses_screen_width, curses_screen_height, 0, 0);
|
||||
|
||||
while (blesses_active) {
|
||||
blesses_render_string ("Heyo world!", 0XFF0000, 0XFF, 0, 0);
|
||||
|
||||
blesses_synchronize ();
|
||||
}
|
||||
|
||||
program_curses_view_file ("example/ada.adb", 0, 0);
|
||||
play_game ();
|
||||
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user