I started to make a C++ RPG game just for fun. It does have quests but it isn't just one long adventure that lasts like 30 minutes. I would like to know what I did well on and things on which I can do better on. This is my normal coding so I would like to know things I could work on.
// Warscape
// (c) 2017 by, Handge
#include <iostream>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <ctime>
#include <tuple>
using namespace std;
// Clears the screen
void clear() {
cout << string(50, '\n');
}
// Draw the ascii art
void draw(string type) {
if (type == "Lion") {
cout << R"(
======================================================================
,w.
,YWMMw ,M ,
_.---.._ __..---._.'MMMMMw,wMWmW,
_.-"" """ YP"WMMMMMMMMMb,
.-' __.' .' MMMMW^WMMMM;
_, .'.-'"; `, /` .--"" :MMM[==MWMW^;
,mM^" ,-'.' / ; ; / , MMMMb_wMW" @\
,MM:. .'.-' .' ; `\ ; `, MMMMMMMW `"=./`-,
WMMm__,-'.' / _.\ F"""-+,, ;_,_.dMMMMMMMM[,_ / `=_}
"^MP__.-' ,-' _.--"" `-, ; \ ; ;MMMMMMMMMMW^``; __|
/ .' ; ; ) )`{ \ `"^W^`, \ :
/ .' / ( .' / Ww._ `. `"
/ Y, `, `-,=,_{ ; MMMP`""-, `-._.-,
(--, ) `,_ / `) \/"") ^" `-, -;"\:
`""" `""" `"' `---"
======================================================================
)" << endl;
} else if (type == "Boar") {
cout << R"(
============================================
_,-""""-..__
|`,-'_. ` ` `` `--'""".
; ,' | `` ` ` ` ``` `.
,-' ..-' ` ` `` ` `` ` ` |==.
,' ^ ` ` `` ` ` `. ; \
`}_,-^- _ . ` \ ` ` __ ` ; #
`"---"' `-`. ` \---""`.`. `;
\\` ; ; `. `,
||`; / / | |
//_;` ,_;' ,_;"
============================================
)" << endl;
} else if (type == "Spider") {
cout << R"(
================================
/\ .-"""-. /\
//\\/ ,,, \//\\
|/\| ,;;;;;, |/\|
//\\\;-"""-;///\\
// \/ . \/ \\
(| ,-_| \ | / |_-, |)
//`__\.-.-./__`\\
// /.-(() ())-.\ \\
(\ |) '---' (| /)
` (| |) `
\) (/
================================
)" << endl;
} else if (type == "Wolf") {
cout << R"(
=====================================
, ,
|\---/|
/ , , |
__.-'| / \ /
__ ___.-' ._O|
.-' ' : _/
/ , . . |
: ; : : _/
| | .' __: /
| : /'----'| \ |
\ |\ | | /| |
'.'| / || \ |
| /|.' '.l \\_
|| || '-'
'-''-'
=====================================
)" << endl;
} else if (type == "Elephant") {
cout << R"(
=====================================================
___.-~"~-._ __....__
.' ` \ ~"~ ``-.
/` _ ) `\ `\
/` a) / | `\
:` / | \
<`-._|` .-. ( / . `;\\
`-. `--'_.'-.;\___/' . . | \\
_ /:--` | / / .' \\
("\ /`/ | ' ' / :`;
`\'\_/`/ .\ /`~`=-.: / ``
`._.' /`\ | `\ /(
/ /\ | `Y / \
J / Y | | /`\ \
/ | | | | | | |
"---" /___| /___| /__|
=====================================================
)" << endl;
} else if (type == "Slime") {
cout << R"(
==================================
, - ~ ~ ~ - ,
, ' ' ,
, ! ! ,
, ,
, @@ @@ ,
, ! @@ @@ ,
, ! ,
, ,
, ! ,
, ! , '
' - , _ _ _ , '
==================================
)" << endl;
}
}
// Displays the abilities
pair<string, int> getability(string pclass, int mana, int inte, int level) {
string input;
string ability;
int cost;
cout << "Choose an ability" << endl;
if (pclass == "Champion") { // Champion Section
cost = inte*(level);
cout << "[1] Cleaving Strike [" << cost << " mana]" << endl;
cout << "[2] Melting Thrust [" << cost << " mana]" << endl;
cout << "[3] Critical Bash [" << cost << " mana]" << endl;
cost = inte*(level+1);
cout << "[4] Purify [" << cost << " mana]" << endl;
cost = inte*(level);
cin >> input;
if (input == "1") {
ability = "cleaving strike";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "2") {
ability = "melting thrust";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "3") {
ability = "critical bash";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "4") {
ability = "purify";
if (mana >= cost) {
cost = inte*(level+1);
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else {
ability = "none";
return {ability, mana};
}
} else if (pclass == "Necromancer") { // Necromancer Section
cost = inte*(level);
cout << "[1] Shadow Strike [" << cost << " mana]" << endl;
cout << "[2] Cripple [" << cost << " mana]" << endl;
cout << "[3] Mutilate [" << cost << " mana]" << endl;
cost = inte*(level+2);
cout << "[4] Life Tap [" << cost << " mana]" << endl;
cost = inte*(level);
cin >> input;
if (input == "1") {
ability = "shadow strike";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "2") {
ability = "cripple";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "3") {
ability = "mutilate";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "4") {
ability = "life tap";
if (mana >= cost) {
cost = inte*(level+2);
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else {
ability = "none";
return {ability, mana};
}
} else if (pclass == "Assassin") { // Assassin Section
cost = inte*(level);
cout << "[1] Back Stab [" << cost << " mana]" << endl;
cout << "[2] Headcrack [" << cost << " mana]" << endl;
cout << "[3] Poison [" << cost << " mana]" << endl;
cost = inte*10;
cout << "[4] Assassinate [" << cost << " mana]" << endl;
cost = inte*(level);
cin >> input;
if (input == "1") {
ability = "back stab";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "2") {
ability = "headcrack";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "3") {
ability = "poison";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "4") {
ability = "assassinate";
if (mana >= cost) {
cost = inte*10;
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else {
ability = "none";
return {ability, mana};
}
}else if (pclass == "Cleric") { // Cleric Section
cost = inte*(level);
cout << "[1] Smite [" << cost << " mana]" << endl;
cout << "[2] Enflame [" << cost << " mana]" << endl;
cout << "[3] Atonement [" << cost << " mana]" << endl;
cout << "[4] Flash Heal [" << cost << " mana]" << endl;
cin >> input;
if (input == "1") {
ability = "smite";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "2") {
ability = "enflame";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "3") {
ability = "atonement";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else if (input == "4") {
ability = "flash heal";
if (mana >= cost) {
mana = mana - cost;
return {ability, mana};
} else {
ability = "none";
return {ability, mana};
}
} else {
ability = "none";
return {ability, mana};
}
} else {
ability = "none";
return {ability, mana};
}
}
int main()
{
srand((unsigned)time(0));
// Strings
string code; // Game save code
string name; // Player name
string etype; // Enemy Type (spider, giant)
string pclass; // Player class
string action; // Player ability / action
// Dungeons
string status = "arena"; // Status if in Dungeon or just Arena
int ckills; // Current amount of kills in Dungeon
int rkills; // Required amount of kills to complete a Dungeon
// Armor / Weapons
int bhp = 0; // Bonus hp
double bphp = 0; // Bonus precantage hp
double goldb = 0; // Bonus Gold
int slimychestplate = 0; // Slimy Chestplate (TRUE or FALSE)
int slimyhelmet = 0; // Slimy Helmet (TRUE or FALSE)
int oozinglegplates = 0; // Oozing Legplates
int oozingboots = 0; // Oozing Boots
string head = "Open"; // Whats on the head
string chest = "Open"; // Whats on the chest
string legs = "Open"; // Whats on the legs
string feet = "Open"; // Whats on the feet
// Player Variables
int piclass = 0;
int level = 1; // Level
int xp = 0; // Current XP
int rxp; // Reward XP
int xpb = 50; // Increases each level, adds to max hp to increase it
int xpl = 25; // XP Required till next level
int stre = 5; // Strength
int inte = 5; // Intelligence
int dext = 5; // Dexterity
int skill = 0; // Skillpoint points (used to increase Strength, Intelligence, Dexterity)
int dmg; // Damage
int hp = stre * 20; // Current hp
int mhp = stre * 20; // Max hp
int shp = stre * 20; // hp used to set enemy hp
int mana = inte * 10; // Mana
int mmana = inte * 10; // Max Mana
// Inventory / Shop
int hpotion = 1; // Healing Potions
int price; // Price of item(s)
// Resources
int gold = 25; // Gold
int rgold; // Reward Gold
// Saving / Loading Strings
int x = 0;
int y = 0;
string sslimychestplate = std::to_string(slimychestplate);
string sslimyhelmet = std::to_string(slimyhelmet);
string soozinglegplates = std::to_string(oozinglegplates);
string soozingboots = std::to_string(oozingboots);
string spiclass = std::to_string(piclass);
string slevel = std::to_string(level);
string sxp = std::to_string(xp);
string sxpb = std::to_string(xpb);
string sxpl = std::to_string(xpl);
string sstre = std::to_string(stre);
string sinte = std::to_string(inte);
string sdext = std::to_string(dext);
string shpotion = std::to_string(hpotion);
string sgold = std::to_string(gold);
string sskill = std::to_string(skill);
// Questhall
int quest = 0; // The quest
int questr = 0; // The quest requirements (6 boars slain)
int questc = 0; // The current position on the requirements (3/6 boars slain)
int questreward = 0; // The quest's reward
string questmob; // Enemy that must be slain to complete the quest
string questdisplay; // Displays the quest
int qxpr; // Quest xp reward
int qgoldr; // Quest gold reward
// Monster Variables
int elevel; // Enemy level
int ehp; // Enemy hp
int mehp; // Max Enemy hp
int edmg; // Enemy dmg
string eability; // Enemy Ability
// Other Variables
int random; // Random Integer #1
int random2; // Random Integer #2
// Weapons
// Inputs
string input; // Standard Input
// Setup
neworload:
clear();
cout << "Hello there please choose an option" << endl;
cout << "1- New Game" << endl;
cout << "2- Load Game" << endl;
cin >> input;
if (input == "1") {
goto setup;
} else if (input == "2") {
goto loadgame;
} else {
goto neworload;
}
setup:
clear();
cout << "Hello there, what is your name?" << endl;
cin >> input;
name = input;
cout << "Ahh, is it " << input << "? [y/n]" << endl;
cin >> input;
if (input == "n") {
clear();
goto setup;
}
// Class Setup
csetup:
cout << endl;
cout << "Choose your Class" << endl;
cout << "=================" << endl;
cout << "[1] Champion" << endl;
cout << "[2] Necromancer" << endl;
cout << "[3] Assassin" << endl;
cout << "[4] Cleric" << endl;
cin >> input;
if (input == "1") {
pclass = "Champion";
piclass = 1;
} else if (input == "2") {
pclass = "Necromancer";
piclass = 2;
} else if (input == "3") {
pclass = "Assassin";
piclass = 3;
} else if (input == "4") {
pclass = "Cleric";
piclass =4;
}
goto menue;
// Main Menue
menue:
clear();
if (pclass == "Assassin") {
goldb = 1.25;
} else {
goldb = 1;
}
cout << R"(
___ ___ _____ _ _ _ _
| \/ || ___| \ | | | | |
| . . || |__ | \| | | | |
| |\/| || __|| . ` | | | |
| | | || |___| |\ | |_| |
\_| |_/\____/\_| \_/\___/
)" << endl;
cout << pclass << " " << name << endl;
cout << "[1] Traveller's Encounter" << endl;
cout << "[2] Inventory" << endl;
cout << "[3] Rest (Returns you to full health/mana)" << endl;
cout << "[4] Assign Skillpoints [" << skill << " available]" << endl;
cout << "[5] Shop" << endl;
cout << "[6] Questhall" << endl;
cout << "[7] Dungeons" << endl;
cout << "[98] Save Game" << endl;
cout << "[99] Exit" << endl;
cin >> input;
if (input == "1") {
goto sarena;
} else if (input == "99") {
goto leave;
} else if (input == "2") {
goto inventory;
} else if (input == "3") {
mhp = stre * 20;
mmana = inte * 10;
hp = mhp;
hp = hp * (bphp+1);
hp = hp + bhp;
mana = mmana;
goto menue;
} else if (input == "4") {
goto askill;
} else if (input == "5") {
goto shop;
} else if (input == "6") {
goto questhall;
} else if (input == "7") {
goto dungeonmenue;
} else if (input == "98") {
goto savegame;
}
goto menue;
// Setting Up Enemys
sarena:
mmana = inte * 10;
shp = stre * 20;
mhp = stre * 20;
random = rand()%5+1;
switch (random) {
case 1:
etype = "Lion";
random2 = rand()%2+1;
eability = "gnaw";
if (random2 == 1) {
ehp = shp + rand()%(level*5);
} else {
ehp = shp - rand()%(level*5);
}
mehp = ehp;
break;
case 2:
etype = "Boar";
random2 = rand()%2+1;
eability = "goar";
if (random2 == 1) {
ehp = shp + rand()%(level*3);
} else {
ehp = shp - rand()%(level*3);
}
mehp = ehp;
break;
case 3:
etype = "Spider";
random2 = rand()%2+1;
eability = "webspin";
elevel = rand()%(level+3)+1;
if (random2 == 1) {
ehp = shp + rand()%(level*2);
} else {
ehp = shp - rand()%(level*2);
}
mehp = ehp;
break;
case 4:
etype = "Wolf";
random2 = rand()%2+1;
eability = "growl";
elevel = rand()%(level+3)+1;
if (random2 == 1) {
ehp = shp + rand()%(level*4);
} else {
ehp = shp - rand()%(level*4);
}
mehp = ehp;
break;
case 5:
etype = "Elephant";
random2 = rand()%2+1;
eability = "stomp";
if (random2 == 1) {
ehp = shp + rand()%(level*6);
} else {
ehp = shp - rand()%(level*3);
}
mehp = ehp;
break;
}
elevel = rand()%(level+3)+1;
if ((elevel < (level-3)) || (elevel > (level+3))) {
elevel = level;
}
goto aarena;
// Arena Main
aarena:
clear();
draw(etype);
cout << "[" << etype << " " << elevel << "] >>> " << ehp << "/" << mehp << endl;
cout << "[" << name << " " << level << "] >>> " << hp << "/" << mhp << endl;
cout << "-Mana- >>> " << mana << "/" << mmana << endl;
cout << "[1] Attack" << endl;
cout << "[2] Do ability" << endl;
cout << "[3] Inventory" << endl;
cout << "[99] Flee" << endl;
cin >> input;
if (input == "1") {
dmg = (stre+inte) + rand()%(dext*2);
ehp = ehp - dmg;
// If enemy ehp < 0 then
if (ehp <= 0) {
clear();
cout << "[*] You killed the " << etype << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
goto rarena;
}
random2 = rand()%2+1;
edmg = dmg;
// Setting up enemy damage
if (random2 == 1) {
edmg = edmg + rand()%(dext*2);
} else {
edmg = edmg - rand()%(dext*2);
}
hp = hp - edmg;
// If player hp < 0 then
if (hp <= 0) {
clear();
cout << "[*] You Died" << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
if (status != "arena") {
status = "arena";
}
goto menue;
}
goto aarena;
} else if (input == "99") {
goto menue;
} else if (input == "2") {
goto darena;
} else if (input == "3") {
goto iarena;
} else {
goto aarena;
}
// Using items from Inventory
iarena:
clear();
cout << "Inventory" << endl;
cout << "[1] Health Potion, " << hpotion << " (Heals you to max health)" << endl;
cout << "[99] Exit" << endl;
cin >> input;
if (input == "1") {
if (hpotion > 0) {
hpotion = hpotion - 1;
hp = mhp;
}
} else if (input == "99") {
goto aarena;
}
goto iarena;
// Picking spells and doing damage
darena:
clear();
tie(action, mana) = getability(pclass, mana, inte, level);
// Champion spells
if (action == "cleaving strike") {
dmg = (stre+inte+dext) + rand()%(dext*2);
} else if (action == "melting thrust") {
dmg = (stre+inte+dext) + rand()%(stre*2);
} else if (action == "critical bash") {
dmg = (stre+inte+dext) + rand()%(inte*2);
} else if (action == "purify") {
hp = hp + inte*(level)+(rand()%inte);
if (hp > mhp) {
hp = mhp;
}
goto aarena;
} else if (action == "none") {
goto aarena;
} else if (action == "shadow strike") {
dmg = (stre+inte+dext) + rand()%(dext*2);
} else if (action == "cripple") {
dmg = (stre+inte+dext) + rand()%(stre*2);
} else if (action == "mutilate") {
dmg = (stre+inte+dext) + rand()%(inte*2);
} else if (action == "life tap") {
dmg = (stre+inte) + rand()%(inte);
hp = hp + dmg;
if (hp > mhp) {
hp = mhp;
}
ehp = ehp - dmg;
// If enemy ehp < 0 then
if (ehp <= 0) {
clear();
cout << "[*] You killed the " << etype << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
goto rarena;
}
goto aarena;
} else if (action == "back stab") {
dmg = (stre+inte+dext) + rand()%(dext*2);
} else if (action == "headcrack") {
dmg = (stre+inte+dext) + rand()%(stre*2);
} else if (action == "poison") {
dmg = (stre+inte+dext) + rand()%(inte*2);
} else if (action == "assassinate") {
dmg = (stre+inte+dext) + rand()%((inte+stre)*3);
ehp = ehp - dmg;
// If enemy ehp < 0 then
if (ehp <= 0) {
clear();
cout << "[*] You killed the " << etype << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
goto rarena;
}
goto aarena;
} else if (action == "smite") {
dmg = (stre+inte+dext) + rand()%(dext);
} else if (action == "enflame") {
dmg = (stre+inte+dext) + rand()%(stre);
} else if (action == "atonement") {
dmg = (stre+inte+dext) + rand()%(inte);
} else if (action == "flash heal") {
hp = hp + ((stre * level) + (rand()%(inte*2)));
if (hp > mhp) {
hp = mhp;
}
goto aarena;
}
// Doing the damage (Enemy)
ehp = ehp - dmg;
// If enemy ehp < 0 then
if (ehp <= 0) {
clear();
cout << "[*] You killed the " << etype << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
goto rarena;
}
random2 = rand()%2+1;
dmg = (stre+inte) + rand()%(dext*2);
edmg = dmg;
// Setting up enemy damage
if (random2 == 1) {
edmg = edmg + rand()%(dext*2);
} else {
edmg = edmg - rand()%(dext*2);
}
hp = hp - edmg;
// If player hp < 0 then
if (hp <= 0) {
clear();
cout << "[*] You Died" << endl;
cout << "Type [1] to continue" << endl;
cin >> input;
if (status != "arena") {
status = "arena";
}
goto menue;
}
goto aarena;
// Here you get arena awards
rarena:
clear();
rxp = rand()%(level*2)+5;
rgold = rand()%(level*5);
rgold = rgold * goldb;
cout << R"(
*******************************************************************************
| | | |
_________|________________.=""_;=.______________|_____________________|_______
| | ,-"_,="" `"=.| |
|___________________|__"=._o`"-._ `"=.______________|___________________
| `"=._o`"=._ _`"=._ |
_________|_____________________:=._o "=._."_.-="'"=.__________________|_______
| | __.--" , ; `"=._o." ,-"""-._ ". |
|___________________|_._" ,. .` ` `` , `"-._"-._ ". '__|___________________
| |o`"=._` , "` `; .". , "-._"-._; ; |
_________|___________| ;`-.o`"=._; ." ` '`."\` . "-._ /_______________|_______
| | |o; `"-.o`"=._`` '` " ,__.--o; |
|___________________|_| ; (#) `-.o `"=.`_.--"_o.-; ;___|___________________
____/______/______/___|o;._ " `".o|o_.--" ;o;____/______/______/____
/______/______/______/_"=._o--._ ; | ; ; ;/______/______/______/_
____/______/______/______/__"=._o--._ ;o|o; _._;o;____/______/______/____
/______/______/______/______/____"=._o._; | ;_.--"o.--"_/______/______/______/_
____/______/______/______/______/_____"=.o|o_.--""___/______/______/______/____
/______/______/______/______/______/______/______/______/______/______/______/_
*******************************************************************************
)" << endl; // That chest looks amazing
if (status == "oozeworks") {
goto oozeworksr;
} else {
goto rrarena;
}
rrarena:
cout << "You recieved " << rxp << " xp" << endl;
cout << "You recieved " << rgold << " gold" << endl;
xp = xp + rxp;
gold = gold + rgold;
if (xp >= xpl) {
level = level + 1;
xpl = xpl + xpb;
xpb = xpb + 50;
skill = skill + 5;
cout << "You have leveled up to level " << level << endl;
}
cout << "Type [1] to continue to the menue" << endl;
if (etype == questmob) {
questc = questc + 1;
}
cin >> input;
goto menue;
// Inventory
inventory:
clear();
cout << R"(
___
)_( _
| | [_ ]
.-'-'-. _ .-'. '-.
/-::_..-\ _[_]_ /:;/ _.-'\
)_ _( /_ _\ [-] |:._ .-|
|;:: | )_``'_( .-'-'-. (-) |:._ |
|;:: | |;: | :-...-: .-'-'-. |:._ |
|;:: | |;: | |;: | |-...-| |:._ |
|;::-.._| |;:.._| |;:.._| |;:.._| |:._ |
`-.._..-' `-...-' `-...-' `-...-' `-.____.-'
)" << endl;
levelalert:
if (xp >= xpl) {
level = level + 1;
xpl = xpl + xpb;
xpb = xpb + 50;
skill = skill + 5;
goto levelalert;
} else {
goto inventory2;
}
inventory2:
cout << "[Level] >>> " << level << endl;
cout << "[XP] >>> " << xp << "/" << xpl << endl;
cout << "[Gold] >>> " << gold << endl;
cout << "[Healing Potions] >>> " << hpotion << endl;
cout << "==================================" << endl;
cout << "Head- " << head << endl;
cout << "Chest- " << chest << endl;
cout << "Legs- " << legs << endl;
cout << "Feet- " << feet << endl;
cout << "==================================" << endl;
cout << "1- Armory" << endl;
cout << "99- Exit" << endl;
cin >> input;
if (input == "99") {
goto menue;
} else if (input == "1") {
goto armory;
}
goto inventory;
armory:
clear();
cout << R"(
, A {}
/ ,円 | , .--.
| =|= > /.--.\
\ /` | ` |====|
` | |`::`|
| .-;`\..../`;_.-^-._
/\\/ / |...::..|` : `|
|:'\ | /'''::''| .:. |
\ /\;-,/\ :: |..:::::..|
|\ <` > >._::_.| ':::::' |
| `""` / ^^ | ':' |
)" << endl;
cout << "1- Slimy Chestplate >>> [10% HP Increase] ";
if (slimychestplate == 1) {
cout << ">>> [Owned] ";
if (chest == "slimychestplate") {
cout << ">>> [Equiped]" << endl;
} else {
cout << endl;
}
} else {
cout << endl;
}
cout << "2- Slimy Helmet >>> [5% HP Increase] ";
if (slimyhelmet == 1) {
cout << ">>> [Owned] ";
if (head == "slimyhelmet") {
cout << ">>> [Equiped]" << endl;
} else {
cout << endl;
}
} else {
cout << endl;
}
cout << "3- Oozing Legplates >>> [15% HP Increase] ";
if (oozinglegplates == 1) {
cout << ">>> [Owned] ";
if (legs == "oozinglegplates") {
cout << ">>> [Equiped]" << endl;
} else {
cout << endl;
}
} else {
cout << endl;
}
cout << "4- Oozing Boots >>> [10% HP Increase] ";
if (oozingboots == 1) {
cout << ">>> [Owned] ";
if (feet == "oozingboots") {
cout << ">>> [Equiped]" << endl;
} else {
cout << endl;
}
} else {
cout << endl;
}
cout << "99- Back" << endl;
cin >> input;
if (input == "99") {
goto armor2;
} else if (input == "1") {
if (chest == "slimychestplate") {
chest = "Open";
goto armory;
} else if (slimychestplate == 1) {
chest = "slimychestplate";
goto armory;
} else {
goto armory;
}
} else if (input == "2") {
if (head == "slimyhelmet") {
head = "Open";
goto armory;
} else if (slimyhelmet == 1) {
head = "slimyhelmet";
goto armory;
} else {
goto armory;
}
} else if (input == "3") {
if (legs == "oozinglegplates") {
legs = "Open";
goto armory;
} else if (oozinglegplates == 1) {
legs = "oozinglegplates";
goto armory;
} else {
goto armory;
}
} else if (input == "4") {
if (feet == "oozingboots") {
feet = "Open";
goto armory;
} else if (oozingboots == 1) {
feet = "oozingboots";
goto armory;
} else {
goto armory;
}
}
armor2:
bhp = 0;
bphp = 0;
// Head
if (head == "slimyhelmet") {
bphp = bphp + .10;
}
// Chest
if (chest == "slimychestplate") {
bphp = bphp + .05;
}
// Legs
if (legs == "oozinglegplates") {
bphp = bphp + .15;
}
// Feet
if (feet == "oozingboots") {
bphp = bphp + .10;
}
goto inventory;
askill:
clear();
cout << R"(
.__=\__ .__==__,
jf' ~~=,円 _=/~' `,円
._jZ' `\q, /=~ `\__
j5(/ `\./ V\,円
.Z))' _____ | .____, \)/\
j5(K=~~ ~~~~\=_, | _/=~~~~' `~~+K\,円
.Z)\/ `~=L | _=/~ t\ZL
j5(_/.__/===========\__ ~q |j/ .__============___/\J(N,
4L#XXXL_________________XGm, \P .mXL_________________JXXXW8L
~~~~~~~~~~~~~~~~~~~~~~~~~YKWmmWmmW@~~~~~~~~~~~~~~~~~~~~~~~~~~
)" << endl; // Bedtime stories?
cout << "Available Skillpoints [" << skill << "]" << endl;
cout << "1- Strength [" << stre << "]" << endl;
cout << "2- Intelligence [" << inte << "]" << endl;
cout << "3- Dexterity [" << dext << "]" << endl;
cout << "99- Exit" << endl;
cin >> input;
if (skill > 0) {
if (input == "1") {
stre = stre + 1;
skill = skill - 1;
} else if (input == "2") {
inte = inte + 1;
skill = skill - 1;
} else if (input == "3") {
dext = dext + 1;
skill = skill - 1;
} else if (input == "99") {
goto menue;
}
} else {
goto menue;
}
goto askill;
shop:
clear();
cout << R"(
____
_ |---|| _
||__________|---||___________||
/_ _ _ _ _ _ |:._|'_ _ _ _ _ _ _\`.
/_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\:`.
/_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\::`.
/:.___________________________________\:::`-._
_.-'_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _`::::::`-.._
_.-' _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ `:::::::::`-._
,'_:._________________________________________________`:_.::::-';`
`.'/ || |:::::`.'/::::::::`.'/::::::::`.'/::::::|.`.'/.| :|
|| || |::::::||::::::::::||::::::::::||:::::::|..||..| ||
|| || | __ || :: ___ || :: __ || :: |..||;|| ||
|| || | |::| || :: |:::| || :: |::| || :: |.|||:||_____||__
|| || | |::| || :: |:::| || :: |::| || :: |.|||:||_|_|_||,(
||_.|| | |::| || :: |:::| || :: |::| || :: |.'||..| _||,|
.-'::_.:'.:-.--.-::--.-:.--:-::--.--.--.-::--.--.-:.-::,'.--.'_|| |
);||_|__||_|__|_||__|_||::|_||__|__|__|_||__|__|_|;-'|__|_(,' || '-
|||| || |. . . ||. . . . . ||. . . . . ||. . . .|::||;''|| ||:'
||||.; _|._._._||._._._._._||._._._._._||._._._.|:'||,, ||,,
''''' ''- ''- ''- ''' '''
)" << endl;
price = level*5;
cout << "[Gold] >>> " << gold << endl;
cout << "1- Health Potion (" << price << " gold)" << endl;
cout << "99- Exit" << endl;
cin >> input;
if (input == "1") {
price = level*5;
if (gold >= price) {
hpotion = hpotion + 1;
gold = gold - price;
goto shop;
} else {
goto menue;
}
} else if (input == "99") {
goto menue;
}
goto shop;
// Receive / "Turn" in quests
questhall:
clear();
cout << R"(
_______________
()==( (@==()
'______________'|
| |
| |
__)_____________|
()==( (@==()
'--------------'
)" << endl;
cout << "Current Quest >>> ";
if (quest == 0) {
quest = rand()%5+1;
switch (quest) {
case 1:
questc = 0;
questr = 6;
questmob = "Boar";
questreward = rand()%4+1;
questdisplay = "Slay Boars";
break;
case 2:
questc = 0;
questr = 6;
questmob = "Lion";
questreward = rand()%4+1;
questdisplay = "Slay Lions";
break;
case 3:
questc = 0;
questr = 6;
questmob = "Elephant";
questreward = rand()%4+1;
questdisplay = "Slay Elephants";
break;
case 4:
questc = 0;
questr = 6;
questmob = "Wolf";
questreward = rand()%4+1;
questdisplay = "Slay Wolves";
break;
case 5:
questc = 0;
questr = 6;
questmob = "Spider";
questreward = rand()%4+1;
questdisplay = "Slay Spiders";
break;
}
}
cout << questdisplay << " [" << questc << "/" << questr << "]" << endl;
cout << "Reward >>> ";
switch (questreward) {
case 1:
qgoldr = rand()%(level*10)+(level*10);
cout << qgoldr << " Gold" << endl;
break;
case 2:
qxpr = rand()%(level*5)+(level*5);
cout << qxpr << " XP" << endl;
break;
case 3:
cout << "Slimy Chestplate >>> [10% HP Increase]" << endl;
break;
case 4:
cout << "Slimy Helmet >>> [5% HP Increase]" << endl;
break;
}
if (questc >= questr) {
quest = 0;
switch (questreward) {
case 1:
gold = gold + qgoldr;
goto questhall;
break;
case 2:
xp = xp + qxpr;
goto questhall;
break;
case 3:
slimychestplate = 1;
goto questhall;
break;
case 4:
slimyhelmet = 1;
goto questhall;
break;
}
}
cout << "99- Exit" << endl;
cin >> input;
goto menue;
dungeonmenue:
clear();
cout << R"(
_________________________________________________________
| , |
| .-'````````'. '(` .-'```````'-. |
| .` | `. `)' .` | `. |
| / | () \ U / | () \ |
| | | ; | o T o | | ; | |
| | | ; | . | . | | ; | |
| | | ; | . | . | | ; | |
| | | ; | .|. | | ; | |
| | |____;_________| | | |____;_________| |
| | / __ ; - | ! | / `'() _ - | |
| | / __ () -| - | / __-- - | |
| | / __-- _ | _- _ - | / __--_ | |
|__|/__________________|___________|/__________________|__|
/ _ - lc \
/ -_- _ - _- _--- -_- -_ \
)" << endl;
cout << "Available Dungeons" << endl;
cout << "[1] Oozing Oozeworks >>> [150 Gold, Oozing Gear]" << endl;
cout << "[99] Return to Menue" << endl;
cin >> input;
if (input == "99") {
goto menue;
} else if (input == "1") {
if (gold >= 150) {
status = "oozeworks";
gold = gold - 150;
ckills = 0;
rkills = 6;
goto oozeworks;
} else {
goto menue;
}
}
goto dungeonmenue;
oozeworks:
clear();
cout << R"(
_____ _ _____ _
| _ | (_) | _ | | |
| | | | ___ _____ _ __ __ _ | | | | ___ ______ _____ _ __| | _____
| | | |/ _ \_ / | '_ \ / _` | | | | |/ _ \_ /\ \ /\ / / _ \| '__| |/ / __|
\ \_/ / (_) / /| | | | | (_| | \ \_/ / (_) / / \ V V / (_) | | | <\__ \
\___/ \___/___|_|_| |_|\__, | \___/ \___/___| \_/\_/ \___/|_| |_|\_\___/
__/ |
|___/
)" << endl;
if (ckills >= rkills) {
goto oozeworksc;
}
cout << "[1] Continue [" << ckills << "/" << rkills << " slimes killed]" << endl;
cout << "[2] Adrenalin (Heals all missing hp/mana)" << endl;
cout << "[99] Exit" << endl;
cin >> input;
if (input == "1") {
mmana = inte * 10;
shp = stre * 20;
mhp = stre * 20;
etype = "Slime";
random2 = rand()%2+1;
eability = "ooze";
if (random2 == 1) {
ehp = shp + rand()%(level*8);
} else {
ehp = shp + rand()%(level*6);
}
mehp = ehp;
elevel = rand()%(level+3)+1;
if ((elevel < (level-3)) || (elevel > (level+3))) {
elevel = level;
}
goto aarena;
} else if (input == "99") {
goto menue;
} else if (input == "2") {
mhp = stre * 20;
mmana = inte * 10;
hp = mhp;
hp = hp * (bphp+1);
hp = hp + bhp;
mana = mmana;
}
goto oozeworks;
oozeworksc:
status = "arena";
cout << "You have completed the Oozing Oozworks" << endl;
rxp = rand()%(level*6)+5;
rgold = rand()%(level*9);
cout << "You recieved " << rxp << " xp" << endl;
cout << "You recieved " << rgold << " gold" << endl;
xp = xp + rxp;
gold = gold + rgold;
random = rand()%2+1;
switch (random) {
case 1:
cout << "New legplates unlocked! >>> [Oozing Legplates, increases hp by 15%]" << endl;
oozinglegplates = 1;
break;
case 2:
cout << "New boots unclocked! >>> [Oozing Boots, increases hp by 10%]" << endl;
oozingboots = 1;
break;
}
cout << "Type [1] to return to the menue" << endl;
cin >> input;
goto menue;
oozeworksr:
ckills = ckills + 1;
rxp = rand()%(level*4)+5;
rgold = rand()%(level*7);
rgold = rgold * goldb;
cout << "You recieved " << rxp << " xp" << endl;
cout << "You recieved " << rgold << " gold" << endl;
xp = xp + rxp;
gold = gold + rgold;
if (xp >= xpl) {
level = level + 1;
xpl = xpl + xpb;
xpb = xpb + 50;
skill = skill + 5;
cout << "You have leveled up to level " << level << endl;
}
cout << "Type [1] to continue your adventure" << endl;
cin >> input;
goto oozeworks;
savegame:
clear();
// Saving / Loading Strings
sslimychestplate = std::to_string(slimychestplate);
sslimyhelmet = std::to_string(slimyhelmet);
soozinglegplates = std::to_string(oozinglegplates);
soozingboots = std::to_string(oozingboots);
spiclass = std::to_string(piclass);
slevel = std::to_string(level);
sxp = std::to_string(xp);
sxpb = std::to_string(xpb);
sxpl = std::to_string(xpl);
sstre = std::to_string(stre);
sinte = std::to_string(inte);
sdext = std::to_string(dext);
shpotion = std::to_string(hpotion);
sgold = std::to_string(gold);
sskill = std::to_string(skill);
code = "";
code = code + sgold + ":" + sstre + ":" + sinte + ":" + sdext + ":" + sskill + ":" + sxp + ":" + sxpl + ":" + sxpb + ":" + slevel + ":";
code = code + shpotion + ":";
code = code + spiclass + sslimyhelmet + sslimychestplate + soozinglegplates + soozingboots;
cout << code << endl;
cout << "Type [1] to return to the menue" << endl;
cin >> input;
goto menue;
// Loading the game code they input
loadgame:
clear();
sslimychestplate = "";
sslimyhelmet = "";
soozinglegplates = "";
soozingboots = "";
spiclass = "";
slevel = "";
sxp = "";
sxpb = "";
sxpl = "";
sstre = "";
sinte = "";
sdext = "";
shpotion = "";
sgold = "";
sskill = "";
cout << "Please input your game code EXACTLY (if you don't this may corrupt your game save)" << endl;
cin >> input;
code = input;
goto compile;
cout << code << endl;
// Loading Game Save FILE
compile:
x = 0;
goldr:
cout << sgold << endl;
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto strer;
} else {
sgold = sgold + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto goldr;
}
strer:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto inter;
} else {
sstre = sstre + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto strer;
}
inter:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto dextr;
} else {
sinte = sinte + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto inter;
}
dextr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto skillr;
} else {
sdext = sdext + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto dextr;
}
skillr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto xpr;
} else {
sskill = sskill + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto skillr;
}
xpr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto xplr;
} else {
sxp += code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto xpr;
}
xplr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto xpbr;
} else {
sxpl = sxpl + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto xplr;
}
xpbr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto levelr;
} else {
sxpb = sxpb + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto xpbr;
}
levelr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto hpotionr;
} else {
slevel = slevel + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto levelr;
}
hpotionr:
if (code[x] == ':') { // If the character is a pipe
x=x+1; // Go to next variable
goto items;
} else {
shpotion = shpotion + code[x]; // If not add the variables
x=x+1; // Go to next charcter
goto hpotionr;
}
items:
spiclass += code[x];
x=x+1;
sslimyhelmet += code[x];
x=x+1;
sslimychestplate += code[x];
x=x+1;
soozinglegplates += code[x];
x=x+1;
soozingboots += code[x];
goto settingvaribles;
settingvaribles:
gold = atoi(sgold.c_str()); // Gold
stre = atoi(sstre.c_str()); // Strength
inte = atoi(sinte.c_str()); // Intelligence
dext = atoi(sdext.c_str()); // Dexterity
skill = atoi(sskill.c_str()); // Skillpoints
xp = atoi(sxp.c_str()); // Xp
xpl = atoi(sxpl.c_str()); // Xp till level
xpb = atoi(sxpb.c_str()); // Increases xpl
level = atoi(slevel.c_str()); // Level
hpotion = atoi(shpotion.c_str()); // HP potions
piclass = atoi(spiclass.c_str()); // Class
slimyhelmet = atoi(sslimyhelmet.c_str()); // Slimy Helmet
slimychestplate = atoi(sslimychestplate.c_str()); // Slimy Chestplate
oozinglegplates = atoi(soozinglegplates.c_str()); // Oozing Legplates
oozingboots = atoi(soozingboots.c_str()); // Oozing Boots
if (piclass == 1) {
pclass = "Champion";
} else if (piclass == 2) {
pclass = "Necromancer";
} else if (piclass == 3) {
pclass = "Assassin";
} else if (piclass == 4) {
pclass = "Cleric";
}
/*
gold = arr[1];
stre = arr[2];
inte = arr[3];
dext = arr[4];
skill = arr[5];
xp = arr[6];
xpl = arr[7];
xpb = arr[8];
level = arr[9];
hpotion = arr[10];
piclass = arr[11];
slimyhelmet = arr[12];
slimychestplate = arr[13];
oozinglegplates = arr[14];
oozingboots = arr[15];
*/
goto compilend;
compilend:
clear();
loadingload:
cout << R"(
db .d88b. .d8b. d8888b. d888888b d8b db d888b
88 .8P Y8. d8' `8b 88 `8D `88' 888o 88 88' Y8b
88 88 88 88ooo88 88 88 88 88V8o 88 88
88 88 88 88~~~88 88 88 88 88 V8o88 88 ooo
88booo. `8b d8' 88 88 88 .8D .88. 88 V888 88. ~8~
Y88888P `Y88P' YP YP Y8888D' Y888888P VP V8P Y888P
)" << endl;
y = 0;
loadingload2:
if (y < 101) {
y = y + 1;
goto loadingload2;
}
clear();
cout << "-------~ Process Complete ~-------" << endl;
cout << "Please re-input your name" << endl;
cin >> input;
name = input;
goto menue;
leave:
clear();
cout << "See you later!" << endl;
return 0;
}
-
47\$\begingroup\$ Some very impressive ascii art. \$\endgroup\$Loki Astari– Loki Astari2017年08月05日 16:07:22 +00:00Commented Aug 5, 2017 at 16:07
-
2\$\begingroup\$ Thx, also known problem "using namespace std" didn't know it was bad until I was half way done, currently I am still working on this project. \$\endgroup\$Handge– Handge2017年08月05日 16:18:59 +00:00Commented Aug 5, 2017 at 16:18
-
3\$\begingroup\$ Can you talk a little about why you wanted to do this in C++ rather than, say, some scripting language? \$\endgroup\$einpoklum– einpoklum2017年08月05日 19:40:33 +00:00Commented Aug 5, 2017 at 19:40
-
1\$\begingroup\$ Well, reason why I did this in C++, was because: a- One of the only languages I really know. b- I think C++ is really nice and easy and I don't really want to learn another language currently \$\endgroup\$Handge– Handge2017年08月05日 20:38:11 +00:00Commented Aug 5, 2017 at 20:38
-
4\$\begingroup\$ I highly doubt that he did the ascii art, since this page has some of it with different names attached to it... ascii.co.uk/art \$\endgroup\$Florian Schaetz– Florian Schaetz2017年08月07日 14:04:44 +00:00Commented Aug 7, 2017 at 14:04
6 Answers 6
First of all, let me join others in pointing out what sweet ASCII art you've done. This definitely has the beginnings of a much nicer text game than most.
I'd start by defining some structures to hold data about specific things in the game. For a couple of examples:
struct Ability {
std::string name;
int level_adder;
int cost(int level, int inte) {
return (level + level_adder) * inte;
}
bool can_afford(int level, int inte, int manna) {
return cost(level, inte) <= manna;
}
void show(int level, int inte) {
std::cout << name << "[" << cost() << " manna]\n";
}
};
class PlayerClass {
std::string name;
std::vector<Ability> abilities;
size_t ability_count() { return abilities.size(); }
void show(int level, int inte) {
for (int i=0; i<abilities.size(); i++)
std::cout << "[" << i << "] ";
abilities[i].show(level, inte);
}
Ability const &operator[](size_t index) const {
return abilities.at(index);
}
};
With these, we can define all the data for the Player classes something like this:
PlayerClass Champion{
"Champion",
{ "Cleaving Strike", 0},
{ "Melting Thrust", 0},
{"Critical Bash", 0},
{"Purify", 1}
};
PlayerClass Necromancer{
"Necromancer",
{ "Shadow Strike", 0},
{ "cripple", 0},
{ "Mutilate", 0},
{ "Life Tap", 2}
};
...and so on for the other player classes. For only one example, this makes it much easier to add more player classes in the future--for example, I can sit down and decide I want to add a "thief" class:
PlayerClass Thief {
"Thief",
{ "Pick Pocket", 0},
{ "Grab Purse", 0},
{ "Rob Business", 1},
{ "Rob Bank", 4}
};
...and most of the rest of the game can work with this new player class without any modification at all. Likewise, I can add a new ability to an existing player class by simply deciding on a name and a relative cost for using that ability--I don't have to modify all the ability-related logic to take the newly added ability into account.
Then we can define a player to hold (for example) a reference to a PlayerClass
object:
class Player {
PlayerClass &pclass;
// ...
};
With this, getability
obviously returns (possibly a pointer or reference to) an Ability object, and looks something like this:
player.pclass.show();
cin >> input;
// This logic isn't complete--we need to add a call to `can_afford` to see
// whether the player can afford to use an ability.
if (input > player.pclass.ability_count()
ability = None;
else {
ability = player.pclass[input];
player.manna -= ability.cost();
}
Note how this has eliminated huge amounts of repetition in the code, with essentially identical logic repeated once for every ability of every player class.
Prevent mistakes
I'd also consider checking whether the player can afford to use a particular ability before displaying that ability. This way they only choose from the abilities they can use, rather than trying to choose an ability they can't actually afford, then finding out too late that they made a bad choice and nothing happens.
Naming
Some of the names you've used are shortened to the point that I'm not sure what they're intended to mean. Just for a couple of examples, inte
and stre
--I'd de-abbreviate these to the point that somebody reading the code can easily understand what they're supposed to really mean.
-
2\$\begingroup\$ Funny how your players are not instances of their player class :-) \$\endgroup\$einpoklum– einpoklum2017年08月05日 19:39:42 +00:00Commented Aug 5, 2017 at 19:39
-
\$\begingroup\$ @einpoklum: It is somewhat--and tends to suggest that the names I used still aren't exactly ideal... \$\endgroup\$Jerry Coffin– Jerry Coffin2017年08月05日 19:48:56 +00:00Commented Aug 5, 2017 at 19:48
-
6\$\begingroup\$ Well, you can't call it "PlayerClassButNotInTheCppSense"... \$\endgroup\$einpoklum– einpoklum2017年08月05日 21:00:45 +00:00Commented Aug 5, 2017 at 21:00
-
12\$\begingroup\$ If you're looking for an alternative to 'class' in the RPG sense, sometimes 'job' or 'profession' are preferred. \$\endgroup\$Pharap– Pharap2017年08月06日 02:41:13 +00:00Commented Aug 6, 2017 at 2:41
There's good news and bad news about this code. The bad news is that it's not very good code. The good news is that it's not going to be too hard to improve it greatly, and the ASCII art is awesome! Here are some observations that may help you improve your code.
Don't abuse using namespace std
Putting using namespace std
at the top of every program is a bad habit that you'd do well to avoid.
Consider separating I/O from the algorithm
Right now, everything is done in main. Better practice is to separate things into functions. In particular, I'd recommend separating the input/output routines into separate functions. Right now the control flow is too difficult to follow and it's hard to tell what's happening in the code.
Avoid using goto
Having a proliferation of goto
statements is usually a sign of bad design. Better would be to eliminate them entirely -- it makes the code easier to follow and less error-prone. In this code, it's probable that you could use a state machine and switch
statement instead which would make the code much easier to follow and much less prone to error.
Use objects
Because you're writing in C++, it would make sense to use objects. For example, you might have a class Player
which contains all of the variables currently only labelled with a comment saying "player variables" and a game state class to simplify loading and saving the game state.
Consider using a better random number generator
You are currently using
random = rand()%5+1;
There is a problem with this approach: the low order bits of the random number generator are not particularly random, so neither will random
be. On my machine, there's a slight but measurable bias toward 0 with that. A better solution, if your compiler and library supports it, would be to use the C++11 `std::uniform_int_distribution. It looks complex, but it's actually pretty easy to use.
class Die {
public:
Die(int min, int max) : min{min}, max{max} {}
Die(int max=6) : min{1}, max{max} {}
int operator()() {
std::uniform_int_distribution<> dist(min,max);
return dist(eng);
}
private:
int min;
int max;
static std::mt19937 eng;
};
std::mt19937 Die::eng{std::random_device{}()};
When you want, as in this example, some number from 1 to 6, just call it like this:
int main() {
Die die{6}; // create conventional 6-sided die
for (int i=20; i; --i) {
std::cout << die();
}
}
Eliminate "magic numbers"
There are numbers sprinkled throughout the code, such as 5
and 6
that have a specific meaning in their particular context. By using named constants the program becomes easier to read and maintain. For cases in which the constant only has sense with respect to a particular object, consider making that constant part of the object.
Omit return 0
When a C or C++ program reaches the end of main
the compiler will automatically generate code to return 0, so there is no need to put return 0;
explicitly at the end of main
.
-
20\$\begingroup\$ With due respect - I think it's a waste of advise space to talk about
return 0
. It really doesn't matter this way or the other, it has no effect on the design of anything. Suggest you just drop that section of a long (and useful) answer. \$\endgroup\$einpoklum– einpoklum2017年08月05日 19:36:54 +00:00Commented Aug 5, 2017 at 19:36 -
\$\begingroup\$ I've trimmed it. \$\endgroup\$Edward– Edward2017年08月05日 21:43:13 +00:00Commented Aug 5, 2017 at 21:43
-
4\$\begingroup\$ A matter of personal coding style, but I learned to write
return EXIT_SUCCESS;
and usually still do. Any other function in C or C++ declared as returningint
needs areturn
statement, and magic numbers are bad style. \$\endgroup\$Davislor– Davislor2017年08月07日 08:31:51 +00:00Commented Aug 7, 2017 at 8:31 -
\$\begingroup\$ Int-to-bool implicit casting makes i; a valid expression in the condition statement, but is this common practice for C++? I'd think i != 0 or i > 0 is better. \$\endgroup\$kettlecrab– kettlecrab2017年08月07日 20:13:53 +00:00Commented Aug 7, 2017 at 20:13
-
\$\begingroup\$ @Anon234_4521: yes, it's common practice and I think you'll find that on most architectures and compilers, it produces identical code if it's instead written
i != 0
. \$\endgroup\$Edward– Edward2017年08月07日 21:02:49 +00:00Commented Aug 7, 2017 at 21:02
Control Flow
I want to endorse and flesh out a bit Edward’s advice on removing the goto
statements. A better approach here is some kind of state machine. You’ve got some kind of game state that tracks what mode you’re in, such as whether you’re selecting an ability, moving around, starting an encounter, and so on.
You draw whatever screen is appropriate to the current state. (Say, a map in overland mode, ASCII art of a monster in battle mode, maybe a box-drawn first-person dungeon if you’re paying homage to Wizardry or Ultima?) The options displayed depend on the game mode, what abilities you have, and whatever else is appropriate (such as what items are in your inventory, maybe, or how much energy you have, or a cooldown period). Selecting any option then updates the game state. For example, winning a battle might send you to the loot screen. The program logic is a loop something like this:
while ( !state.hasQuit() ) {
state.displayScreen();
state.getActionAndUpdate();
}
A lot simpler and easier to follow than a bunch of goto
statements!
Then displayScreen()
could, say, clear the screen, print a status bar, print the ASCII art, look up what abilities your character has, print those, and give you a menu prompt.
If the state becomes big and bulky enough, you would no longer want to keep the entire game state in a single "God object" that everything needs to muck around with. Player stats might go into one object, world maps in another, enemy stats in others. Separate subsystems however it makes sense. But that kind of loop is a nice, simple pattern for tracking which game mode you’re currently in and acting appropriately.
Using OO to Clean Up the Code
A variant is to make the last line something like state = state->getNewState()
, where state
is a pointer whose type is the base class of all states. That lets you split up the abstract concept of a game mode into concrete instances, like overland mode or combat mode. These can be daughter classes whose member functions implement the correct behavior for that one mode. Each of those member functions would then be shorter and clearer than one big function that contains all the code paths for every mode. You select between them by returning a reference to another state object, no if
, switch
or goto
involved.
If you have only a finite number of static game modes, you can create a single object for each and return const
references to them. The specific code for each mode would live inside a member function of one derived class.
If you need to create new state objects, such as to store different information for each battle, you want to manage them with smart pointers like std::unique_ptr
. This does all the memory management for you, which is otherwise one of the trickiest things to get right.
If you do have any remaining sections of code that fit the pattern of a series of tests to select a different code branch for each of many cases, the classic solution is to define an enum
with a constant for every possible case and then write a switch
statement. Your compiler might even have a prayer of warning you if you forget or mistype one of the cases.
Avoid Global Variables
These make your program very hard to debug, because any part of the code could have changed them. A better solution is to have the player state store internally things like how many health potions you have. Then, only the code to use one decrements the count, and only the code to check inventory looks it up and displays it.
You also would want to store numeric data as a number, rather than as a string that you repeatedly convert back into a number.
Use Data Structures
In general, I would advise moving your special cases into data structures, not code branches. Jerry Coffin had good suggestions about how to do this for player abilities.
But, for example, your phenomenal ASCII art and other monster data could be stored as values in a hash table (std::unordered_map
in the STL) with the names you’re using to select them as the keys. This would have several advantages. One of the most important is that, if you add another monster, you just need to add its entry to the table in one place.
Treating every new ability and every new monster as a special case that needs special handling in every piece of code that deals with them becomes a real nightmare: you’ve always got to check that you remembered them all everywhere and dealt with them consistently. If you put all the data in one place, and you write code in another place that can handle any piece of the data, adding stuff becomes so much easier.
Some Technicalities
In this case, you might consider storing your ASCII art as vectors of rows. (Since two-dimensional array can mean a few different things, a good unambiguous name for this is a rectangular array.) This would make it possible to, for example, display a picture of the monster in a box and wrap an infodump around it, or show its stats on the left or right of the picture, or overlay a caption like "-999 HP" on the frame.
I want to repeat and emphasize the advice to separate the data from the display code. You want code orthogonal to your game data. You shouldn’t be rewriting the display code for every kind of monster, because that makes it a nightmare to ever change. Don’t repeat yourself! That also makes it possible for you to do a lot more things with your data or run your code on arbitrary new data.
If you make the art arrays wide characters, then use std::wcout
, you can use Unicode art, and not just ASCII art. You might not necessarily want to. Your art is spectacular. The classic games you’re hearkening back to used the box-drawing and geometric shape characters now in Unicode. But the option is there. Note that Windows needs a bit of non-standard initialization code inside an #ifdef
block for this to work, but I can share it if you want.
On many systems even today, you can also insert ANSI color codes (16 colors, foreground and background, and some special features like bold and underline, like in the late 8-bit or early 16-bit era, and exactly like classic Unix terminals over telnet) or xterm color (more than 200). Can’t get more authentically retro than that. The Linux console supports those codes natively, and I think the OSX terminal does too. ETA: And so does Windows, through a non-standard API.
You might want to load the ascii art from separate text files so you can edit those without messing with the functional code.
-
\$\begingroup\$ This seems to be more like a comment, but I also notice you didn't have enough reputation to post a comment. +1 anyway. \$\endgroup\$Oskar Skog– Oskar Skog2017年08月07日 20:02:18 +00:00Commented Aug 7, 2017 at 20:02
Separate the code in short and simple functions
Your current functions are way too long: for example, main()
has 1282 lines. That's much more than acceptable.
While the exact maximal number of lines is a subjective thing, there are some good rules of thumb. For example, this question mentions 100 to 200 lines as the upper bound. Ideally, every function should do just one thing and do it well - this called SRP, single responsibility principle.
In many programs main()
is somewhat an exception of this, as it usually does both initialization (by calling an init()
or similar function) and the main loop. Still, the main loop should consist mainly of calls to other functions and/or state machine logic. I believe the other answers give more details on structuring it.
Of all the answers, the one thing that I would emphasize the most is use objects. This was already answered, but again, I wanted to emphasize it in detail. If you only learn that, this is worth more than the rest of the answers combined.
You seem to think in procedures and algorithms. This is nice if you do C, but if you want to do C++, learn to think in objects. Objects have clear responsibilities in which you trust blindly (as long as their unit tests work) and of which you don't need to know HOW they do stuff. Objects work as autonomous entities. I'd recommend that you try to use no functions at all, best is only when you need to extend the functionality of a standard library type like std::vector.
In the end, your main should look like this:
int main() {
Game game;
game.run();
return 0;
}
The Game class (or however you want to call it) then creates other classes which in turn might create some more, in some sort of hierarchy. The methods of such a class call methods of other classes and do not much more than to make the child objects work together. Actual algorithms are as short as possible, in methods of most basic classes.
Anything that is content - like ASCII art, RPG character class names, names of places et cetera - should not be in the code itself.
Focus on the concept of readability. Your code should have a very clear outline. If a stranger wants to find some specific functionality, it should be evident where to look. Right now, one has to browse through one big file. If you have several classes with header and source files, one knows where to look.
-
\$\begingroup\$ OOP is one of the approaches, but not the only one or necessarily the best one for all of things. C++ is a multi paradigm language. \$\endgroup\$kfx– kfx2017年08月08日 09:52:58 +00:00Commented Aug 8, 2017 at 9:52
-
1\$\begingroup\$ @kfx I'd say that the cases in which you use other approaches are comparatively rare. Basically when it comes down to actual algorithms that solve some very mathematically modeled problem. anyway, thing is, a beginner should learn the one approach that will be the best one in most situations he will ever encounter, which is OOP. We can argue in which cases other approaches are better, but if we have a beginner... And the actual case we have at hand is certainly best solved by OOP. \$\endgroup\$Aziuth– Aziuth2017年08月08日 15:00:26 +00:00Commented Aug 8, 2017 at 15:00