Last active
December 13, 2017 22:19
-
-
Save fedor57/742f8cadb3c0fc0d7595c82414779cbb to your computer and use it in GitHub Desktop.
Lift emulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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