Skip to content

Commit b040d44

Browse files
authored
Merge pull request #69 from sharvil-lade/main
feat: implemented terminal chat simulator
2 parents e69ff31 + 91bb8b7 commit b040d44

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
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 << "\nShutting 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

Comments
 (0)