Oldies but goldies Angband rules.. ported from MAngaband:
- Don’t use floating point calculations.
- Don’t break savefile compatibility (if not absolutely necessary).
- No C++ code. That also means that it’s better not to use ‘//’ comments (though for Tangaria we use // to distinguish T changes from PWMA changes).
- Put system dependent code between #ifdef xyz … #endif /* xyz */ .
- No “magic numbers”. Use #defines. The #defines should be in defines.h rather than the source file, unless they’re very local (such as dungeon generation parameters).
- Spaces around the mathematical, comparison, and assignment operators (
+
,-
,*
,/
,=
,!=
,==
,>
, …). - Spaces between C-identifiers like
if
,while
,for
, andreturn
and the opening brackets (if (foo)
,while (bar)
, …). - No spaces between function names and brackets and between brackets and function arguments (
function(1, 2)
instead offunction ( 1, 2 )
). - If the precedence of operations is ambiguous then put brackets around them. Also try to insert parentheses whenever necessary for readability, eg. around (foo & RF3_SOME_DEFINE). There is usually no ambiguity and no macro resolution, but this is an aesthetic detail.
- If a function takes no arguments then it should be declared with a void argument list. Example:
int foo(void)
instead ofint foo()
. - Function declaration and definition should look the same.
- Don’t assume that functions declared without a return type return int. Specify the type explicitly. Example:
int foo(void)
instead offoo(void)
. - Indentation with tabs. But generally avoid getting lines over 80 characters.
Code
String functions
Our codebase (dating back to 1980s’) is a tangled mess of supporting many broken, non-standard, POSIX vs ANSI, etc, compilers and environments. Because of that you can see evil things like #define strcasecmp stricmp
AND #define stricmp strcasecmp
and similar.
Don’t guess! Just use those functions:
Operation | Don’t use | Use |
---|---|---|
allocate new and copy | strdup, _strdup | string_make |
case-sensitive compare | !strcmp, _strcmp | streq |
case-insensitive compare | stricmp, strcasecmp | my_stricmp |
case-insensitive cmp,len | strnicmp, strncasecmp | my_strnicmp |
starts with | prefix | |
ends with | suffix | |
copy src into dst | strcpy, strncpy, strlcpy | my_strcpy |
concat src into dst | strcat, strncat, strlcat | my_strcat |
free string | free | string_free |
Primitive types
Integers
Do NOT use platform-dependent C types, like int
, long
, long int
, etc. You are allowed to use int
for creating iterator variables, when it will likely won’t matter, e.g.
int i;
for (i = 0; i < MAX_HOUSES; i++)
but in all other cases, please stick to (M)Angband types:
name | sign | bits |
---|---|---|
byte | unsigned | 8 bits |
char | signed..? | 8 bits |
u16b | unsigned | 16 bits |
s16b | signed | 16 bits |
u32b | unsigned | 32 bits |
s32b | signed | 32 bits |
Note, that we also have syte
(a signed byte) type, which should be used instead of char
, in practice that is not ever used, so you can ignore it.
(M)Angband codebase also has huge
and micro
types, which are defined as “largest possible integer on your platform”. Those should NOT be used, unless absolutely necessary. Do NOT try to transmit those over network.
Strings
When creating strings, cptr
type should be used. This resolves to const char*
.
Boolean
When you need a boolean value, use bool
. It’s probably a char
or an int
, beneath the surface, but it denotes your intent. Note, that we have TRUE
and FALSE
defined, use those instead of 0
and 1
.
Return values
It’s OK to return int
from functions that need to report an error. However, please consider the alternatives: errr
is (probably) the same as int
, but signifies your intent much better, it assumes 0
means “no error”. bool
, on the other hand, means TRUE
for success, and FALSE
on error. Had you used plain int
, ambiguity around 0
would be created, is it an error or no-error condition?
Bitflags
When creating bitflag variables, use u32b
. This gives you 32 bits to play with. If you need more, create ANOTHER variable, e.g.
struct some_type {
u32b flags1;
u32b flags2;
}
All actual flags MUST be defined with appropriate names, e.g.
#define XXXF1_SOME_FLAG 0x00000001 /* "1" here means "flags1" */
#define XXXF1_OTHER_FLAG 0x00000002
#define XXXF2_YET_ANOTHER 0x00000001 /* "2" here means "flags2" */
Buffer sizes
We display up to 80 characters on generic terminal. That means: indexes 0-79 may contain data and index 80 is used for string terminator. Meaning the buffers have to declared with size 81 (!). When passing length argument to string functions, pass the buffer size length, not maximum string length.
For your convenience, 2 global constants are defined: MAX_CHARS (80+1)
and MAX_COLS 80
. Therefore,
char buf[MAX_CHARS];
my_strcpy(buf, foo, MAX_CHARS);
buf[MAX_COLS] = '\0'; /* When cutting */
buf[79] = '\0'; /* WRONG!!! */
buf[78] = '\0'; /* WRONG!!! */
Buffer overflows
Never, ever, ever use strcpy
and strcat
. Always use my_strcpy
and my_strcat
, which are mangband equivalents to strlcpy and strlcat from BSD. Our ancestors provided them for a reason.
Switches
It’s fine to use switches and it’s fine to omit breaks, but all such cases must be explicitly marked via a comment.
switch (value) {
case 0:
case 1:
a = 1;
/* fallthrough */
case 2:
b = 2;
break;
}
Indentation-wise, put that comment on the same line you would’ve wrote “break”.
Line breaks
All .c and .h files must use UNIX (LF) line-breaks.
Indentation
/*
* Multi-line comments look like this.
* C++ // comments are not allowed
*/
\t int scope(void)
\t {
\t \t int variable; /* C89 declarations on top of the scope */
\t \t char buf[80]; /* Declare arrays from constants */
\n
\t \t /* Put curly braces on an extra line with the same indentation level as the previous line */
\t \t if (something == 0)
\t \t {
\t \t \t do_something(buf)
\t \t }
\n
\t \t /* Put empty lines between logical blocks of code */
\t \t do_something_else(variable);
\t \t return 0;
\t }
Coordinates
Whenever you need a 2-dimensional structure, or to pass X and Y coordinates to a function, always use row/Y first, then column/X.
Whenever you need to display those to player, the reverse applies. All human-readable output should be in a “X,Y” format.
Gameplay messages
voice of DM
in great Moria tradition, most of the original game-play related error messages are coming from first person. We should enforce this. Good example of using DM voice:
I see no down staircase here.
personal
You see nothing there to close.
in-personal
You seem unable to go down. Try going up.
There is nothing on the floor.
So all in all, we lack standards here 🙂
File names
All .c/.h files despite directory of residence must have unique names. Duplicate files shall append prefix to every copy but first, prioritizing by common, server, client order. The prefixes are “c-” for client, “m” for server and no prefix for common.
Sample table:
original file | common/ | server/ | client/ |
---|---|---|---|
birth.c | birth.c | ||
birth.c | birth.c | ||
birth.c | birth.c | ||
birth.c | birth.c | mbirth.c | |
birth.c | birth.c | c-birth.c | |
birth.c | birth.c | c-birth.c | |
birth.c | birth.c | mbirth.c | c-birth.c |
Network
cq_printf
and cq_scanf
functions accept the following format specifiers:
Integers:
type | bits | sign | fmt |
---|---|---|---|
char | 8 | ??? | %c |
byte | 8 | no | %b |
s16b | 16 | yes | %d |
u16b | 16 | no | %ud |
s32b | 32 | yes | %l |
u32b | 32 | no | %ul |
Strings:
fmt | max length | comment |
---|---|---|
%s | MAX_CHARS-1 | null-terminated |
%S | MSG_LEN-1 | null-terminated |
%n | MAX_CHARS-1 | pascal-style with byte prefix |
%N | MSG_LEN-1 | pascal-style with u16b prefix |
We do not have floats and we do not ever transmit floats over network.
Micro-optimizations
Our ancestors were a bit too obsessive about micro-optimizing the code. In particular, function calls were considered evil (it adds a lot of asm overhead!), with lots of code duplication, and #define FOO(B)
were used a lot instead of proper functions.
It might be tempting to follow suite, but in the current year, with processors faster by several orders of magnitude, code readability should come first.
We’re not looking to rewrite everything, just for the sake of it, but when writing new code DO NOT BE AFRAID to introduce new functions!
Also, do not use inline
keyword, ever.
Etc dev notes
Leftovers after Necro design:
fire_ball(who, PROJ_MISSILE, 0, 300, 2, false, true);
<<< wrong dir
effect_simple(EF_BLAST, who, "300", PROJ_MISSILE, 2, 0, 0, 0, &context->ident);
<<< wrong source (only around p)
// (index, source, dmg, subtype, radius, other, y, x, ident)
MIN(a,b) (a > b) ? b : a
MAX(a,b) (a < b) ? b : a
ABS(a) (a < 0) ? -a : a
SGN(a) (a < 0) ? -1 : a != 0
CMP(a,b) (a < b) ? -1 : (b < a) ? 1 : 0
Housing
Important note: Y goes from top! It’s not traditional 2D graph, but graph which starts on top left corner of the map!
struct house_type struct loc grid_1; Location of house struct loc grid_2; struct loc door; Location of door struct worldpos wpos; Position @ world map s32b price; Cost of buying s32b ownerid; Owner ID char ownername[NORMAL_WID]; Owner name byte color; Door color byte state; State byte free; Bought with Deed of Property
State of a house: 0 = unallocated 1 = normal 2 = extended 3 = custom
Which characters allowed to be used in string literals of C program except numbers and letters?
! " # % & ' ( ) * + , - . / :
; < = > ? [ \ ] ^ _ { | } ~
Also space character and control characters representing horizontal tab, vertical tab and form feed.
In C language *
, /
, %
got equal precedence, but evaluate left to right:
x * y / z
is the same as (x * y) / z
and x / y * z
is the same as (x / y) * z
1 / 10 == 0;
chunk_get()
struct dice_s { int b, x, y, m; bool ex_b, ex_x, ex_y, ex_m; dice_expression_entry_t *expressions; };
typedef struct dice_s dice_t;
dice_parse_string(effect.dice, dice_string);