I wrote a little adventure game in C++.
#include <string>
#include <utility>
#include <deque>
#include <cstdlib>
#include <cstdarg>
#include <iostream>
#include <cmath>
#include <cstdio>
#include <map>
static const struct { const char* a; float b, c; } MoneyTypes[] =
{ {"platinum",10,0}, {"gold",1,0}, {"silver",.6},
{"bronze",.4,0}, {"copper",.2,0}, {"wood",.01} },
BuildTypes[] =
{ {"iron",.4,3}, {"fur",.01,.2}, {"platinum",2,4}, {"gold",1,3.5},
{"chromium",.9,2}, {"bronze",.1,2.7}, {"pewter",.05,2},
{"bamboo",.01,1}, {"silk",.01,.1}, {"steel",.7,3},
{"leather",.09,.5}, {"glass",.04,2} },
ItemTypes[] =
{ {"shirt",1}, {"tie",.25}, {"bracelet",.2}, {"shoe",.4}, {"leggings",.8},
{"battlesuit",10}, {"hammer",.4}, {"dagger",.1}, {"cap", .6},
{"crown",3}, {"sceptre",4}, {"cape",.7}, {"overalls",4} },
CondTypes[] =
{ {"awesome",1.2}, {"excellent",1}, {"good",.9}, {"average",.75},
{"bad",.6}, {"poor",.5}, {"thrashed",.4} },
WallTypes[] = { {"open",1}, {"collapsed",.5}, {"blocked"} },
EnvTypes[] = { {"dark"}, {"humid"}, {"tall"}, {"beautiful"}, {"narrow"} },
BodyParts[] = { {"finger",10}, {"teeth",30}, {"elbow",100}, {"toe",40} },
FoodTypes[] = {
{"b akbdl epqfts dblf",50000}, {"b kbqhf okbsf pe dgjdlfm kfht",35000},
{"b dbvkcqpm pe dpplfc opsbspft",20000}, {"b dgjdlfm gps cph",10000},
{"dgfftf bmc nbdbqpmj",6000}, {"b avssfqnjkl ajtdvjs",3000}, {"b apjkfc fhh",2000},
{"tpnf kjdgfm tsfx",1000}, {"b xppc dpqsfw tbmcxjdg",700},
{"b dvo pe ujmfhbq", 500}, {"b dvo pe bookf tffct", 300},
{"b qpssfm dbqqps", 200}, {"b nvh pe nvccz xbsfq",110}, {"tpnf qbaajs cqpoojmht",70},
{"b cfbc dpdlqpbdg", 50}, {"b npmsg pkc tojcfq mfs", 30},
{"b gjkk pe cvts", 16}, {"b gfbo pe cvts", 8}, {"b ajh ojkf pe cvts", 4},
{"b ojkf pe cvts", 2}, {"b tofdlkf pe cvts", 1}
}; // badcfehgjilknmporqtsvuxwzy
#define NItems(x) (sizeof((x))/sizeof(*(x)))
static double frand() { return std::rand() / ((double)RAND_MAX+1.0); }
static const char* Sep(size_t pos, size_t max) { return pos?pos==max-1?" and ":", ":""; }
struct ItemType {
size_t build, item, cond; // Indexes.
const std::string Name1() const { return ItemTypes[item].a; }
const std::string Name2() const { return std::string(BuildTypes[build].a)+" "+Name1(); }
const std::string Name3() const { return std::string(CondTypes[cond].a)+" "+Name2(); }
const std::string Name1c() const { return std::string(CondTypes[cond].a)+" "+Name1(); }
const std::string Name3b(bool condition = false) const {
std::string n = condition ? Name3() : Name2();
if(n[n.size()-1]=='s') return n;
return (n[0]=='a'||n[0]=='e'||n[0]=='i'||n[0]=='o'||n[0]=='u' ? "an ":"a ")+n;
}
const std::string Name(int level) const
{ return level==1 ? Name1() : level==2 ? Name2() : level==3 ? Name3() : Name1c(); }
ItemType()
: build ( std::rand() % (frand() > 0.4 ? NItems(BuildTypes) : 2) ),
item ( std::rand() % (frand() > 0.4 ? NItems(ItemTypes) : 4) ),
cond ( std::rand() % (frand() > 0.8 ? NItems(CondTypes) : 3) ) { }
double value() const {
return 200.0 * ItemTypes[item].b * BuildTypes[build].b * CondTypes[cond].b;
}
double weight() const { return BuildTypes[build].c; }
};
static std::string Appraise(double value,int v=1,int maxi=3) {
std::deque<std> list; redo:
for(size_t a=0; a<NItems>= FoodTypes[a].b*3.0) {
std::string k = FoodTypes[a].a;
for(size_t b=0; b<k.size(); ++b) if(k[b]-' ') k[b]=1+((k[b]-1)^v);
list.push_back(k); value -= FoodTypes[a].b*3.0;
if((int)list.size() < maxi) goto redo; break;
}
if(list.empty()) list.push_back("only hungry dreams");
std::string resp;
for(size_t a=0; a<list.size(); ++a) resp += Sep(a,list.size()) + list[a];
return resp;
}
static struct Eq {
std::deque<ItemType> Items;
long Money[ NItems(MoneyTypes) ];
template<typename>
bool print(bool is_inv, PrFunc& P) const {
double moneyvalue = 0;
for(size_t a=0; a<Items.size(); ++a)
P("%s\n", Items[a].Name3b(!is_inv).c_str()),
moneyvalue += Items[a].value();
if(is_inv && moneyvalue) P("The total value of your items is %.2f gold.\n", moneyvalue);
moneyvalue = 0;
for(size_t a=0; a<NItems> 0 || !Items.empty();
}
double value() const {
double res = 0;
for(size_t a=0; a<Items.size(); ++a) res += Items[a].value();
for(size_t a=0; a<NItems(Money); ++a) res += Money[a] * MoneyTypes[a].b;
return res;
}
int burden() const {
double weight = 0;
for(size_t a=0; a<NItems(Money); ++a) weight += Money[a] / 100.0;
for(size_t a=0; a<Items.size(); ++a) weight += Items[a].weight();
return 1 + weight;
}
double look_item(long no, bool in, bool all) const {
if(all) std::printf("You see a %s made of %s, in %s condition. ",
ItemTypes[Items[no].item].a,
BuildTypes[Items[no].build].a,
CondTypes[Items[no].cond].a);
else std::printf("It is a %s made of %s. It is in %s condition.\n",
ItemTypes[Items[no].item].a,
BuildTypes[Items[no].build].a,
CondTypes[Items[no].cond].a);
if(in) std::printf("It does not hide anything interesting inside thereof.\n");
else if(all) std::printf("\n");
else std::printf(
"You estimate that with it you could probably buy %s.\n",
Appraise(Items[no].value(),1, 1).c_str());
return Items[no].value();
}
void look_money(long a, bool in, bool all) const {
std::printf("%ld %s coin%s\n", Money[a], MoneyTypes[a].a, "s"+(Money[a]==1));
if(in) std::printf("They are just normal coins.\n");
else if(!all) std::printf("The coins are worth %.2f gold total\n", Money[a]*MoneyTypes[a].b);
}
void clear(size_t n_items = 0) {
Items.resize(n_items);
for(size_t a=0; a<NItems(Money); ++a) Money[a] = 0;
}
std::deque<std> move(Eq& target, const std::string& what, bool all) {
std::deque<std> movelist;
Eq target_backup = target, me_backup = *this;
bool ok = true;
if(all)
ok = move_one(target, what, movelist, all);
else
for(size_t begin = 0; begin != what.size(); ) {
while(what[begin]==' ') ++begin;
size_t end = what.find(',', begin);
if(end == what.npos) end = what.size();
size_t e = end;
while(e > begin && what[e-1] == ' ') --e;
ok = ok && move_one(target, what.substr(begin, e-begin),
movelist, all);
begin = end; if(begin<what> what.c_str()
&& number_begin[-1] >= '0' && number_begin[-1] <= '9') --number_begin;
long index = std::strtol(number_begin, 0, 10);
while(number_begin > what.c_str() && number_begin[-1] == ' ') --number_begin;
std::string itemname = what.substr(0, number_begin-what.c_str());
bool skip = itemname.size() != what.size();
for(int level=4; level>=1; --level)
{
long occurrences = 0;
for(size_t a=first; a<Items.size(); ++a)
if((itemname == "" && all)
|| itemname == "a "+Items[a].Name(level)
|| itemname == "an "+Items[a].Name(level)
|| itemname == Items[a].Name(level))
{
if(skip && ++occurrences != index) continue;
return a;
}
}
return -1;
}
long find_money(const std::string& what, bool all, size_t first=0) const {
for(size_t moneytype = first; moneytype < NItems(MoneyTypes); ++moneytype) {
if(Money[moneytype] <= 0) continue;
if((what == "" && all)
|| what == "coins"
|| what == MoneyTypes[moneytype].a
|| what == MoneyTypes[moneytype].a+std::string(" coin")
|| what == MoneyTypes[moneytype].a+std::string(" coins")) {
return moneytype;
}
}
return -1;
}
bool move_one(Eq& target, const std::string& what,
std::deque<std>& movelist,
bool all) {
bool found_item = false, found_money = false;
for(long item_id; (item_id=find_item(what, all)) >= 0;) {
target.Items.push_front(Items[item_id]);
movelist.push_back(Items[item_id].Name3b());
Items.erase(Items.begin()+item_id);
found_item = true;
if(!all) break;
}
char* end = 0, * begin = (char*)what.c_str();
long numeral = std::strtol(begin, &end, 10);
if(!end || end == begin) numeral = 0;
else if(numeral <1>= 0;) {
long mynumeral = numeral;
if(mynumeral == 0) mynumeral = Money[moneytype];
if(mynumeral > Money[moneytype]) return false;
target.Money[moneytype] += mynumeral;
Money[moneytype] -= mynumeral;
char explanation[512];
std::sprintf(explanation, "%ld %s coin%s",
mynumeral, MoneyTypes[moneytype].a, "s"+(mynumeral==1));
movelist.push_back(explanation);
found_money = true;
if(!all) break;
}
return found_money || found_item;
}
} eq;
static struct Room {
size_t Wall, Env; // Indexes
float Chest; // 0=no chest
int seed; // For maze generation
Eq items; // What's lying on the floor
Room() : Wall(0),Env(0),Chest(0),seed(),items() {}
} const defaultroom;
static struct Maze {
std::map<long/*x*/,std::map<long> > rooms;
Room& GenerateRoom(long x,long y,const Room& model, int seed) {
std::srand((x<<12)^y);
std::pair<std::map<long>::iterator, bool>
insres ( rooms[x].insert(std::make_pair(y, model)) );
Room& room = insres.first->second;
if(insres.second) {
room.Chest = frand() > 0.7 ? 1.0 : 0.0;
room.seed = (seed + (frand() > 0.9 ? rand() : 0)) & 3;
if(frand() > 0.9) room.Env = rand() % NItems(EnvTypes);
if(frand() > (seed==model.seed ? 0.9 : 0.2))
room.Wall = frand() > 0.9 ? 0 : 2;
room.items.clear(unsigned(std::pow(frand(),90.0) * 2.5));
} return room;
}
char Char(long x,long y) const
{
std::map<long/*x*/,std::map<long> >::const_iterator
i = rooms.find(x);
if(i == rooms.end()) return ' ';
std::map<long>::const_iterator j = i->second.find(y);
if(j == i->second.end()) return ' ';
if(j->second.Wall) return '#';
if(j->second.Chest > 0.0) return 'c';
return (j->second.items.value() != 0.0) ? 'i' : '.';
}
} maze;
#define SpawnRooms(x,y) \
const Room \
&room = maze.GenerateRoom(x,y, defaultroom, 0), \
&room_n = maze.GenerateRoom(x,y-1, room, 0), \
&room_s = maze.GenerateRoom(x,y+1, room, 1), \
&room_w = maze.GenerateRoom(x-1,y, room, 2), \
&room_e = maze.GenerateRoom(x+1,y, room, 3)
static long x=0, y=0, life=1000;
struct VecPrint: public std::deque<std>
{
void operator() (const char* fmt, ...)
{
if(pos < size()) printf("%s | ", operator[](pos++).c_str());
else if(!empty()) printf("%*s | ", (int)operator[](0).size(), "");
va_list ap;
va_start(ap, fmt);
std::vprintf(fmt, ap);
va_end(ap);
}
size_t pos; VecPrint() : pos(0) { }
~VecPrint() /* flush */
{ while(pos < size()) printf("%s |\n", operator[](pos++).c_str()); }
};
static void Look() {
SpawnRooms(x,y);
for(int o=1; o<5&&!maze.GenerateRoom(x,y+o, room, 0).Wall; ++o) { SpawnRooms(x,y+o); }
for(int o=1; o<5&&!maze.GenerateRoom(x,y-o, room, 0).Wall; ++o) { SpawnRooms(x,y-o); }
for(int o=1; o<6&&!maze.GenerateRoom(x-o,y, room, 0).Wall; ++o) { SpawnRooms(x-o,y); }
for(int o=1; o<6&&!maze.GenerateRoom(x+o,y, room, 0).Wall; ++o) { SpawnRooms(x+o,y); }
VecPrint mapgraph;
for(long yo=-4; yo<=4; ++yo)
{
std::string line;
for(long xo=-5; xo<5> 0.0 || room.items.value() > 0.0) mapgraph("\n");
if(room.Chest > 0.0) mapgraph("a chest\n");
room.items.print(false, mapgraph);
}
static void Inv() {
if(!eq.print(true, std::printf))
std::printf("You are carrying nothing.\n");
}
static void LookAt(std::string what, bool in=false) {
const Room &room = maze.GenerateRoom(x,y, defaultroom, 0);
bool all = false;
if(what == "all") { all=true; what=""; }
else if(what.substr(0,4) == "all ") { all=true; what.erase(0,4); }
long no,c=0;
double value=0;
if(all)
{
for(no=0; (no = room.items.find_item(what,all,no)) >= 0; ++c) value += room.items.look_item(no++,in,all);
for(no=0; (no = room.items.find_money(what,all,no)) >= 0; ++c) room.items.look_money(no++,in,all);
if(!c)
{
for(no=0; (no = eq.find_item(what,all,no)) >= 0; ++c) value += eq.look_item(no++,in,all);
for(no=0; (no = eq.find_money(what,all,no)) >= 0; ++c) eq.look_money(no++,in,all);
}
}
else
{
for(no=0; (no = eq.find_item(what,all,no)) >= 0; ++c) value += eq.look_item(no++,in,all);
if(!c) for(no=0; (no = eq.find_money(what,all,no)) >= 0; ++c) eq.look_money(no++,in,all);
if(!c) for(no=0; (no = room.items.find_item(what,all,no)) >= 0; ++c) value += room.items.look_item(no++,in,all);
if(!c) for(no=0; (no = room.items.find_money(what,all,no)) >= 0; ++c) room.items.look_money(no++,in,all);
}
if(c&&all) std::printf(
"You estimate that with them you could probably buy %s.\n",
Appraise(value,1, 1).c_str());
if(c) return;
if(what == "" && all && room.Chest > 0)
std::printf("You see a closed chest here. You can try to <open> it.\n");
else if(what == "chest" && room.Chest > 0)
std::printf(in
?"The chest is closed. You cannot see inside. You can try to <open> it.\n"
:"There is a chest here. It is closed. You can try to <open> it.\n");
else
std::printf("There %s no %s here that you can look at.\n",
what[what.size()-1]=='s' ? "are" : "is", what.c_str());
}
static void EatLife(long l) {
if(life>=800 && life-l<800>=150 && life-l<150>=70 && life-l<70) std::printf("You are about to collapse any second!\n");
life -= l;
}
static void OpenChest() {
Room &room = maze.GenerateRoom(x,y, defaultroom, 0);
if(room.Chest <0> 0.96) {
unsigned sprain = rand()%NItems(BodyParts);
EatLife(BodyParts[sprain].b);
std::printf("You sprain your %s!\n", BodyParts[sprain].a);
}
if(room.Chest <= 0.0) {
std::printf("The chest bursts into pieces!\nEverything it contained is scattered on the ground.\n");
std::srand((y<<12> 0.96) { // pure money is rare.
unsigned moneytype((1.0-std::pow(frand(), 4)) * (NItems(MoneyTypes)-1));
room.items.Money[moneytype] += rand() % long(600/MoneyTypes[moneytype].b);
}
else
room.items.Items.push_front(ItemType());
while(frand() > 0.3);
}
else std::printf("The chest resists your meddling! Try harder.\n");
}
static void Get(std::string what) {
Room &room = maze.GenerateRoom(x,y, defaultroom, 0);
bool all = false;
if(what == "all") { all=true; what=""; }
else if(what.substr(0,4) == "all ") { all=true; what.erase(0,4); }
if(what == "chest" && room.Chest > 0)
{ std::printf("You cannot take the chest.\n"); return; }
std::deque<std> moved = room.items.move(eq, what, all);
if(moved.empty())
std::printf("There %s no %s here!\n",
what[what.size()-1]=='s'?"are":"is", what.c_str());
else {
std::printf("You take ");
for(size_t a=0; a<moved.size(); ++a)
std::printf("%s%s", Sep(a,moved.size()), moved[a].c_str());
std::printf(".\n");
EatLife(moved.size() * 2);
}
}
static void Drop(std::string what) {
Room &room = maze.GenerateRoom(x,y, defaultroom, 0);
bool all = false;
if(what == "all") { all=true; what=""; }
else if(what.substr(0,4) == "all ") { all=true; what.erase(0,4); }
std::deque<std> moved = eq.move(room.items, what, all);
if(moved.empty())
std::printf("You don't have %s!\n", what.c_str());
else {
std::printf("You drop ");
for(size_t a=0; a<moved> 0; ) {
std::printf("[life:%ld]> ", life); fflush(stdout);
std::getline(std::cin, cmd);
if(!std::cin.good() || cmd == "quit") break;
if(cmd == "!") cmd = lastcmd; else lastcmd = cmd;
if(cmd == "i" || cmd == "inv" || cmd == "inventory") Inv();
else if(cmd == "n" || cmd == "north" || cmd == "go n" || cmd == "go north")
{ if(TryMove(0,-1)) { EatLife(eq.burden()); Look(); } }
else if(cmd == "s" || cmd == "south" || cmd == "go s" || cmd == "go south")
{ if(TryMove(0,1)) { EatLife(eq.burden()); Look(); } }
else if(cmd == "w" || cmd == "west" || cmd == "go w" || cmd == "go west")
{ if(TryMove(-1,0)) { EatLife(eq.burden()); Look(); } }
else if(cmd == "e" || cmd == "east" || cmd == "go e" || cmd == "go east")
{ if(TryMove(1,0)) { EatLife(eq.burden()); Look(); } }
else if(cmd == "sw" || cmd == "nw" || cmd == "se" || cmd == "ne")
std::printf("The concept of diagonal movements makes you squeasy.\n");
else if(cmd.substr(0,4) == "wear" || cmd.substr(0,5) == "wield")
std::printf("You are scavenging for survival and not playing an RPG character.\n");
else if(cmd == "l" || cmd == "look") Look();
else if(cmd.substr(0,3) == "la ") LookAt(cmd.substr(3));
else if(cmd.substr(0,8) == "look at ") LookAt(cmd.substr(8));
else if(cmd.substr(0,8) == "look in ") LookAt(cmd.substr(8),true);
else if(cmd.substr(0,5) == "look ") LookAt(cmd.substr(5));
else if(cmd.substr(0,5) == "l at ") LookAt(cmd.substr(5));
else if(cmd.substr(0,5) == "l in ") LookAt(cmd.substr(5),true);
else if(cmd.substr(0,2) == "l ") LookAt(cmd.substr(2));
else if(cmd == "open" || cmd == "open chest" || cmd == "pry") OpenChest();
else if(cmd == "ga") Get("all");
else if(cmd.substr(0,3) == "ga ") Get("all "+cmd.substr(3));
else if(cmd.substr(0,4) == "get ") Get(cmd.substr(4));
else if(cmd.substr(0,5) == "drop ") Drop(cmd.substr(5));
else if(cmd == "help" || cmd == "what" || cmd == "?") goto help;
else std::printf("what?\n");
}
double value = eq.value();
return std::printf(
"%s\n[life:%ld] Game over\nYou managed to collect stuff worth %.2f gold.\n"
"With all your possessions, you purchase %s. You consume your reward eagerly.\nYOU %s\n",
life<0?"You are pulled out from the maze by a supernatural force!":"byebye",
life, value,
Appraise(value).c_str(),
value<10000.0
? "DID NOT SURVIVE. Hint: Learn to judge the value/weight ratio."
: "SURVIVED! CONGRATULATION. ;)") < 0;
}
A pre-compiled Windows binary can be found in this zip:
http://bisqwit.iki.fi/kala/bisqwit-adv.zip
In this game you are a starving adventurer whose goal is to scange the dungeon for artifacts worth enough to buy him a meal -- and to do so before he is too famished to do anything.
EDIT: Improved the parser a bit, motivated by subtle persuation at TVTropes