I have a very basic "game" where a robot is placed on a small 5x5 board, and commands are given to it. To start, the robot must be placed on the board, then you may command it to move, or rotate left and right. I have tried to use object oriented concepts, but it was not a requirement for my task. I think I have done an okay job, and I would love to know what I have done incorrectly.
Here are a few "requirements" given to me:
- The "place" command takes an x, y position, and a direction or NORTH, EAST, SOUTH, or WEST.
- No other commands may be run unless the robot has been placed
- Move will move the robot forward by 1, in the direction it is facing.
- LEFT and RIGHT will rotate the robot in their respective directions
- REPORT will print the current position, and facing direction of the robot
- Place is a valid command, even after it has already been placed
- Inputs that will cause the robot to fall off the board are to be rejected or ignored
- Invalid or "junk" input is to be ignored
- The robot will take commands as plaintext, the robot must take this text and run respective instructions given that they are valid
Example input/output:
PLACE 1,3,WEST
MOVE
REPORT
Output: 0,3,WEST
and here is my robot.cpp
#include "robot.h"
#include "tabletop.h"
#include "helpers.h"
namespace ToyRobot
{
//used as an interface to supply instructions as text, robot will perform
bool Robot::command(std::string instruction)
{
std::istringstream iss(instruction);
std::vector<std::string> tokens{ std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{} };
if (this->commandList.count(tokens[0]) > 0) //is the first token, a valid command?
{
//command is valid
if (tokens[0] == "PLACE")
{ //check if there are valid arguments
if (tokens.size() < 2)
{
std::cout << "Error! Not enough arguments for 'PLACE'\n";
return false;
}
else
{
try
{
uint8_t arg1 = std::stoi(split(tokens[1], ",")[0]);
uint8_t arg2 = std::stoi(split(tokens[1], ",")[1]);
std::string arg3 = split(tokens[1], ",")[2];
this->place(arg1, arg2, arg3);
}
catch (...)
{
return false;
}
return true;
}
}
else if (tokens[0] == "MOVE")
{
this->move();
}
else if (tokens[0] == "LEFT" || tokens[0] == "RIGHT")
{
this->rotate(tokens[0]);
}
else if (tokens[0] == "REPORT")
{
this->printStatus();
}
return true;
}
else
return false;
}
//checks if a given position is valid (used only by other methods)
bool Robot::isValidPosition(uint8_t x, uint8_t y)
{
if (x < 0 || x > TABLETOP_MAX_X || y < 0 || y > TABLETOP_MAX_Y)
return false;
else
return true;
}
//places robot, ignores invalid positions
bool Robot::place(uint8_t x_place_pos, uint8_t y_place_pos, std::string facingDirection)
{
if (x_place_pos < 0 || x_place_pos > TABLETOP_MAX_X || y_place_pos < 0 || y_place_pos > TABLETOP_MAX_Y)
return false;
if (this->facingDirections.count(facingDirection) == 0) //check if given facing direction was valid
return false;
this->x_pos = x_place_pos;
this->y_pos = y_place_pos;
this->facingDirection = this->facingDirections[facingDirection];
this->placed = true;
return true;
}
//moves robot forward by one, ignored invalid movements
bool Robot::move()
{
if (this->placed)
{
uint8_t sim_x = this->x_pos;
uint8_t sim_y = this->y_pos;
//simulate movement
if (facingDirection == 0)
sim_y += 1;
else if (facingDirection == 1)
sim_x += 1;
else if (facingDirection == 2)
sim_y -= 1;
else if (facingDirection == 3)
sim_x -= 1;
if (isValidPosition(sim_x, sim_y))//if it was valid, set and return true
{
this->x_pos = sim_x;
this->y_pos = sim_y;
return true;
}
else //invalid move (would be out of bounds)
return false;
}
else //not placed
return false;
}
//rotates robot given a direction string
bool Robot::rotate(std::string direction)
{
if (this->placed)
{
uint8_t sim_direction = this->facingDirection;
if (direction == "LEFT")
sim_direction = (sim_direction + 3) % 4; //rotate left
else if (direction == "RIGHT")
sim_direction = (sim_direction + 1) % 4; //rotate right
else
return false; //invalid input
this->facingDirection = sim_direction;
return true;
}
else //not placed
return false;
}
void Robot::printStatus()
{
if (this->placed)
std::cout << int(this->x_pos) << ',' << int(this->y_pos) << ',' << (this->reversedDirections[this->facingDirection]) << "\n";
else
std::cout << "Robot is not yet placed on the tabletop!\n";
}
}
robot.h
#pragma once
#include "stdafx.h"
namespace ToyRobot
{
class Robot
{
private:
bool placed = false;
uint8_t x_pos = NULL;
uint8_t y_pos = NULL;
uint8_t facingDirection = NULL;
const std::unordered_set<std::string> commandList = { "PLACE","MOVE","LEFT","RIGHT","REPORT" };
std::unordered_map <std::string, int> facingDirections
= { {"NORTH", 0}, {"EAST", 1},
{"SOUTH", 2}, {"WEST", 3} };
std::unordered_map <int, std::string> reversedDirections
= { {0, "NORTH"}, {1, "EAST"},
{2, "SOUTH"}, {3, "WEST"} };
bool isValidPosition(uint8_t, uint8_t);
public:
Robot() //constructor
{
}
bool command(std::string);
bool place(uint8_t, uint8_t, std::string);
bool move();
bool rotate(std::string);
void printStatus();
};
}
helpers.cpp
#include "stdafx.h"
#include "helpers.h"
//python's "split" function, implemented in C++. returns a vector of split std::strings by a specified delimiter
std::vector<std::string> split(const std::string& in, const std::string& delim)
{
using std::string;
using std::vector;
string::size_type start = in.find_first_not_of(delim), end = 0;
vector<string> out;
while (start != in.npos)
{
end = in.find_first_of(delim, start);
if (end == in.npos)
{
out.push_back(in.substr(start));
break;
}
else
{
out.push_back(in.substr(start, end - start));
}
start = in.find_first_not_of(delim, end);
}
return out;
}
helpers.h
#pragma once
#include "stdafx.h"
std::vector<std::string> split(const std::string& in, const std::string& delim);
tabletop.h (there is no tabletop.cpp, as there is no need for it)
#pragma once
#include "stdafx.h"
constexpr auto TABLETOP_MAX_X = 4;
constexpr auto TABLETOP_MAX_Y = 4;
//0,0 is south west corner https://i.imgur.com/pm2XVHx.png
//Tabletop is never used, but it is here if required
class Tabletop
{
private:
const uint8_t x_len = TABLETOP_MAX_X;
const uint8_t y_len = TABLETOP_MAX_Y;
public:
};
and finally my stdafx.h
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <sstream>
#include <algorithm>
#include <iterator>
My robot header and implementation is in a namespace, as it is to be compiled into a library.
How is my project structure? Thanks