Originally a graduate admissions assignment for SMU Guildhall’s Game Design Programming track, this is a data construct and string manipulation project that seeks to test C++ skill levels.
The program will load from “resource.txt” on start, reading each line as follows: the first word is the name of resource, and each subsequent word is a connected resource. Each resource is a node on a directed graph, and if any new connected resource is encountered, the program will automatically create a node for it on the graph.
The program displays a representation of the directed graph, and a table which shows which nodes are usable according to the assignment. The player can delete a node by name, or quit the program at any time.
The original assignment is below…
Assignment:
Create a Resource Manager for a Real Time Strategy (RTS) game. Your program manages a “directed graph” (a system of nodes and links between them going in one direction, much like web pages and their links). On startup, your program reads a file “resource.txt” (from the current directory) which describes resources and the resources they depend on. The file resource.txt could, for example, contain (exactly 4 lines):
handgun bullets
bullets ore
bombs ore
turret bullets
The first line says that there is a link from a node called “handgun” to a node called ‘bullet”. This means for a handgun to be a useable, it relies on the resource bullets.
Requirements:
- Your program should work with any amount of nodes and any amount of links between them (your program may be tested with a larger resource.txt made by SMU Guildhall faculty). To this end, you should represent nodes with a Node class, and the set of links for a single node using an STL container of your choice.
- Your program should loop and display two items on the screen: (1) a current view of the graph, and (2) a list of each node and whether it is usable or not. For any current node, if any of the nodes it relies on get deleted that node becomes unusable.
- Your program should handle two types of input. A user should be allowed to delete a node and quit at any time. If using a console program, input should be “q” for quit, or the name of a node to delete it. When you delete a node, make sure any links to it are deleted correctly too. Be certain to have correct memory management for these operations ensuring no memory leaks when the program exits.
Skills Demonstrated:
- STL Familiarity
- STL Containers (Maps, Vectors, and Sets)
- Iteration Over Containers Using “auto” Keyword
- File Stream, String Stream, and Input Stream Understanding
- Typename Ambiguity with Templates
- Pointers
- I/O Familiarity
- Namespace Familiarity
Download:
You can download the source code here. The zip includes a “resource.txt” file which can be edited to test for new inputs.
Header File:
#pragma once
#include <map>
#include <string>
#include <vector>
using std::string;
using std::map;
using std::vector;
class Node {
public:
string resourceName;
bool useable = true; // Unstable marker for faster check time
map<string, Node>* linkedNodes = new map<string, Node>;
};
map<string, Node>* nodes = new map<string, Node>; // Dictionary for storing nodes <resource name, node>
void AddNodeToResources(string resourceName);
void AddNodeToResources(string resourceName, vector<string> linkedResources);
Node CreateNode(string resourceName);
void DeleteNode(string resourceName, map<string, Node>* map);
bool CheckNodeExists(const string& resource, map<string, Node>* map);
bool CheckUseable(Node resource);
void PrintResourceMap(map<string, Node>* map);
void PrintResourceMap_LinkedResources(map<string, Node> map);
void PrintNodeList(map<string, Node>* map);
void ExitProgram();
template <typename T>
vector<T> RemoveFirstElemOfVector(vector<T> vec);
CPP File:
// Author: Orrett Coke
#include <iostream>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iterator>
#include "RTS Resource Manager.h"
using std::cout;
using std::cin;
using std::ifstream;
using std::getline;
using std::istringstream;
using std::istream_iterator;
int main()
{
string command;
ifstream rscFile("resource.txt");
string fileLine;
nodes = new map<string, Node>; // Holds resource nodes
if (rscFile.is_open()) {
// Pull resources from text file line-by-line and add to resources
while (getline(rscFile, fileLine))
{
// Use string stream to seperate each file line from a string into a vector of words
istringstream stringStream(fileLine);
vector<string> results((istream_iterator<string>(stringStream)),
istream_iterator<string>());
if (results.size() == 1)
AddNodeToResources(results[0]);
else
AddNodeToResources(results[0], RemoveFirstElemOfVector(results)); // Remove the first word to get the linked resources in this file line
}
rscFile.close();
}
else {
cout << "Cannot Find/Open resource.txt\n";
return 0;
}
while (true)
{
// Show Graph
cout << "\n========================\n";
cout << "Resource Directed Graph:\n";
cout << "========================\n";
if (nodes->empty())
cout << "No resources.\n";
else
PrintResourceMap(nodes);
// Show Useability
cout << "\n========================\n";
cout << "Resource Usability:\n";
cout << "========================\n";
if (nodes->empty())
cout << "No resources.\n";
else
PrintNodeList(nodes);
while (true) // Input Loop
{
// Wait for command
cout << "\nPlease type a resource name for deletion, or q to exit:\n";
cin >> command;
// Quit command
if (command == "q" || command == "Q")
ExitProgram();
// Delete the specified node if it exists
if (CheckNodeExists(command, nodes))
{
DeleteNode(command, nodes);
break;
}
else
{
cout << "\nERROR: A node of this name does not exist...\n\n";
}
}
}
ExitProgram();
}
void ExitProgram()
{
cout << "\n**************\n";
cout << "Program Exit\n";
cout << "**************\n";
exit(0);
}
void AddNodeToResources(string resourceName) {
// If node does not exist, create and assign node
if (!CheckNodeExists(resourceName, nodes))
{
nodes->emplace(resourceName, CreateNode(resourceName));
}
}
void AddNodeToResources(string resourceName, vector<string> linkedResources) {
// Check if node already exists (in resources dictionary)
if (CheckNodeExists(resourceName, nodes))
{
// Add linked resources to resources
for (auto const& elem : linkedResources)
{
// If linked resource has not been encountered (is not in resources dictionary), add it to resources as well
AddNodeToResources(elem);
// Add linked resources to the node's linked resources list
nodes->find(resourceName)->second.linkedNodes->emplace(elem, nodes->find(elem)->second);
}
}
else
{
// Add new node to resources, as well as linked nodes
nodes->emplace(resourceName, CreateNode(resourceName));
for (auto const& elem : linkedResources)
{
AddNodeToResources(elem);
nodes->find(resourceName)->second.linkedNodes->emplace(elem, nodes->find(elem)->second);
}
}
}
Node CreateNode(string resourceName) {
// Create and assign node
Node tempNode;
tempNode.resourceName = resourceName;
return tempNode;
}
void DeleteNode(string resourceName, map<string, Node>* map) {
// Find and delete node
map->erase(resourceName);
// Update useability
for (auto elem : *map)
{
// Change useability if a node depends on the deleted resource (deleted resource is in another node's linked resources)
if (CheckNodeExists(resourceName, elem.second.linkedNodes))
{
map->find(elem.first)->second.useable = false; // Change useability
map->find(elem.first)->second.linkedNodes->erase(resourceName); // Delete node in linked resources, too
}
}
}
bool CheckNodeExists(const string& resource, map<string, Node>* map) {
return map->find(resource) != map->end();
}
// Check if node's dependencies (linked resources) have been deleted
bool CheckUseable(Node resource)
{
return resource.useable;
}
void PrintResourceMap(map<string, Node>* map) {
// Print map elements
for (auto elem : *map)
{
cout << elem.first;
if (!elem.second.linkedNodes->empty()) // Print linked resources if they exist (print connected nodes, a.k.a. dependencies)
{
cout << " ->";
PrintResourceMap_LinkedResources(*(elem.second.linkedNodes));
}
cout << "\n";
}
}
// Print map elements
void PrintResourceMap_LinkedResources(map<string, Node> map) {
cout << " " << map.begin()->first; // Print first linked resource (connected node, a.k.a. resource dependency)
// Iterate through and print remaining linked resources
map.erase(map.begin());
for (auto elem : map)
{
cout << ", ";
cout << elem.first;
}
}
// Print list of nodes with usability
void PrintNodeList(map<string, Node>* map) {
// Print map elements
for (auto elem : *map)
{
cout << elem.first;
if (CheckUseable(elem.second))
cout << " (Y)\n";
else
cout << " (N)\n";
}
}
template <typename T>
vector<T> RemoveFirstElemOfVector(vector<T> vec) {
vec.erase(vec.begin());
return vec;
}