Skip to content

Instantly share code, notes, and snippets.

@fedor57
Last active December 13, 2017 22:19
Show Gist options
  • Save fedor57/742f8cadb3c0fc0d7595c82414779cbb to your computer and use it in GitHub Desktop.
Save fedor57/742f8cadb3c0fc0d7595c82414779cbb to your computer and use it in GitHub Desktop.
Lift emulation
#include <iostream>
#include <exception>
#include <string>
#include <sstream>
#include <vector>
#include <regex>
#include <thread>
#include <future>
#include <cassert>
using namespace std;
enum EMovingDirection {
Stop = 1,
Down = 2,
Up = 3
};
string DirectionToStr(EMovingDirection direction) {
if (direction == Stop)
return "nowhere";
else if (direction == Down)
return "down";
else if (direction == Up)
return "up";
else
return "unknown";
}
struct TLiftStatus {
int Floor;
EMovingDirection Direction;
};
struct TLiftSettings {
int MinFloor;
int MaxFloor;
int FloorSpeedMs;
};
typedef std::chrono::system_clock Time;
typedef std::chrono::milliseconds ms;
typedef std::chrono::duration<float> fsec;
class ILift {
public:
virtual bool IsValidFloor(int floor) const = 0;
virtual TLiftStatus GetStatus() const = 0;
virtual TLiftSettings GetSettings() const = 0;
virtual void ProcessState() = 0;
virtual bool GoToFloor(int floor) = 0;
};
class TLift: public ILift {
private:
const int MinFloor = 0;
const int MaxFloor = 0;
const int FloorSpeedMs = 0;
const int NowhereFloor = -1;
// State must be correct alltimes
chrono::system_clock::time_point FloorTime = chrono::system_clock::time_point::min();
int Floor = MinFloor;
int GoingToFloor = NowhereFloor - 1;
EMovingDirection Direction = Stop;
private:
void UpdateSpeedAndPosition() {
while (true) {
EMovingDirection oldDirection = Direction;
if (!IsValidFloor(GoingToFloor))
Direction = Stop;
else if (GoingToFloor > Floor)
Direction = Up;
else if (GoingToFloor < Floor)
Direction = Down;
else {
GoingToFloor = NowhereFloor;
Direction = Stop;
}
chrono::system_clock::time_point now = chrono::system_clock::now();
if (Direction != oldDirection)
FloorTime = now;
// Not 100% correct model of a real lift
if (Direction == Stop) {
FloorTime = chrono::system_clock::time_point::min();
break;
}
assert(IsValidFloor(GoingToFloor));
chrono::milliseconds elapsed = chrono::duration_cast<chrono::milliseconds>(now - FloorTime);
if (elapsed.count() < FloorSpeedMs)
break;
if (Direction == Up)
Floor++;
if (Direction == Down)
Floor--;
FloorTime += chrono::milliseconds(FloorSpeedMs);
}
}
public:
virtual bool IsValidFloor(int floor) const {
return (floor >= MinFloor && floor <= MaxFloor);
}
TLift(int minFloor, int maxFloor, int floorSpeedMs)
: MinFloor(minFloor)
, MaxFloor(maxFloor)
, FloorSpeedMs(floorSpeedMs)
, NowhereFloor(minFloor - 1)
, Floor(MinFloor)
, GoingToFloor(NowhereFloor)
, Direction(EMovingDirection::Stop)
{
if (maxFloor <= minFloor)
throw invalid_argument("TLift:: bad min/max floor parameters");
}
public:
virtual TLiftSettings GetSettings() const {
return { MinFloor, MaxFloor, FloorSpeedMs };
}
virtual TLiftStatus GetStatus() const {
return { Floor, Direction };
}
virtual void ProcessState() {
UpdateSpeedAndPosition();
}
virtual bool GoToFloor(int floor) {
if (Direction != EMovingDirection::Stop)
return false;
if (!IsValidFloor(floor))
return false;
if (floor == Floor)
return false;
GoingToFloor = floor; // If lift is stopped then it's going anywhere yet
UpdateSpeedAndPosition();
return true;
}
};
class TLiftControllerBase {
protected:
ILift & Lift;
TLiftSettings Settings;
public:
TLiftControllerBase(ILift & lift)
: Lift(lift)
, Settings(Lift.GetSettings())
{
}
bool IsValidFloor(int floor) {
return (floor >= Settings.MinFloor && floor <= Settings.MaxFloor);
}
};
// In the future refactoring each controller type may have a custom internal
// state and deliver different custom status
class TInLiftController : protected TLiftControllerBase {
public:
TInLiftController(ILift & lift)
: TLiftControllerBase(lift)
{
}
bool PushFloorButton(int floor) {
if (!IsValidFloor(floor))
return false;
return Lift.GoToFloor(floor);
}
string GetStatusStr() {
TLiftStatus status = Lift.GetStatus();
stringstream ss;
ss << "Welcome to the lift! floor: " << status.Floor << ", going: " << DirectionToStr(status.Direction);
return ss.str();
}
};
class TOutLiftController : protected TLiftControllerBase {
private:
int AtFloor = -1;
public:
TOutLiftController(ILift & lift, int floor)
: TLiftControllerBase(lift)
, AtFloor(floor)
{
if (!IsValidFloor(floor))
throw invalid_argument("TLiftController - Wrong floor number");
}
public:
bool PushCallButton() {
return Lift.GoToFloor(AtFloor);
}
string GetStatusStr() {
TLiftStatus status = Lift.GetStatus();
stringstream ss;
ss << "Push button to call the lift. floor: " << status.Floor << ", going: " << DirectionToStr(status.Direction);
return ss.str();
}
};
class TBuilding {
private:
TLift Lift;
TInLiftController LiftController;
vector<TOutLiftController> FloorControllers; // don't modify outside constuctor, so pointers do not invalidate
int controllerOffet = 0;
public:
TBuilding(int minFloor, int maxFloor, int speedFloorMs) // Let's construct the building ;)
: Lift(minFloor, maxFloor, speedFloorMs) // Will throw exception on wrong parameters
, LiftController(Lift)
{
controllerOffet = minFloor;
for (int floor = minFloor; floor <= maxFloor; floor++) {
FloorControllers.emplace_back(Lift, floor);
}
}
TInLiftController * GetInLiftController() {
return &LiftController;
}
TOutLiftController * GetOutLiftController(int floor) {
int pos = floor - controllerOffet;
if (pos < 0 || pos >= FloorControllers.size())
return nullptr;
return &FloorControllers[pos];
}
string GetStatusStr() {
stringstream ss;
ss << "Welcome to the building, we have floors from " << controllerOffet << " to " << controllerOffet + FloorControllers.size() << endl;
ss << "You are welcome to use our lift! Ask \"help\" to get further assistance" << endl;
return ss.str();
}
void ProcessState() {
Lift.ProcessState();
}
};
class TLiftTextController {
private:
TBuilding & Building;
ostream & OutStream;
string LastStatus = "";
TInLiftController * CurrentInLiftController = nullptr;
TOutLiftController * CurrentOutLiftController = nullptr;
string PanelName = "";
bool ExitBuilding = false;
private:
void Help() {
OutStream << "use lift - start using lift inner panel" << endl;
OutStream << "use 1 - start using panel on the 1st floor" << endl;
OutStream << "push 5 - while using lift panel push 5th floor button" << endl;
OutStream << "push call - while using floor panel call the lift" << endl;
OutStream << "show this help" << endl;
}
public:
TLiftTextController(TBuilding & building, ostream & outStream)
: Building(building)
, OutStream(outStream)
, CurrentInLiftController(nullptr)
, CurrentOutLiftController(nullptr)
{
OutStream << Building.GetStatusStr();
}
void ProcessCommand(string command) {
static regex useLiftRegex("use lift", regex_constants::icase);
static regex useFloorRegex("use (-?\\d+)", regex_constants::icase);
static regex pushFloorRegex = regex("push (-?\\d+)", regex_constants::icase);
static regex pushCallRegex = regex("push call", regex_constants::icase);
static regex helpRegex = regex("help", regex_constants::icase);
static regex exitRegex = regex("exit", regex_constants::icase);
if (command.empty())
return;
std::smatch floor_match;
if (regex_match(command, useLiftRegex)) {
CurrentInLiftController = Building.GetInLiftController();
CurrentOutLiftController = nullptr;
PanelName = "Lift panel";
OutStream << "Switched to lift panel" << endl;
return;
}
if (regex_match(command, floor_match, useFloorRegex)) {
if (floor_match.size() == 2) {
int floor = stoi(floor_match[1].str());
auto ctl = Building.GetOutLiftController(floor);
if (ctl != nullptr) {
CurrentOutLiftController = ctl;
CurrentInLiftController = nullptr;
OutStream << "Switched to floor panel at #" << floor << " floor" << endl;
stringstream pn;
pn << "Floor #" << floor << " panel";
PanelName = pn.str();
}
else
Help();
}
return;
}
if (regex_match(command, floor_match, pushFloorRegex)) {
if (CurrentInLiftController != nullptr) {
int floor = stoi(floor_match[1].str());
bool result = CurrentInLiftController->PushFloorButton(floor);
OutStream << "Pushed #" << floor << ", " << (result ? "click" : "nothing happened") << endl;
}
else
Help();
return;
}
if (regex_match(command, pushCallRegex)) {
if (CurrentOutLiftController != nullptr) {
bool result = CurrentOutLiftController->PushCallButton();
OutStream << "Pushed call" << ", " << (result ? "click" : "nothing happened") << endl;
}
else
Help();
return;
}
if (regex_match(command, helpRegex)) {
Help();
return;
}
if (regex_match(command, exitRegex)) {
ExitBuilding = true;
return;
}
Help();
}
bool UpdateStatus() {
string status = "";
if (CurrentInLiftController != nullptr) {
status = CurrentInLiftController->GetStatusStr();
} else if (CurrentOutLiftController != nullptr) {
status = CurrentOutLiftController->GetStatusStr();
}
if (status != LastStatus) {
LastStatus = status;
OutStream << "[ " << PanelName << " ] " << LastStatus << endl;
return true;
}
return false;
}
void ShowPrompt() {
OutStream << "> ";
}
bool DoExitBuilding() const {
return ExitBuilding;
}
};
class TAsyncLineReader {
private:
istream & InStream;
future<string> asyncReader;
public:
string ReadLineSync() {
string s;
getline(InStream, s);
return s;
}
TAsyncLineReader(istream & inStream)
: InStream(inStream) {
}
void StartReading() {
asyncReader = async(launch::async, &TAsyncLineReader::ReadLineSync, this);
}
string ReadLine()
{
string result = "";
if (!asyncReader.valid())
StartReading();
if (asyncReader.wait_for(chrono::seconds(0)) == future_status::ready)
return asyncReader.get();
else
return "";
}
};
int main() {
istream & inStream = cin;
ostream & outStream = cerr;
TBuilding building(-2, 9, 2500);
TAsyncLineReader cmdReader(inStream);
TLiftTextController controller(building, outStream);
controller.ShowPrompt();
cmdReader.StartReading();
while (true) {
string cmd = cmdReader.ReadLine();
if (!cmd.empty()) {
controller.ProcessCommand(cmd);
if (controller.DoExitBuilding())
break;
controller.ShowPrompt();
cmdReader.StartReading();
}
building.ProcessState();
if (controller.UpdateStatus())
controller.ShowPrompt();
this_thread::sleep_for(chrono::milliseconds(100));
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment