Initial specification v0.1 and core testing example...
This commit is contained in:
parent
797ca2c81c
commit
f00f770ac2
132
README.md
132
README.md
@ -1,3 +1,131 @@
|
|||||||
# xungeons
|
# Small Dungeon Crawler Specification
|
||||||
|
|
||||||
Small Dungeon Crawler Specification -- Game-Dev Challenge
|
```
|
||||||
|
_____ ____ ____ _____
|
||||||
|
/ ___| | _ \ / ___| / ___| Small
|
||||||
|
\___ \ | | | | | | \___ \ Dungeon
|
||||||
|
___) | | |_| | | |___ ___) | Crawler
|
||||||
|
|____/ |____/ \____| |____/ Specification
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Code
|
||||||
|
|
||||||
|
General:
|
||||||
|
- This specification can be implemented in any programming language and in any formatting style.
|
||||||
|
- True aim is that it should be minimal in terms of source lines of code, and resource usage when possible.
|
||||||
|
- All memory leaks should be cleaned, which can be checked with Valgrind, compiler warnings aren't taken in account.
|
||||||
|
- Some APIs, drivers and libraries like SDL2, Raylib, OpenGL and Vulkan leak memory, those leaks are forgiven.
|
||||||
|
- Also, cleaning up all compiler and/or linter warnings is a benefit, but is not required at all.
|
||||||
|
|
||||||
|
Scoring:
|
||||||
|
- As this was intended as a challenge for various programming languages and programmers, scoring rules are:
|
||||||
|
```
|
||||||
|
+2 points per weapon, armour, jewelry, enemy, attribute, skill, class or spell definition.
|
||||||
|
+1 point per full line of code (in C, this means that blank lines or lines with characters '{', '}', '};', '},' are ignored).
|
||||||
|
-1 point per compiler warning, on maximum warning options (in C, that would be -Weverything or -Wall -Wextra).
|
||||||
|
-2 points per memory leak of 10 bytes (again, libraries and drivers aren't accounted in this case, they can leak).
|
||||||
|
```
|
||||||
|
- Then, final score is the total number of points divided by count of lines of code.
|
||||||
|
|
||||||
|
## Gameplay
|
||||||
|
|
||||||
|
General:
|
||||||
|
- Note that further explanations for gameplay elements are given separately, in text below for consistency.
|
||||||
|
- Game screen layout consists of 2 panels, on the left is world state preview, on the right is player state preview.
|
||||||
|
- World state is updated on every action (key pressed), unless some menu is active, which includes animations and bots too.
|
||||||
|
- Default keyboard action mapping must be consistent across games that follow this specification, but bindings are allowed.
|
||||||
|
- Important gameplay elements must be implemented, and the amount of them is up to developer to decide.
|
||||||
|
|
||||||
|
Default keyboard action mapping:
|
||||||
|
```
|
||||||
|
Use - KP0 - O
|
||||||
|
Inventory - KP- - I
|
||||||
|
Wait - KP5 - .
|
||||||
|
Up - KP8 - K
|
||||||
|
Down - KP2 - J
|
||||||
|
Left - KP4 - H
|
||||||
|
Right - KP6 - L
|
||||||
|
Up+Left - KP7 - Y
|
||||||
|
Up+Right - KP9 - U
|
||||||
|
Down+Left - KP1 - B
|
||||||
|
Down+Right - KP3 - N
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Game following this specification can be implemented in terminal or graphical window, as developer decides.
|
||||||
|
- It's not important if game has 3 or 300 types of weapons or armours, it's important that it has them.
|
||||||
|
- Lastly, it's important to have most minimal implementation in terms of source lines of code, for any language.
|
||||||
|
|
||||||
|
Elements:
|
||||||
|
- Attributes influence health, mana, stamina and experience points of all creatures (party members and bots).
|
||||||
|
- Skills are passive abilities influencing damage, condition, proficiency and other item usage modifiers.
|
||||||
|
- Spells are active abilities (activated when casted) that apply certain effects on the world state.
|
||||||
|
- Potions, manuals and scrolls are items that temporarily increase or decrease any previously defined element.
|
||||||
|
- Weapons are equipment that must have attack category, damage, condition, value.
|
||||||
|
- Armours are equipment that must have defence category, resistance, condition and value.
|
||||||
|
- Jewelry is type of equipment that can be enchanted and give some effect to party member.
|
||||||
|
- Coins are collectively gathered and can be used for trading, buying and selling items and other equipment.
|
||||||
|
|
||||||
|
## Player
|
||||||
|
|
||||||
|
General:
|
||||||
|
- Player controls party of 6 characters (like in Wizardry), as one group in tactical turn-based combat.
|
||||||
|
- Each party member has health, mana, stamina and experience points, attributes, skills, spells, equipment and inventory.
|
||||||
|
- Collectively, party has coins (currency), equipment and inventory, which can be shared and redistributed.
|
||||||
|
- Movement is done with Keypad and Vim-like-keys, respectively KP1...KP9 and H, J, K, L, Y, U, B, N and fullstop.
|
||||||
|
- All party members are randomly generated, they have randomized attributes, skills, spells and equipment.
|
||||||
|
|
||||||
|
## Enemies
|
||||||
|
|
||||||
|
General:
|
||||||
|
- When an enemy is in view range of player, they'll start following until they enter the battle state.
|
||||||
|
- Enemies always leave random amount of coins, and randomly some item like potion, scroll or manual.
|
||||||
|
- Attack decisions are made by attacking ranged and magical party members (archers and wizards) first, then fighers.
|
||||||
|
|
||||||
|
## Dungeons
|
||||||
|
|
||||||
|
General:
|
||||||
|
- Every dungeon level is 2D matrix (tilemap or gridmap) of random width and height in arbituary limitation.
|
||||||
|
- Procedural generation is up to developer to implement, but level layouts and elements must always be randomly generated.
|
||||||
|
- Maximum width and height limitation is undefined (developer choice), minimum width and height must be respectively 80 and 24.
|
||||||
|
- Elements of dungeon are walls (clip), floors (noclip), decorations (clip/noclip) and special elements (defined below).
|
||||||
|
- Special elements of dungeons execute game state changing arbituary function when 'use' command is given.
|
||||||
|
- Difficulty is tied to number of levels passed, it increments with each level, and generates rarer dungeon elements.
|
||||||
|
- Player can go down to next level, but never return to previous level, no stair-hopping allowed.
|
||||||
|
|
||||||
|
Terminal extension:
|
||||||
|
- Basic VT100 escape codes should be used to output buffers in colours, offset the cursor and change screen size.
|
||||||
|
- Visually distinct colours and styles should be used to express different game elements (defined below).
|
||||||
|
- Terminal should be updated in real-time and include size changes, using curses-like approach to print at terminal size offset.
|
||||||
|
```
|
||||||
|
@ CYAN BLINK - Player (blinking for easier recognition)
|
||||||
|
# GREY REVERSE - Wall (biome can be expressed with colour)
|
||||||
|
. GREY REVERSE - Floor (biome can be expressed with colour)
|
||||||
|
> PINK BOLD - Stairs (go to next level)
|
||||||
|
} PINK BOLD - Portal (go to random noclip tile)
|
||||||
|
a...z|A...Z RED ITALIC - Enemies (weaker lowercase, stronger uppercase)
|
||||||
|
$ WHITE BOLD - Coins (random number)
|
||||||
|
) YELLOW NORMAL - Shooting weapons (bow, crossbow, sling...)
|
||||||
|
| YELLOW BOLD - Piercing weapons (spear, halberd, lance...)
|
||||||
|
! YELLOW BOLD - Slashing weapons (sword, greatsword, dagger...)
|
||||||
|
? YELLOW BOLD - Swinging weapons (axe, mace, flail...)
|
||||||
|
/ YELLOW ITALIC - Magical weapons (staff, wand...)
|
||||||
|
- GREEN NORMAL - General jewelry (ring, amulet, earing...)
|
||||||
|
] GREEN BOLD - General armours (complete set)
|
||||||
|
= BLUE NORMAL - General potions (affect attributes)
|
||||||
|
% BLUE BOLD - General manuals (affect skills)
|
||||||
|
; BLUE ITALIC - General scrolls (affect spells)
|
||||||
|
```
|
||||||
|
|
||||||
|
Graphical extension:
|
||||||
|
- Every tile (2D sprite) should be of same dimensions for pixel art, to form a grid, loaded as a spritesheet or individual sprites.
|
||||||
|
- Optionally, sprite-overdraw can be used to draw on top of player sprite, for example armours and weapon sprites.
|
||||||
|
- Unlike with terminal extension rules, there are no visual rules here, it's up to common sense of the developer to make them.
|
||||||
|
|
||||||
|
## Battles
|
||||||
|
|
||||||
|
- Each party member gets the turn to do one combat action (defined below), then one of the enemies does the same.
|
||||||
|
- Battle is over when all party members or enemies are dead, and battle menu is hidden until next battle.
|
||||||
|
- Combat actions are attack, block, shoot (ranged), evoke (magical), cast (spell), quaff (potion), read (scroll) and wait (skip).
|
||||||
|
- Attack, block and shoot use stamina points, which can be recovered on wait or when party is resting after the battle.
|
||||||
|
- Evoke and cast use mana points, which can be recovered slowly but automatically after the battle.
|
||||||
|
218
xungeons.c
Normal file
218
xungeons.c
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#define level_width (256)
|
||||||
|
#define level_height (256)
|
||||||
|
#define members ( 6)
|
||||||
|
#define items ( 36)
|
||||||
|
|
||||||
|
enum { normal = '0', bold = '1', italic = '2', blink = '5', reverse = '7' };
|
||||||
|
|
||||||
|
enum { grey = '0', red = '1', green = '2', yellow = '3', blue = '4', pink = '5', cyan = '6', white = '7' };
|
||||||
|
|
||||||
|
enum { cut = 1, blunt = 2, pierce = 4, range = 8, magic = 16 };
|
||||||
|
|
||||||
|
enum { health, mana, stamina, experience, points };
|
||||||
|
|
||||||
|
enum { strength, dexterity, agility, intelligence, attributes };
|
||||||
|
|
||||||
|
enum { fighting, blocking, shooting, spellcasting, evoking, trading, healing, lockpicking, skills };
|
||||||
|
|
||||||
|
enum { warrior, mage, thief, priest, archer, alchemist, warlock, merchant, classes };
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
char * name, damage, condition, value, spell, type, style, colour, symbol;
|
||||||
|
} const weapons [] = {
|
||||||
|
{ "Bone Dagger", 1, 11, 5, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Iron Dagger", 1, 19, 7, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Steel Dagger", 1, 23, 11, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Silver Dagger", 2, 29, 13, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Bone Sword", 3, 23, 11, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Iron Sword", 5, 31, 29, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Steel Sword", 5, 37, 31, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Silver Sword", 7, 43, 37, 0, cut, bold, yellow, '!' },
|
||||||
|
{ "Bone Greatsword", 7, 41, 23, 0, blunt | cut, bold, yellow, '!' },
|
||||||
|
{ "Iron Greatsword", 11, 47, 71, 0, blunt | cut, bold, yellow, '!' },
|
||||||
|
{ "Steel Greatsword", 13, 53, 73, 0, blunt | cut, bold, yellow, '!' },
|
||||||
|
{ "Silver Greatsword", 17, 59, 79, 0, blunt | cut, bold, yellow, '!' }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
char * name, resistance, condition, value, spell, type, style, colour, symbol;
|
||||||
|
} const armours [] = {
|
||||||
|
{ "Wanderer Robe", 0, 11, 11, 0, cut, bold, green, ']' },
|
||||||
|
{ "Priest Robe", 0, 13, 7, 0, cut, bold, green, ']' },
|
||||||
|
{ "Commoner Clothes", 0, 5, 5, 0, blunt, bold, green, ']' },
|
||||||
|
{ "Nobleman Clothes", 0, 7, 31, 0, magic, bold, green, ']' },
|
||||||
|
{ "Assassin Clothes", 1, 11, 13, 0, blunt, bold, green, ']' },
|
||||||
|
{ "Adventurer Clothes", 1, 13, 17, 0, cut, bold, green, ']' },
|
||||||
|
{ "Thief Clothes", 1, 11, 11, 0, blunt, bold, green, ']' },
|
||||||
|
{ "Merchant Clothes", 1, 11, 13, 0, pierce, bold, green, ']' },
|
||||||
|
{ "Fur Armour", 2, 19, 23, 0, blunt | pierce, bold, green, ']' },
|
||||||
|
{ "Leather Armour", 2, 23, 31, 0, blunt, bold, green, ']' },
|
||||||
|
{ "Chainmail Armour", 3, 31, 41, 0, blunt | cut, bold, green, ']' },
|
||||||
|
{ "Mithril Armour", 7, 83, 97, 0, blunt | cut | pierce, bold, green, ']' },
|
||||||
|
{ "Bone Armour", 3, 29, 37, 0, blunt | pierce, bold, green, ']' },
|
||||||
|
{ "Iron Armour", 5, 53, 61, 0, blunt | cut, bold, green, ']' },
|
||||||
|
{ "Steel Armour", 5, 67, 71, 0, blunt | cut, bold, green, ']' },
|
||||||
|
{ "Silver Armour", 5, 71, 89, 0, blunt | cut | pierce, bold, green, ']' }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
char x, y, coins, inventory [items], weapon [members], armour [members], point [members] [points], attribute [members] [attributes], skill [members], class [members];
|
||||||
|
} player;
|
||||||
|
|
||||||
|
static struct termios old_terminal;
|
||||||
|
static struct termios new_terminal;
|
||||||
|
|
||||||
|
static int signal;
|
||||||
|
static int screen_width;
|
||||||
|
static int screen_height;
|
||||||
|
static char * screen;
|
||||||
|
|
||||||
|
static char * map;
|
||||||
|
|
||||||
|
static void screen_add (char style, char colour, char symbol, int x, int y) {
|
||||||
|
char format [] = "\033[ ;3 m \033[0m";
|
||||||
|
|
||||||
|
format [2] = style;
|
||||||
|
format [5] = colour;
|
||||||
|
format [7] = symbol;
|
||||||
|
|
||||||
|
strncpy (& screen [(y * screen_width + x) * 12 + 3], format, sizeof (format) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_screen (void) {
|
||||||
|
struct winsize screen_dimension;
|
||||||
|
|
||||||
|
ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension);
|
||||||
|
|
||||||
|
screen_width = (int) screen_dimension.ws_col;
|
||||||
|
screen_height = (int) screen_dimension.ws_row;
|
||||||
|
|
||||||
|
tcgetattr (STDIN_FILENO, & old_terminal);
|
||||||
|
|
||||||
|
new_terminal = old_terminal;
|
||||||
|
|
||||||
|
new_terminal.c_cc [VMIN] = (unsigned char) 0;
|
||||||
|
new_terminal.c_cc [VTIME] = (unsigned char) 1;
|
||||||
|
|
||||||
|
new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
new_terminal.c_oflag &= (unsigned int) ~(OPOST);
|
||||||
|
new_terminal.c_cflag |= (unsigned int) (CS8);
|
||||||
|
new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
|
||||||
|
tcsetattr (STDIN_FILENO, TCSAFLUSH, & new_terminal);
|
||||||
|
|
||||||
|
screen = calloc ((unsigned long int) (12 * screen_width * screen_height + 4), sizeof (* screen));
|
||||||
|
|
||||||
|
strcpy (screen, "\033[H");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deinitialize_screen (void) {
|
||||||
|
free (screen);
|
||||||
|
|
||||||
|
write (STDOUT_FILENO, "\033[2J\033[H", 7);
|
||||||
|
|
||||||
|
tcsetattr (STDIN_FILENO, TCSAFLUSH, & old_terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void synchronize_screen (void) {
|
||||||
|
int new_screen_width, new_screen_height;
|
||||||
|
|
||||||
|
struct winsize screen_dimension;
|
||||||
|
|
||||||
|
ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension);
|
||||||
|
|
||||||
|
new_screen_width = (int) screen_dimension.ws_col;
|
||||||
|
new_screen_height = (int) screen_dimension.ws_row;
|
||||||
|
|
||||||
|
if ((screen_width != new_screen_width) || (screen_height != new_screen_height)) {
|
||||||
|
screen_width = new_screen_width;
|
||||||
|
screen_height = new_screen_height;
|
||||||
|
free (screen);
|
||||||
|
screen = calloc ((unsigned long int) (12 * screen_width * screen_height + 4), sizeof (* screen));
|
||||||
|
}
|
||||||
|
|
||||||
|
write (STDOUT_FILENO, screen, (unsigned long int) (12 * screen_width * screen_height + 4));
|
||||||
|
|
||||||
|
signal = 0;
|
||||||
|
|
||||||
|
read (STDIN_FILENO, & signal, sizeof (signal));
|
||||||
|
|
||||||
|
switch (signal) {
|
||||||
|
case 'k': { player.y -= 1; } break;
|
||||||
|
case 'j': { player.y += 1; } break;
|
||||||
|
case 'h': { player.x -= 1; } break;
|
||||||
|
case 'l': { player.x += 1; } break;
|
||||||
|
case 'y': { player.x -= 1; player.y -= 1; } break;
|
||||||
|
case 'u': { player.x += 1; player.y -= 1; } break;
|
||||||
|
case 'b': { player.x -= 1; player.y += 1; } break;
|
||||||
|
case 'n': { player.x += 1; player.y += 1; } break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.x = (player.x > level_width) ? (level_width - 1) : ((player.x < 0) ? 0 : player.x);
|
||||||
|
player.y = (player.y > level_height) ? (level_height - 1) : ((player.y < 0) ? 0 : player.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_party (void) {
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
player.x = 0;
|
||||||
|
player.y = 0;
|
||||||
|
player.coins = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < items; ++i) {
|
||||||
|
player.inventory [i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < members; ++i) {
|
||||||
|
player.weapon [i] = rand () % 3;
|
||||||
|
player.armour [i] = rand () % 3;
|
||||||
|
for (j = 0; j < points; ++j) {
|
||||||
|
player.point [i] [j] = rand () % 12 + 12;
|
||||||
|
}
|
||||||
|
for (j = 0; j < attributes; ++j) {
|
||||||
|
player.attribute [i] [j] = rand () % 6 + 1;
|
||||||
|
}
|
||||||
|
player.skill [i] = rand () % skills;
|
||||||
|
player.class [i] = rand () % classes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_level (void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (void) {
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
initialize_screen ();
|
||||||
|
|
||||||
|
generate_party ();
|
||||||
|
generate_level ();
|
||||||
|
|
||||||
|
while (signal != 'Q') {
|
||||||
|
for (y = 0; y < screen_height; ++y) {
|
||||||
|
for (x = 0; x < screen_width - 1; ++x) {
|
||||||
|
screen_add (bold, grey, '.', x, y);
|
||||||
|
}
|
||||||
|
screen_add (bold, red, '#', x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_add (bold, cyan, '@', player.x, player.y);
|
||||||
|
|
||||||
|
synchronize_screen ();
|
||||||
|
}
|
||||||
|
|
||||||
|
deinitialize_screen ();
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user