A realtime browser-based drawing and guessing game, skribbl.io clone.
This game was built using the following technologies:
- React JS: A popular JavaScript library for building user interfaces.
- SignalR : A library for building real-time web applications.
- HTML and CSS: The languages used for creating the user interface and styling the game.
- Navigate to the game in your web browser by visiting
http://localhost:5000. - Enter your username and choose a room then click "Join Room".
- Once there are enough players in the lobby, the game will start.
- If its your turn, you will be given three random secret words to choose from.
- Use the drawing tools to create an image that represents the secret word you've just choosen.
- When you're done drawing, the other players will have a limited amount of time to guess the word.
- If someone guesses the word correctly will earn points accroding to the order of guessing.
- The first player who reaches a 3000 points will take the 1st place. the 2nd and 3rd will also be announced.
- Clone the repository:
git clone https://github.com/mohammed0xff/SkribblUp.io - Navigate to the project directory:
cd SkribblUp.io - Run the Docker Compose command to build and start the containers:
docker-compose up - Open your web browser and go to
http://localhost:5000
- Clone the repository: git clone
https://github.com/mohammed0xff/SkribblUp.io - Navigate to the project directory:
cd Backend/DrawAndGuess - Build the project:
dotnet build - Run the project with launch settings:
dotnet run
- or just open
.slnfile and pressf5to build and run the project -
- Navigate to the project directory:
cd Frontend - Install dependencies with:
npm install - Start the development server with:
npm run devor justvite - Open your web browser and go to
http://localhost:5000.
Example : user started by sending a message ..
- In React app --> Chat Component
// user sends a message
sendMessage = async (message) => {
try {
await this.props.connection.invoke(InvokeMethods.SendMessage, message);
} catch (e) {
console.log(e);
}
}- Chathub recieves a call on
SendMessagemethod providing a string parametermessage
public class GameHub : Hub
{
private readonly ICommandProcessor _commandProcessor;
public void SendMessage(string message)
{
// creates a SendMessageCommand and pass it to command processor.
_commandProcessor.AddCommand(
new SendMessageCommand(Context.ConnectionId) { Message = message }
);
}
// other hub methods here ..
}By using the command pattern, the application is more maintainable and extensible, as each command encapsulates a specific action or behavior that can be easily added or modified. The command pattern also helps to decouple the logic of executing actions from the rest of our application.
- In
CommandProcessorclass
public class CommandProcessor : ICommandProcessor
{
private readonly Game _game;
private readonly ILogger<CommandProcessor> _logger;
private readonly TaskQueue _taskQueue;
public void AddCommand(ICommand command)
{
_taskQueue.EnqueueTask(
() => ProcessCommand(command)
);
}
private async Task ProcessCommand(ICommand command)
{
try
{
await command.ExecuteAsync(_game);
}
catch (Exception ex)
{
_logger.LogError($"Error in executing command {command.GetType().Name} : {ex.Message}");
}
}
}- Command executes asynchronous in the task queue
public class SendMessageCommand : ICommand
{
public string ConnectionId { get; init; }
public string Message { get; init; }
public async Task ExecuteAsync(Game game)
{
var user = game.GetUserByConnectionId(ConnectionId);
// some exception handling here..
//
// send message only if it doesnt contain a right guess.
if (! await game.CheckRightGuess(user, Message))
{
await game.Broadcaster.SendMessage(user, Message);
}
}
}BroadcasterSends the message to room if user didn't make a right guess.
public async Task SendMessage(User user, string message)
{
try
{
await SendToGroup(user.Room, ClientMethods.ReceiveMessage,
new Message
{
Username = user.UserName,
Content = message
});
}
catch (Exception ex)
{
_logger.LogError($"Error sending message for user {user.UserName} in room {user.Room}: {ex.Message}");
}
}- As client code listens on
ReceiveMessage
connection.on(ListeningMethods.ReceiveMessage, ({username, content}) => {
this.setState(prevState => ({
messages : [...prevState.messages, { username, content}]
}));
});- All users in the room receive the message successfully.
If you'd like to contribute, feel free to submit a pull request!
-
Backend :
- sending errors to client.
- announcing winners.
- make some commands require certain permissions or challenges before they can be executed.
-
Frontend :
- brush sizing.
- eraser (actually, we just turn the drawing color white 😀).
- fill tool
- make it responsive
This game is licensed under the MIT license. See LICENSE.md for more information.