1+ /*
2+ * -----------------------------------------------------------------
3+ * C++ TERMINAL CHAT SIMULATOR (v2: User Switching)
4+ * -----------------------------------------------------------------
5+ * This program simulates a multi-user chat room in the terminal.
6+ * It uses multithreading to demonstrate concurrency concepts
7+ * without any networking.
8+ *
9+ * Concepts Demonstrated:
10+ * - Object-Oriented Programming (OOP)
11+ * - Multithreading (std::thread)
12+ * - Synchronization (std::mutex, std::lock_guard, std::unique_lock)
13+ * - Producer-Consumer Model
14+ * - Broadcasting (std::condition_variable)
15+ *
16+ * How to compile (C++11 or newer):
17+ * g++ -o chat_simulator chat_simulator.cpp -std=c++11 -pthread
18+ * or
19+ * clang++ -o chat_simulator chat_simulator.cpp -std=c++11 -pthread
20+ *
21+ * How to run:
22+ * ./chat_simulator
23+ * -----------------------------------------------------------------
24+ */
25+
26+ #include < iostream>
27+ #include < string>
28+ #include < vector>
29+ #include < thread>
30+ #include < mutex>
31+ #include < condition_variable>
32+ #include < chrono> // For std::this_thread::sleep_for
33+ #include < memory> // For std::unique_ptr
34+ #include < atomic> // For std::atomic<bool>
35+
36+ // Forward declarations for cross-class references
37+ class User ;
38+ class ChatRoom ;
39+
40+ // -----------------------------------------------------------------
41+ // CLASS: Message
42+ // Represents a single chat message with a sender and content.
43+ // -----------------------------------------------------------------
44+ class Message {
45+ public:
46+ std::string sender;
47+ std::string content;
48+
49+ Message (const std::string& s, const std::string& c) : sender(s), content(c) {}
50+ };
51+
52+ // -----------------------------------------------------------------
53+ // CLASS: ChatRoom
54+ // The centralized hub. It manages the shared message log and
55+ // handles the synchronization and broadcasting.
56+ // -----------------------------------------------------------------
57+ class ChatRoom {
58+ private:
59+ // The shared message log. This is the "shared message queue"
60+ // described in the overview. A vector is used as a log
61+ // so messages aren't "consumed" (popped) when read.
62+ std::vector<Message> messages;
63+
64+ // Mutex to protect access to the shared 'messages' vector
65+ std::mutex mtx;
66+
67+ // Condition variable to signal new messages
68+ std::condition_variable cv;
69+
70+ public:
71+ /* *
72+ * @brief Adds a new message to the log (Producer).
73+ * This function is called by any User's 'send' method.
74+ */
75+ void addMessage (const std::string& sender, const std::string& content) {
76+ {
77+ // Lock the mutex before modifying the shared vector
78+ std::lock_guard<std::mutex> lock (mtx);
79+ messages.push_back (Message (sender, content));
80+ } // Lock is released here
81+
82+ // Notify all waiting threads that a new message is available
83+ cv.notify_all ();
84+ }
85+
86+ /* *
87+ * @brief A special signal to wake up all threads.
88+ * Used during shutdown to unblock waiting threads.
89+ */
90+ void poke () {
91+ cv.notify_all ();
92+ }
93+
94+ /* *
95+ * @brief Listens for new messages (Consumer side).
96+ * This is the core of the consumer/receiver logic.
97+ * Each User thread calls this function in a loop.
98+ *
99+ * @param user The User who is listening.
100+ * @param lastReadIndex The last message index read by this specific user.
101+ * Passed by reference to be updated.
102+ */
103+ void listen (User* user, size_t & lastReadIndex); // Implementation after User class
104+ };
105+
106+ // -----------------------------------------------------------------
107+ // CLASS: User
108+ // Represents a virtual chat user. Each User instance runs
109+ // its own receiver thread.
110+ // -----------------------------------------------------------------
111+ class User {
112+ private:
113+ std::string name;
114+ ChatRoom* chatRoom; // Pointer to the central room
115+ size_t lastReadIndex; // This user's personal "read cursor"
116+ std::thread receiverThread;
117+ std::atomic<bool > running; // Controls the receiver thread's lifecycle
118+
119+ /* *
120+ * @brief The function that runs on this User's private thread.
121+ * It continuously calls chatRoom->listen() to wait for and
122+ * print new messages.
123+ */
124+ void receiveLoop () {
125+ lastReadIndex = 0 ; // Start reading from the beginning
126+ while (running) {
127+ // This call will block until new messages are available
128+ // or until the 'running' flag is set to false.
129+ chatRoom->listen (this , lastReadIndex);
130+ }
131+ }
132+
133+ public:
134+ User (const std::string& n, ChatRoom* room)
135+ : name(n), chatRoom(room), lastReadIndex(0 ), running(true ) {
136+ // Start the receiver thread as soon as the User is created
137+ receiverThread = std::thread (&User::receiveLoop, this );
138+ }
139+
140+ ~User () {
141+ // Ensure the thread is stopped upon destruction
142+ stop ();
143+ }
144+
145+ /* *
146+ * @brief Stops the receiver thread and joins it.
147+ */
148+ void stop () {
149+ // Set 'running' to false. Use exchange to ensure it only runs once.
150+ bool alreadyStopped = !running.exchange (false );
151+ if (alreadyStopped) return ;
152+
153+ // Wake up the thread from its cv.wait()
154+ chatRoom->poke ();
155+
156+ // Wait for the thread to finish execution
157+ if (receiverThread.joinable ()) {
158+ receiverThread.join ();
159+ }
160+ }
161+
162+ /* *
163+ * @brief Sends a message *from* this user *to* the chat room.
164+ */
165+ void send (const std::string& content) {
166+ chatRoom->addMessage (name, content);
167+ }
168+
169+ // --- Getters ---
170+ const std::string& getName () const { return name; }
171+ bool isRunning () const { return running; }
172+ };
173+
174+ // -----------------------------------------------------------------
175+ // Implementation of ChatRoom::listen
176+ // (Must be defined *after* the User class is fully defined)
177+ // -----------------------------------------------------------------
178+ void ChatRoom::listen (User* user, size_t & lastReadIndex) {
179+ // Acquire a unique lock, which can be locked and unlocked.
180+ // std::unique_lock is required for std::condition_variable::wait
181+ std::unique_lock<std::mutex> lock (mtx);
182+
183+ // Wait blocks the current thread until the predicate is true.
184+ // The predicate checks for two conditions:
185+ // 1. Are there new messages? (lastReadIndex < messages.size())
186+ // 2. Is the user thread supposed to shut down? (!user->isRunning())
187+ // This atomic check prevents a "lost wakeup" on shutdown.
188+ cv.wait (lock, [this , &lastReadIndex, user] {
189+ return lastReadIndex < messages.size () || !user->isRunning ();
190+ });
191+
192+ // If we woke up because we're shutting down, exit immediately.
193+ if (!user->isRunning ()) {
194+ return ;
195+ }
196+
197+ //
198+ // Otherwise, we woke up for new messages.
199+ // Print all new messages since our last read index.
200+ while (lastReadIndex < messages.size ()) {
201+ const Message& msg = messages[lastReadIndex];
202+
203+ // Don't print messages that this user sent themselves
204+ if (msg.sender != user->getName ()) {
205+ // This std::cout simulates this user's private terminal window
206+ std::cout << " [" << user->getName () << " 's Terminal] "
207+ << msg.sender << " : " << msg.content << std::endl;
208+ }
209+ lastReadIndex++; // Update this user's read cursor
210+ }
211+ // The unique_lock is automatically released when it goes out of scope
212+ }
213+
214+ // -----------------------------------------------------------------
215+ // CLASS: ChatSimulator
216+ // Controls the simulation, creates the users, and handles
217+ // the main application lifecycle.
218+ // -----------------------------------------------------------------
219+ class ChatSimulator {
220+ private:
221+ ChatRoom chatRoom;
222+ std::vector<std::unique_ptr<User>> users;
223+ int currentUserIndex; // Track the currently controlled user
224+
225+ public:
226+ ChatSimulator () : currentUserIndex(0 ) {} // Default to User1 (index 0)
227+
228+ /* *
229+ * @brief Prints a list of all available users and their indices.
230+ */
231+ void listUsers () {
232+ std::cout << " --- Available Users ---" << std::endl;
233+ for (size_t i = 0 ; i < users.size (); ++i) {
234+ std::cout << " Index " << (i + 1 ) << " : " << users[i]->getName () << std::endl;
235+ }
236+ std::cout << " -----------------------" << std::endl;
237+ }
238+
239+ /* *
240+ * @brief Attempts to switch the active user.
241+ * @param input The raw command (e.g., "/switch 2")
242+ */
243+ void switchUser (const std::string& input) {
244+ try {
245+ // Extract the number part (e.g., "2" from "/switch 2")
246+ // The index is 1-based for the user, so we subtract 1.
247+ int newIndex = std::stoi (input.substr (8 )) - 1 ;
248+
249+ if (newIndex >= 0 && newIndex < users.size ()) {
250+ currentUserIndex = newIndex;
251+ std::cout << " --- Switched control to "
252+ << users[currentUserIndex]->getName () << " ---" << std::endl;
253+ } else {
254+ std::cout << " --- Error: Invalid user index. ---" << std::endl;
255+ listUsers ();
256+ }
257+ } catch (const std::exception& e) {
258+ std::cout << " --- Error: Invalid command format. Use /switch <number> ---" << std::endl;
259+ }
260+ }
261+
262+ void run () {
263+ std::cout << " 🚀 Starting chat simulation..." << std::endl;
264+
265+ // Create the users. Their constructors automatically start their threads.
266+ users.push_back (std::make_unique<User>(" User1" , &chatRoom));
267+ users.push_back (std::make_unique<User>(" User2" , &chatRoom));
268+ users.push_back (std::make_unique<User>(" User3" , &chatRoom));
269+
270+ std::cout << " Virtual users are now listening in separate threads." << std::endl;
271+ std::cout << " ---------------------------------------------------" << std::endl;
272+
273+ // Give threads a moment to start and wait
274+ std::this_thread::sleep_for (std::chrono::milliseconds (250 ));
275+
276+ // --- SIMULATED CHAT ---
277+ users[0 ]->send (" Hello everyone! This is User1." );
278+ std::this_thread::sleep_for (std::chrono::milliseconds (500 ));
279+
280+ users[1 ]->send (" Hi User1! This is User2. Good to be here." );
281+ std::this_thread::sleep_for (std::chrono::milliseconds (300 ));
282+
283+ users[2 ]->send (" Hey all, User3 checking in." );
284+ std::this_thread::sleep_for (std::chrono::milliseconds (1000 ));
285+
286+ // --- MAIN INTERACTION LOOP ---
287+ std::cout << " \n --- Interactive Mode ---" << std::endl;
288+ std::cout << " Commands:" << std::endl;
289+ std::cout << " /list - Show all users" << std::endl;
290+ std::cout << " /switch <num> - Switch to user (e.g., /switch 2)" << std::endl;
291+ std::cout << " exit - Quit the simulation" << std::endl;
292+ std::cout << " ------------------------" << std::endl;
293+
294+ std::string input;
295+ while (true ) {
296+ // Update prompt to show the current user
297+ std::cout << " [Your Turn (" << users[currentUserIndex]->getName ()
298+ << " )]: " ;
299+ std::getline (std::cin, input);
300+
301+ if (input == " exit" ) {
302+ break ;
303+ } else if (input == " /list" ) {
304+ listUsers ();
305+ } else if (input.rfind (" /switch " , 0 ) == 0 ) { // Check for "/switch "
306+ switchUser (input);
307+ } else if (!input.empty ()) {
308+ // Send the message as the currently selected user
309+ users[currentUserIndex]->send (input);
310+
311+ // Simple "AI" response from User2 if it gets a question
312+ // from someone *other* than itself.
313+ if (currentUserIndex != 1 && // Not sent by User2
314+ input.find (' ?' ) != std::string::npos &&
315+ users.size () > 1 ) {
316+
317+ // Launch a detached thread for an automated reply
318+ std::thread ([&]() {
319+ std::this_thread::sleep_for (std::chrono::milliseconds (750 ));
320+ users[1 ]->send (" That's a great question! I'm not sure." );
321+ }).detach ();
322+ }
323+ }
324+ }
325+
326+ std::cout << " \n Shutting down simulation..." << std::endl;
327+ users.clear (); // Calls destructors, which stop() and join() threads
328+ std::cout << " All threads joined. Simulation complete." << std::endl;
329+ }
330+ };
331+
332+ // -----------------------------------------------------------------
333+ // MAIN FUNCTION
334+ // -----------------------------------------------------------------
335+ int main () {
336+ ChatSimulator simulator;
337+ simulator.run ();
338+ return 0 ;
339+ }
0 commit comments