├── images ├── user.png ├── chatbot.png ├── sf_bridge.jpg ├── chatbot_demo.gif ├── chatbot_demo.png └── sf_bridge_inner.jpg ├── src ├── graphedge.cpp ├── graphedge.h ├── graphnode.cpp ├── chatbot.h ├── chatlogic.h ├── graphnode.h ├── chatgui.h ├── answergraph.txt ├── chatbot.cpp ├── chatgui.cpp └── chatlogic.cpp ├── CMakeLists.txt └── README.md /images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/user.png -------------------------------------------------------------------------------- /images/chatbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/chatbot.png -------------------------------------------------------------------------------- /images/sf_bridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/sf_bridge.jpg -------------------------------------------------------------------------------- /images/chatbot_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/chatbot_demo.gif -------------------------------------------------------------------------------- /images/chatbot_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/chatbot_demo.png -------------------------------------------------------------------------------- /images/sf_bridge_inner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mnslarcher/CppND-Memory-Management-Chatbot/master/images/sf_bridge_inner.jpg -------------------------------------------------------------------------------- /src/graphedge.cpp: -------------------------------------------------------------------------------- 1 | #include "graphedge.h" 2 | #include "graphnode.h" 3 | 4 | GraphEdge::GraphEdge(int id) { _id = id; } 5 | 6 | void GraphEdge::SetChildNode(GraphNode *childNode) { _childNode = childNode; } 7 | 8 | void GraphEdge::SetParentNode(GraphNode *parentNode) { 9 | _parentNode = parentNode; 10 | } 11 | 12 | void GraphEdge::AddToken(std::string token) { _keywords.push_back(token); } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11.3) 2 | 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | project(Membot) 6 | 7 | find_package(wxWidgets REQUIRED COMPONENTS core base) 8 | include(${wxWidgets_USE_FILE}) 9 | 10 | file(GLOB project_SRCS src/*.cpp) 11 | 12 | add_executable(membot ${project_SRCS}) 13 | target_link_libraries(membot ${wxWidgets_LIBRARIES}) 14 | target_include_directories(membot PRIVATE ${wxWidgets_INCLUDE_DIRS}) 15 | -------------------------------------------------------------------------------- /src/graphedge.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHEDGE_H_ 2 | #define GRAPHEDGE_H_ 3 | 4 | #include 5 | #include 6 | 7 | class GraphNode; // forward declaration 8 | 9 | class GraphEdge { 10 | private: 11 | // data handles (not owned) 12 | GraphNode *_childNode; 13 | GraphNode *_parentNode; 14 | 15 | // proprietary members 16 | int _id; 17 | std::vector 18 | _keywords; // list of topics associated with this edge 19 | 20 | public: 21 | // constructor / desctructor 22 | GraphEdge(int id); 23 | 24 | // getter / setter 25 | int GetID() { return _id; } 26 | void SetChildNode(GraphNode *childNode); 27 | void SetParentNode(GraphNode *parentNode); 28 | GraphNode *GetChildNode() { return _childNode; } 29 | std::vector GetKeywords() { return _keywords; } 30 | 31 | // proprietary functions 32 | void AddToken(std::string token); 33 | }; 34 | 35 | #endif /* GRAPHEDGE_H_ */ -------------------------------------------------------------------------------- /src/graphnode.cpp: -------------------------------------------------------------------------------- 1 | #include "graphnode.h" 2 | #include "graphedge.h" 3 | 4 | GraphNode::GraphNode(int id) { _id = id; } 5 | 6 | GraphNode::~GraphNode() { 7 | //// STUDENT CODE 8 | //// 9 | 10 | // delete _chatBot; 11 | 12 | //// 13 | //// EOF STUDENT CODE 14 | } 15 | 16 | void GraphNode::AddToken(std::string token) { _answers.push_back(token); } 17 | 18 | void GraphNode::AddEdgeToParentNode(GraphEdge *edge) { 19 | _parentEdges.push_back(edge); 20 | } 21 | 22 | void GraphNode::AddEdgeToChildNode(GraphEdge *edge) { 23 | _childEdges.push_back(edge); 24 | } 25 | 26 | //// STUDENT CODE 27 | //// 28 | void GraphNode::MoveChatbotHere(ChatBot *chatbot) { 29 | _chatBot = chatbot; 30 | _chatBot->SetCurrentNode(this); 31 | } 32 | 33 | void GraphNode::MoveChatbotToNewNode(GraphNode *newNode) { 34 | newNode->MoveChatbotHere(_chatBot); 35 | _chatBot = nullptr; // invalidate pointer at source 36 | } 37 | //// 38 | //// EOF STUDENT CODE 39 | 40 | GraphEdge *GraphNode::GetChildEdgeAtIndex(int index) { 41 | //// STUDENT CODE 42 | //// 43 | 44 | return _childEdges[index]; 45 | 46 | //// 47 | //// EOF STUDENT CODE 48 | } -------------------------------------------------------------------------------- /src/chatbot.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATBOT_H_ 2 | #define CHATBOT_H_ 3 | 4 | #include 5 | #include 6 | 7 | class GraphNode; // forward declaration 8 | class ChatLogic; // forward declaration 9 | 10 | class ChatBot { 11 | private: 12 | // data handles (owned) 13 | wxBitmap *_image; // avatar image 14 | 15 | // data handles (not owned) 16 | GraphNode *_currentNode; 17 | GraphNode *_rootNode; 18 | ChatLogic *_chatLogic; 19 | 20 | // proprietary functions 21 | int ComputeLevenshteinDistance(std::string s1, std::string s2); 22 | 23 | public: 24 | // constructors / destructors 25 | ChatBot(); // constructor WITHOUT memory allocation 26 | ChatBot(std::string filename); // constructor WITH memory allocation 27 | ~ChatBot(); 28 | 29 | //// STUDENT CODE 30 | //// 31 | 32 | //// 33 | //// EOF STUDENT CODE 34 | 35 | // getters / setters 36 | void SetCurrentNode(GraphNode *node); 37 | void SetRootNode(GraphNode *rootNode) { _rootNode = rootNode; } 38 | void SetChatLogicHandle(ChatLogic *chatLogic) { _chatLogic = chatLogic; } 39 | wxBitmap *GetImageHandle() { return _image; } 40 | 41 | // communication 42 | void ReceiveMessageFromUser(std::string message); 43 | }; 44 | 45 | #endif /* CHATBOT_H_ */ -------------------------------------------------------------------------------- /src/chatlogic.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATLOGIC_H_ 2 | #define CHATLOGIC_H_ 3 | 4 | #include "chatgui.h" 5 | #include 6 | #include 7 | 8 | // forward declarations 9 | class ChatBot; 10 | class GraphEdge; 11 | class GraphNode; 12 | 13 | class ChatLogic { 14 | private: 15 | //// STUDENT CODE 16 | //// 17 | 18 | // data handles (owned) 19 | std::vector _nodes; 20 | std::vector _edges; 21 | 22 | //// 23 | //// EOF STUDENT CODE 24 | 25 | // data handles (not owned) 26 | GraphNode *_currentNode; 27 | ChatBot *_chatBot; 28 | ChatBotPanelDialog *_panelDialog; 29 | 30 | // proprietary type definitions 31 | typedef std::vector> tokenlist; 32 | 33 | // proprietary functions 34 | template 35 | void AddAllTokensToElement(std::string tokenID, tokenlist &tokens, 36 | T &element); 37 | 38 | public: 39 | // constructor / destructor 40 | ChatLogic(); 41 | ~ChatLogic(); 42 | 43 | // getter / setter 44 | void SetPanelDialogHandle(ChatBotPanelDialog *panelDialog); 45 | void SetChatbotHandle(ChatBot *chatbot); 46 | 47 | // proprietary functions 48 | void LoadAnswerGraphFromFile(std::string filename); 49 | void SendMessageToChatbot(std::string message); 50 | void SendMessageToUser(std::string message); 51 | wxBitmap *GetImageFromChatbot(); 52 | }; 53 | 54 | #endif /* CHATLOGIC_H_ */ -------------------------------------------------------------------------------- /src/graphnode.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHNODE_H_ 2 | #define GRAPHNODE_H_ 3 | 4 | #include "chatbot.h" 5 | #include 6 | #include 7 | 8 | // forward declarations 9 | class GraphEdge; 10 | 11 | class GraphNode { 12 | private: 13 | //// STUDENT CODE 14 | //// 15 | 16 | // data handles (owned) 17 | std::vector _childEdges; // edges to subsequent nodes 18 | 19 | // data handles (not owned) 20 | std::vector _parentEdges; // edges to preceding nodes 21 | ChatBot *_chatBot; 22 | 23 | //// 24 | //// EOF STUDENT CODE 25 | 26 | // proprietary members 27 | int _id; 28 | std::vector _answers; 29 | 30 | public: 31 | // constructor / destructor 32 | GraphNode(int id); 33 | ~GraphNode(); 34 | 35 | // getter / setter 36 | int GetID() { return _id; } 37 | int GetNumberOfChildEdges() { return _childEdges.size(); } 38 | GraphEdge *GetChildEdgeAtIndex(int index); 39 | std::vector GetAnswers() { return _answers; } 40 | int GetNumberOfParents() { return _parentEdges.size(); } 41 | 42 | // proprietary functions 43 | void AddToken(std::string token); // add answers to list 44 | void AddEdgeToParentNode(GraphEdge *edge); 45 | void AddEdgeToChildNode(GraphEdge *edge); 46 | 47 | //// STUDENT CODE 48 | //// 49 | 50 | void MoveChatbotHere(ChatBot *chatbot); 51 | 52 | //// 53 | //// EOF STUDENT CODE 54 | 55 | void MoveChatbotToNewNode(GraphNode *newNode); 56 | }; 57 | 58 | #endif /* GRAPHNODE_H_ */ -------------------------------------------------------------------------------- /src/chatgui.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATGUI_H_ 2 | #define CHATGUI_H_ 3 | 4 | #include 5 | #include 6 | 7 | class ChatLogic; // forward declaration 8 | 9 | // middle part of the window containing the dialog between user and chatbot 10 | class ChatBotPanelDialog : public wxScrolledWindow { 11 | private: 12 | // control elements 13 | wxBoxSizer *_dialogSizer; 14 | wxBitmap _image; 15 | 16 | //// STUDENT CODE 17 | //// 18 | 19 | std::unique_ptr _chatLogic; 20 | 21 | //// 22 | //// EOF STUDENT CODE 23 | 24 | public: 25 | // constructor / destructor 26 | ChatBotPanelDialog(wxWindow *parent, wxWindowID id); 27 | ~ChatBotPanelDialog(); 28 | 29 | // getter / setter 30 | ChatLogic *GetChatLogicHandle() { return _chatLogic.get(); } 31 | 32 | // events 33 | void paintEvent(wxPaintEvent &evt); 34 | void paintNow(); 35 | void render(wxDC &dc); 36 | 37 | // proprietary functions 38 | void AddDialogItem(wxString text, bool isFromUser = true); 39 | void PrintChatbotResponse(std::string response); 40 | 41 | DECLARE_EVENT_TABLE() 42 | }; 43 | 44 | // dialog item shown in ChatBotPanelDialog 45 | class ChatBotPanelDialogItem : public wxPanel { 46 | private: 47 | // control elements 48 | wxStaticBitmap *_chatBotImg; 49 | wxStaticText *_chatBotTxt; 50 | 51 | public: 52 | // constructor / destructor 53 | ChatBotPanelDialogItem(wxPanel *parent, wxString text, bool isFromUser); 54 | }; 55 | 56 | // frame containing all control elements 57 | class ChatBotFrame : public wxFrame { 58 | private: 59 | // control elements 60 | ChatBotPanelDialog *_panelDialog; 61 | wxTextCtrl *_userTextCtrl; 62 | 63 | // events 64 | void OnEnter(wxCommandEvent &WXUNUSED(event)); 65 | 66 | public: 67 | // constructor / desctructor 68 | ChatBotFrame(const wxString &title); 69 | }; 70 | 71 | // control panel for background image display 72 | class ChatBotFrameImagePanel : public wxPanel { 73 | // control elements 74 | wxBitmap _image; 75 | 76 | public: 77 | // constructor / desctructor 78 | ChatBotFrameImagePanel(wxFrame *parent); 79 | 80 | // events 81 | void paintEvent(wxPaintEvent &evt); 82 | void paintNow(); 83 | void render(wxDC &dc); 84 | 85 | DECLARE_EVENT_TABLE() 86 | }; 87 | 88 | // wxWidgets app that hides main() 89 | class ChatBotApp : public wxApp { 90 | public: 91 | // events 92 | virtual bool OnInit(); 93 | }; 94 | 95 | #endif /* CHATGUI_H_ */ 96 | -------------------------------------------------------------------------------- /src/answergraph.txt: -------------------------------------------------------------------------------- 1 | 2 | // define all graph nodes 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | // connect nodes with edges 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/chatbot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "chatbot.h" 7 | #include "chatlogic.h" 8 | #include "graphedge.h" 9 | #include "graphnode.h" 10 | 11 | // constructor WITHOUT memory allocation 12 | ChatBot::ChatBot() { 13 | // invalidate data handles 14 | _image = nullptr; 15 | _chatLogic = nullptr; 16 | _rootNode = nullptr; 17 | } 18 | 19 | // constructor WITH memory allocation 20 | ChatBot::ChatBot(std::string filename) { 21 | std::cout << "ChatBot Constructor" << std::endl; 22 | 23 | // invalidate data handles 24 | _chatLogic = nullptr; 25 | _rootNode = nullptr; 26 | 27 | // load image into heap memory 28 | _image = new wxBitmap(filename, wxBITMAP_TYPE_PNG); 29 | } 30 | 31 | ChatBot::~ChatBot() { 32 | std::cout << "ChatBot Destructor" << std::endl; 33 | 34 | // deallocate heap memory 35 | if (_image != NULL) // Attention: wxWidgets used NULL and not nullptr 36 | { 37 | delete _image; 38 | _image = NULL; 39 | } 40 | } 41 | 42 | //// STUDENT CODE 43 | //// 44 | 45 | //// 46 | //// EOF STUDENT CODE 47 | 48 | void ChatBot::ReceiveMessageFromUser(std::string message) { 49 | // loop over all edges and keywords and compute Levenshtein distance to query 50 | typedef std::pair EdgeDist; 51 | std::vector levDists; // format is 52 | 53 | for (size_t i = 0; i < _currentNode->GetNumberOfChildEdges(); ++i) { 54 | GraphEdge *edge = _currentNode->GetChildEdgeAtIndex(i); 55 | for (auto keyword : edge->GetKeywords()) { 56 | EdgeDist ed{edge, ComputeLevenshteinDistance(keyword, message)}; 57 | levDists.push_back(ed); 58 | } 59 | } 60 | 61 | // select best fitting edge to proceed along 62 | GraphNode *newNode; 63 | if (levDists.size() > 0) { 64 | // sort in ascending order of Levenshtein distance (best fit is at the top) 65 | std::sort(levDists.begin(), levDists.end(), 66 | [](const EdgeDist &a, const EdgeDist &b) { 67 | return a.second < b.second; 68 | }); 69 | newNode = levDists.at(0).first->GetChildNode(); // after sorting the best 70 | // edge is at first position 71 | } else { 72 | // go back to root node 73 | newNode = _rootNode; 74 | } 75 | 76 | // tell current node to move chatbot to new node 77 | _currentNode->MoveChatbotToNewNode(newNode); 78 | } 79 | 80 | void ChatBot::SetCurrentNode(GraphNode *node) { 81 | // update pointer to current node 82 | _currentNode = node; 83 | 84 | // select a random node answer (if several answers should exist) 85 | std::vector answers = _currentNode->GetAnswers(); 86 | std::mt19937 generator(int(std::time(0))); 87 | std::uniform_int_distribution dis(0, answers.size() - 1); 88 | std::string answer = answers.at(dis(generator)); 89 | 90 | // send selected node answer to user 91 | _chatLogic->SendMessageToUser(answer); 92 | } 93 | 94 | int ChatBot::ComputeLevenshteinDistance(std::string s1, std::string s2) { 95 | // convert both strings to upper-case before comparing 96 | std::transform(s1.begin(), s1.end(), s1.begin(), ::toupper); 97 | std::transform(s2.begin(), s2.end(), s2.begin(), ::toupper); 98 | 99 | // compute Levenshtein distance measure between both strings 100 | const size_t m(s1.size()); 101 | const size_t n(s2.size()); 102 | 103 | if (m == 0) 104 | return n; 105 | if (n == 0) 106 | return m; 107 | 108 | size_t *costs = new size_t[n + 1]; 109 | 110 | for (size_t k = 0; k <= n; k++) 111 | costs[k] = k; 112 | 113 | size_t i = 0; 114 | for (std::string::const_iterator it1 = s1.begin(); it1 != s1.end(); 115 | ++it1, ++i) { 116 | costs[0] = i + 1; 117 | size_t corner = i; 118 | 119 | size_t j = 0; 120 | for (std::string::const_iterator it2 = s2.begin(); it2 != s2.end(); 121 | ++it2, ++j) { 122 | size_t upper = costs[j + 1]; 123 | if (*it1 == *it2) { 124 | costs[j + 1] = corner; 125 | } else { 126 | size_t t(upper < corner ? upper : corner); 127 | costs[j + 1] = (costs[j] < t ? costs[j] : t) + 1; 128 | } 129 | 130 | corner = upper; 131 | } 132 | } 133 | 134 | int result = costs[n]; 135 | delete[] costs; 136 | 137 | return result; 138 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPPND: Memory Management Chatbot 2 | 3 | This is the project for the third course in the [Udacity C++ Nanodegree Program](https://www.udacity.com/course/c-plus-plus-nanodegree--nd213): Memory Management. 4 | 5 | 6 | 7 | The ChatBot code creates a dialogue where users can ask questions about some aspects of memory management in C++. After the knowledge base of the chatbot has been loaded from a text file, a knowledge graph representation is created in computer memory, where chatbot answers represent the graph nodes and user queries represent the graph edges. After a user query has been sent to the chatbot, the Levenshtein distance is used to identify the most probable answer. The code is fully functional as-is and uses raw pointers to represent the knowledge graph and interconnections between objects throughout the project. 8 | 9 | In this project you will analyze and modify the program. Although the program can be executed and works as intended, no advanced concepts as discussed in this course have been used; there are currently no smart pointers, no move semantics and not much thought has been given to ownership or memory allocation. 10 | 11 | Your goal is to use the course knowledge to optimize the ChatBot program from a memory management perspective. There are a total of five specific tasks to be completed, which are detailed below. 12 | 13 | ## Dependencies for Running Locally 14 | * cmake >= 3.11 15 | * All OSes: [click here for installation instructions](https://cmake.org/install/) 16 | * make >= 4.1 (Linux, Mac), 3.81 (Windows) 17 | * Linux: make is installed by default on most Linux distros 18 | * Mac: [install Xcode command line tools to get make](https://developer.apple.com/xcode/features/) 19 | * Windows: [Click here for installation instructions](http://gnuwin32.sourceforge.net/packages/make.htm) 20 | * gcc/g++ >= 5.4 21 | * Linux: gcc / g++ is installed by default on most Linux distros 22 | * Mac: same deal as make - [install Xcode command line tools](https://developer.apple.com/xcode/features/) 23 | * Windows: recommend using [MinGW](http://www.mingw.org/) 24 | * wxWidgets >= 3.0 25 | * Linux: `sudo apt-get install libwxgtk3.0-dev libwxgtk3.0-0v5-dbg` 26 | * Mac: There is a [homebrew installation available](https://formulae.brew.sh/formula/wxmac). 27 | * Installation instructions can be found [here](https://wiki.wxwidgets.org/Install). Some version numbers may need to be changed in instructions to install v3.0 or greater. 28 | 29 | ## Basic Build Instructions 30 | 31 | 1. Clone this repo. 32 | 2. Make a build directory in the top level directory: `mkdir build && cd build` 33 | 3. Compile: `cmake .. && make` 34 | 4. Run it: `./membot`. 35 | 36 | ## Project Task Details 37 | 38 | Currently, the program crashes when you close the window. There is a small bug hidden somewhere, which has something to do with improper memory management. So your first warm-up task will be to find this bug and remove it. This should familiarize you with the code and set you up for the rest of the upcoming tasks. Have fun debugging! 39 | 40 | Aside from the bug mentioned above, there are five additional major student tasks in the Memory Management chatbot project, which are: 41 | 42 | ### Task 1 : Exclusive Ownership 1 43 | In file `chatgui.h` / `chatgui.cpp`, make `_chatLogic` an exclusive resource to class `ChatbotPanelDialog` using an appropriate smart pointer. Where required, make changes to the code such that data structures and function parameters reflect the new structure. 44 | 45 | ### Task 2 : The Rule Of Five 46 | In file `chatbot.h` / `chatbot.cpp`, make changes to the class `ChatBot` such that it complies with the Rule of Five. Make sure to properly allocate / deallocate memory resources on the heap and also copy member data where it makes sense to you. In each of the methods (e.g. the copy constructor), print a string of the type "ChatBot Copy Constructor" to the console so that you can see which method is called in later examples. 47 | 48 | ### Task 3 : Exclusive Ownership 2 49 | In file `chatlogic.h` / `chatlogic.cpp`, adapt the vector `_nodes` in a way that the instances of `GraphNodes` to which the vector elements refer are exclusively owned by the class `ChatLogic`. Use an appropriate type of smart pointer to achieve this. Where required, make changes to the code such that data structures and function parameters reflect the changes. When passing the `GraphNode` instances to functions, make sure to not transfer ownership and try to contain the changes to class `ChatLogic` where possible. 50 | 51 | ### Task 4 : Moving Smart Pointers 52 | 53 | In files `chatlogic.h` / `chatlogic.cpp` and `graphnodes.h` / `graphnodes.cpp` change the ownership of all instances of `GraphEdge` in a way such that each instance of `GraphNode` exclusively owns the outgoing `GraphEdges` and holds non-owning references to incoming `GraphEdges`. Use appropriate smart pointers and where required, make changes to the code such that data structures and function parameters reflect the changes. When transferring ownership from class `ChatLogic`, where all instances of `GraphEdge` are created, into instances of `GraphNode`, make sure to use move semantics. 54 | 55 | ### Task 5 : Moving the ChatBot 56 | 57 | In file `chatlogic.cpp`, create a local `ChatBot` instance on the stack at the bottom of function `LoadAnswerGraphFromFile`. Then, use move semantics to pass the `ChatBot` instance into the root node. Make sure that `ChatLogic` has no ownership relation to the `ChatBot` instance and thus is no longer responsible for memory allocation and deallocation. Note that the member `_chatBot` remains so it can be used as a communication handle between GUI and `ChatBot` instance. Make all required changes in files `chatlogic.h` / `chatlogic.cpp` and `graphnode.h` / `graphnode.cpp`. When the program is executed, messages on which part of the Rule of Five components of `ChatBot` is called should be printed to the console. When sending a query to the `ChatBot`, the output should look like the following: 58 | 59 | ``` 60 | ChatBot Constructor 61 | ChatBot Move Constructor 62 | ChatBot Move Assignment Operator 63 | ChatBot Destructor 64 | ChatBot Destructor 65 | ``` -------------------------------------------------------------------------------- /src/chatgui.cpp: -------------------------------------------------------------------------------- 1 | #include "chatgui.h" 2 | #include "chatbot.h" 3 | #include "chatlogic.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // size of chatbot window 10 | const int width = 414; 11 | const int height = 736; 12 | 13 | // wxWidgets APP 14 | IMPLEMENT_APP(ChatBotApp); 15 | 16 | std::string dataPath = "../"; 17 | std::string imgBasePath = dataPath + "images/"; 18 | 19 | bool ChatBotApp::OnInit() { 20 | // create window with name and show it 21 | ChatBotFrame *chatBotFrame = new ChatBotFrame(wxT("Udacity ChatBot")); 22 | chatBotFrame->Show(true); 23 | 24 | return true; 25 | } 26 | 27 | // wxWidgets FRAME 28 | ChatBotFrame::ChatBotFrame(const wxString &title) 29 | : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(width, height)) { 30 | // create panel with background image 31 | ChatBotFrameImagePanel *ctrlPanel = new ChatBotFrameImagePanel(this); 32 | 33 | // create controls and assign them to control panel 34 | _panelDialog = new ChatBotPanelDialog(ctrlPanel, wxID_ANY); 35 | 36 | // create text control for user input 37 | int idTextXtrl = 1; 38 | _userTextCtrl = new wxTextCtrl(ctrlPanel, idTextXtrl, "", wxDefaultPosition, 39 | wxSize(width, 50), wxTE_PROCESS_ENTER, 40 | wxDefaultValidator, wxTextCtrlNameStr); 41 | Connect(idTextXtrl, wxEVT_TEXT_ENTER, 42 | wxCommandEventHandler(ChatBotFrame::OnEnter)); 43 | 44 | // create vertical sizer for panel alignment and add panels 45 | wxBoxSizer *vertBoxSizer = new wxBoxSizer(wxVERTICAL); 46 | vertBoxSizer->AddSpacer(90); 47 | vertBoxSizer->Add(_panelDialog, 6, wxEXPAND | wxALL, 0); 48 | vertBoxSizer->Add(_userTextCtrl, 1, wxEXPAND | wxALL, 5); 49 | ctrlPanel->SetSizer(vertBoxSizer); 50 | 51 | // position window in screen center 52 | this->Centre(); 53 | } 54 | 55 | void ChatBotFrame::OnEnter(wxCommandEvent &WXUNUSED(event)) { 56 | // retrieve text from text control 57 | wxString userText = _userTextCtrl->GetLineText(0); 58 | 59 | // add new user text to dialog 60 | _panelDialog->AddDialogItem(userText, true); 61 | 62 | // delete text in text control 63 | _userTextCtrl->Clear(); 64 | 65 | // send user text to chatbot 66 | _panelDialog->GetChatLogicHandle()->SendMessageToChatbot( 67 | std::string(userText.mb_str())); 68 | } 69 | 70 | BEGIN_EVENT_TABLE(ChatBotFrameImagePanel, wxPanel) 71 | EVT_PAINT(ChatBotFrameImagePanel::paintEvent) // catch paint events 72 | END_EVENT_TABLE() 73 | 74 | ChatBotFrameImagePanel::ChatBotFrameImagePanel(wxFrame *parent) 75 | : wxPanel(parent) {} 76 | 77 | void ChatBotFrameImagePanel::paintEvent(wxPaintEvent &evt) { 78 | wxPaintDC dc(this); 79 | render(dc); 80 | } 81 | 82 | void ChatBotFrameImagePanel::paintNow() { 83 | wxClientDC dc(this); 84 | render(dc); 85 | } 86 | 87 | void ChatBotFrameImagePanel::render(wxDC &dc) { 88 | // load backgroud image from file 89 | wxString imgFile = imgBasePath + "sf_bridge.jpg"; 90 | wxImage image; 91 | image.LoadFile(imgFile); 92 | 93 | // rescale image to fit window dimensions 94 | wxSize sz = this->GetSize(); 95 | wxImage imgSmall = 96 | image.Rescale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH); 97 | _image = wxBitmap(imgSmall); 98 | 99 | dc.DrawBitmap(_image, 0, 0, false); 100 | } 101 | 102 | BEGIN_EVENT_TABLE(ChatBotPanelDialog, wxPanel) 103 | EVT_PAINT(ChatBotPanelDialog::paintEvent) // catch paint events 104 | END_EVENT_TABLE() 105 | 106 | ChatBotPanelDialog::ChatBotPanelDialog(wxWindow *parent, wxWindowID id) 107 | : wxScrolledWindow(parent, id) { 108 | // sizer will take care of determining the needed scroll size 109 | _dialogSizer = new wxBoxSizer(wxVERTICAL); 110 | this->SetSizer(_dialogSizer); 111 | 112 | // allow for PNG images to be handled 113 | wxInitAllImageHandlers(); 114 | 115 | //// STUDENT CODE 116 | //// 117 | 118 | // create chat logic instance 119 | _chatLogic = std::make_unique(); 120 | 121 | // pass pointer to chatbot dialog so answers can be displayed in GUI 122 | _chatLogic->SetPanelDialogHandle(this); 123 | 124 | // load answer graph from file 125 | _chatLogic->LoadAnswerGraphFromFile(dataPath + "src/answergraph.txt"); 126 | 127 | //// 128 | //// EOF STUDENT CODE 129 | } 130 | 131 | ChatBotPanelDialog::~ChatBotPanelDialog() { 132 | //// STUDENT CODE 133 | //// 134 | 135 | // delete _chatLogic; 136 | 137 | //// 138 | //// EOF STUDENT CODE 139 | } 140 | 141 | void ChatBotPanelDialog::AddDialogItem(wxString text, bool isFromUser) { 142 | // add a single dialog element to the sizer 143 | ChatBotPanelDialogItem *item = 144 | new ChatBotPanelDialogItem(this, text, isFromUser); 145 | _dialogSizer->Add( 146 | item, 0, wxALL | (isFromUser == true ? wxALIGN_LEFT : wxALIGN_RIGHT), 8); 147 | _dialogSizer->Layout(); 148 | 149 | // make scrollbar show up 150 | this->FitInside(); // ask the sizer about the needed size 151 | this->SetScrollRate(5, 5); 152 | this->Layout(); 153 | 154 | // scroll to bottom to show newest element 155 | int dx, dy; 156 | this->GetScrollPixelsPerUnit(&dx, &dy); 157 | int sy = dy * this->GetScrollLines(wxVERTICAL); 158 | this->DoScroll(0, sy); 159 | } 160 | 161 | void ChatBotPanelDialog::PrintChatbotResponse(std::string response) { 162 | // convert string into wxString and add dialog element 163 | wxString botText(response.c_str(), wxConvUTF8); 164 | AddDialogItem(botText, false); 165 | } 166 | 167 | void ChatBotPanelDialog::paintEvent(wxPaintEvent &evt) { 168 | wxPaintDC dc(this); 169 | render(dc); 170 | } 171 | 172 | void ChatBotPanelDialog::paintNow() { 173 | wxClientDC dc(this); 174 | render(dc); 175 | } 176 | 177 | void ChatBotPanelDialog::render(wxDC &dc) { 178 | wxImage image; 179 | image.LoadFile(imgBasePath + "sf_bridge_inner.jpg"); 180 | 181 | wxSize sz = this->GetSize(); 182 | wxImage imgSmall = 183 | image.Rescale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH); 184 | 185 | _image = wxBitmap(imgSmall); 186 | dc.DrawBitmap(_image, 0, 0, false); 187 | } 188 | 189 | ChatBotPanelDialogItem::ChatBotPanelDialogItem(wxPanel *parent, wxString text, 190 | bool isFromUser) 191 | : wxPanel(parent, -1, wxPoint(-1, -1), wxSize(-1, -1), wxBORDER_NONE) { 192 | // retrieve image from chatbot 193 | wxBitmap *bitmap = isFromUser == true ? nullptr 194 | : ((ChatBotPanelDialog *)parent) 195 | ->GetChatLogicHandle() 196 | ->GetImageFromChatbot(); 197 | 198 | // create image and text 199 | _chatBotImg = new wxStaticBitmap( 200 | this, wxID_ANY, 201 | (isFromUser ? wxBitmap(imgBasePath + "user.png", wxBITMAP_TYPE_PNG) 202 | : *bitmap), 203 | wxPoint(-1, -1), wxSize(-1, -1)); 204 | _chatBotTxt = 205 | new wxStaticText(this, wxID_ANY, text, wxPoint(-1, -1), wxSize(150, -1), 206 | wxALIGN_CENTRE | wxBORDER_NONE); 207 | _chatBotTxt->SetForegroundColour(isFromUser == true ? wxColor(*wxBLACK) 208 | : wxColor(*wxWHITE)); 209 | 210 | // create sizer and add elements 211 | wxBoxSizer *horzBoxSizer = new wxBoxSizer(wxHORIZONTAL); 212 | horzBoxSizer->Add(_chatBotTxt, 8, wxEXPAND | wxALL, 1); 213 | horzBoxSizer->Add(_chatBotImg, 2, wxEXPAND | wxALL, 1); 214 | this->SetSizer(horzBoxSizer); 215 | 216 | // wrap text after 150 pixels 217 | _chatBotTxt->Wrap(150); 218 | 219 | // set background color 220 | this->SetBackgroundColour((isFromUser == true ? wxT("YELLOW") : wxT("BLUE"))); 221 | } 222 | -------------------------------------------------------------------------------- /src/chatlogic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "chatbot.h" 10 | #include "chatlogic.h" 11 | #include "graphedge.h" 12 | #include "graphnode.h" 13 | 14 | ChatLogic::ChatLogic() { 15 | //// STUDENT CODE 16 | //// 17 | 18 | // create instance of chatbot 19 | _chatBot = new ChatBot("../images/chatbot.png"); 20 | 21 | // add pointer to chatlogic so that chatbot answers can be passed on to the 22 | // GUI 23 | _chatBot->SetChatLogicHandle(this); 24 | 25 | //// 26 | //// EOF STUDENT CODE 27 | } 28 | 29 | ChatLogic::~ChatLogic() { 30 | //// STUDENT CODE 31 | //// 32 | 33 | // delete chatbot instance 34 | delete _chatBot; 35 | 36 | // delete all nodes 37 | for (auto it = std::begin(_nodes); it != std::end(_nodes); ++it) { 38 | delete *it; 39 | } 40 | 41 | // delete all edges 42 | for (auto it = std::begin(_edges); it != std::end(_edges); ++it) { 43 | delete *it; 44 | } 45 | 46 | //// 47 | //// EOF STUDENT CODE 48 | } 49 | 50 | template 51 | void ChatLogic::AddAllTokensToElement(std::string tokenID, tokenlist &tokens, 52 | T &element) { 53 | // find all occurences for current node 54 | auto token = tokens.begin(); 55 | while (true) { 56 | token = std::find_if( 57 | token, tokens.end(), 58 | [&tokenID](const std::pair &pair) { 59 | return pair.first == tokenID; 60 | ; 61 | }); 62 | if (token != tokens.end()) { 63 | element.AddToken(token->second); // add new keyword to edge 64 | token++; // increment iterator to next element 65 | } else { 66 | break; // quit infinite while-loop 67 | } 68 | } 69 | } 70 | 71 | void ChatLogic::LoadAnswerGraphFromFile(std::string filename) { 72 | // load file with answer graph elements 73 | std::ifstream file(filename); 74 | 75 | // check for file availability and process it line by line 76 | if (file) { 77 | // loop over all lines in the file 78 | std::string lineStr; 79 | while (getline(file, lineStr)) { 80 | // extract all tokens from current line 81 | tokenlist tokens; 82 | while (lineStr.size() > 0) { 83 | // extract next token 84 | int posTokenFront = lineStr.find("<"); 85 | int posTokenBack = lineStr.find(">"); 86 | if (posTokenFront < 0 || posTokenBack < 0) 87 | break; // quit loop if no complete token has been found 88 | std::string tokenStr = 89 | lineStr.substr(posTokenFront + 1, posTokenBack - 1); 90 | 91 | // extract token type and info 92 | int posTokenInfo = tokenStr.find(":"); 93 | if (posTokenInfo != std::string::npos) { 94 | std::string tokenType = tokenStr.substr(0, posTokenInfo); 95 | std::string tokenInfo = 96 | tokenStr.substr(posTokenInfo + 1, tokenStr.size() - 1); 97 | 98 | // add token to vector 99 | tokens.push_back(std::make_pair(tokenType, tokenInfo)); 100 | } 101 | 102 | // remove token from current line 103 | lineStr = lineStr.substr(posTokenBack + 1, lineStr.size()); 104 | } 105 | 106 | // process tokens for current line 107 | auto type = 108 | std::find_if(tokens.begin(), tokens.end(), 109 | [](const std::pair &pair) { 110 | return pair.first == "TYPE"; 111 | }); 112 | if (type != tokens.end()) { 113 | // check for id 114 | auto idToken = 115 | std::find_if(tokens.begin(), tokens.end(), 116 | [](const std::pair &pair) { 117 | return pair.first == "ID"; 118 | }); 119 | if (idToken != tokens.end()) { 120 | // extract id from token 121 | int id = std::stoi(idToken->second); 122 | 123 | // node-based processing 124 | if (type->second == "NODE") { 125 | //// STUDENT CODE 126 | //// 127 | 128 | // check if node with this ID exists already 129 | auto newNode = std::find_if( 130 | _nodes.begin(), _nodes.end(), 131 | [&id](GraphNode *node) { return node->GetID() == id; }); 132 | 133 | // create new element if ID does not yet exist 134 | if (newNode == _nodes.end()) { 135 | _nodes.emplace_back(new GraphNode(id)); 136 | newNode = _nodes.end() - 1; // get iterator to last element 137 | 138 | // add all answers to current node 139 | AddAllTokensToElement("ANSWER", tokens, **newNode); 140 | } 141 | 142 | //// 143 | //// EOF STUDENT CODE 144 | } 145 | 146 | // edge-based processing 147 | if (type->second == "EDGE") { 148 | //// STUDENT CODE 149 | //// 150 | 151 | // find tokens for incoming (parent) and outgoing (child) node 152 | auto parentToken = std::find_if( 153 | tokens.begin(), tokens.end(), 154 | [](const std::pair &pair) { 155 | return pair.first == "PARENT"; 156 | }); 157 | auto childToken = std::find_if( 158 | tokens.begin(), tokens.end(), 159 | [](const std::pair &pair) { 160 | return pair.first == "CHILD"; 161 | }); 162 | 163 | if (parentToken != tokens.end() && childToken != tokens.end()) { 164 | // get iterator on incoming and outgoing node via ID search 165 | auto parentNode = std::find_if( 166 | _nodes.begin(), _nodes.end(), 167 | [&parentToken](GraphNode *node) { 168 | return node->GetID() == std::stoi(parentToken->second); 169 | }); 170 | auto childNode = std::find_if( 171 | _nodes.begin(), _nodes.end(), [&childToken](GraphNode *node) { 172 | return node->GetID() == std::stoi(childToken->second); 173 | }); 174 | 175 | // create new edge 176 | GraphEdge *edge = new GraphEdge(id); 177 | edge->SetChildNode(*childNode); 178 | edge->SetParentNode(*parentNode); 179 | _edges.push_back(edge); 180 | 181 | // find all keywords for current node 182 | AddAllTokensToElement("KEYWORD", tokens, *edge); 183 | 184 | // store reference in child node and parent node 185 | (*childNode)->AddEdgeToParentNode(edge); 186 | (*parentNode)->AddEdgeToChildNode(edge); 187 | } 188 | 189 | //// 190 | //// EOF STUDENT CODE 191 | } 192 | } else { 193 | std::cout << "Error: ID missing. Line is ignored!" << std::endl; 194 | } 195 | } 196 | } // eof loop over all lines in the file 197 | 198 | file.close(); 199 | 200 | } // eof check for file availability 201 | else { 202 | std::cout << "File could not be opened!" << std::endl; 203 | return; 204 | } 205 | 206 | //// STUDENT CODE 207 | //// 208 | 209 | // identify root node 210 | GraphNode *rootNode = nullptr; 211 | for (auto it = std::begin(_nodes); it != std::end(_nodes); ++it) { 212 | // search for nodes which have no incoming edges 213 | if ((*it)->GetNumberOfParents() == 0) { 214 | 215 | if (rootNode == nullptr) { 216 | rootNode = *it; // assign current node to root 217 | } else { 218 | std::cout << "ERROR : Multiple root nodes detected" << std::endl; 219 | } 220 | } 221 | } 222 | 223 | // add chatbot to graph root node 224 | _chatBot->SetRootNode(rootNode); 225 | rootNode->MoveChatbotHere(_chatBot); 226 | 227 | //// 228 | //// EOF STUDENT CODE 229 | } 230 | 231 | void ChatLogic::SetPanelDialogHandle(ChatBotPanelDialog *panelDialog) { 232 | _panelDialog = panelDialog; 233 | } 234 | 235 | void ChatLogic::SetChatbotHandle(ChatBot *chatbot) { _chatBot = chatbot; } 236 | 237 | void ChatLogic::SendMessageToChatbot(std::string message) { 238 | _chatBot->ReceiveMessageFromUser(message); 239 | } 240 | 241 | void ChatLogic::SendMessageToUser(std::string message) { 242 | _panelDialog->PrintChatbotResponse(message); 243 | } 244 | 245 | wxBitmap *ChatLogic::GetImageFromChatbot() { 246 | return _chatBot->GetImageHandle(); 247 | } 248 | --------------------------------------------------------------------------------