add multiplayer and playing against bots#50
Conversation
the `main.go` is the actual path manager (somewhat of a API), protocol stores all the `structs` required and `room.go` keeps tracks of players and manages the core functionality of multiplayer
handles connections and rooms, while room.go handles the actual gameplay inside those rooms.
`usrname` str correctly identifies each player uniquely. serverURL needs to be stored for connections to the server while playing multiplayer
|
Attach a video with it please |
|
I tried it locally and it looks so cool 🔥 |
|
multiplayer mode is not working... have you tested it on your machine? |
|
I’ve created a |
|
So basically multiplayer won't work if your joining two sessions frkm the same device. |
what if two different people using same wifi network? |
|
Btw, for letter tracking, which do you think looks cleaner: caret tracking with a different color or the current look? |
switched to cloudflare tunnel, since safer, and faster
big changes made. - now there are rooms - rooms can be public or private. - private rooms require a code to join - the host can configure settings for the room these changes were made by the feedback of @vyrx-dev
- after multiplayer was over, toofan would just freeze. the fix involved in the server responding with a status of over and then the client displaying the correct results. - added bot-like gameplay, with ghost cursors and colored list while playing multiplyer
…when the user tries to play another
|
what's the status? |
|
I think its ready for merging. you can check the code and it was testing locally. 👍 |
…lly start the race
…oms. fixed an issue where the multiplayer states would not update
… words mode was selected
|
@vyrx-dev, I tried it out too, it's awesome af! pls merge it, would love it to be in upstream! ❤️🔥 |
There was a problem hiding this comment.
Pull request overview
This PR introduces competitive racing to Toofan by adding (1) a standalone SSE-based multiplayer race server, and (2) local AI bot racers, plus the TUI/client-side wiring to render races (ghost cursors, race bars, lobby/results views) and persist multiplayer settings.
Changes:
- Added a standalone
race_server/Go module implementing rooms, lobby/countdown/race state, and an SSE join endpoint with HTTP progress updates. - Added a local AI bot system and race UI elements (bot picker, ghost cursors, race bars, bot results).
- Added an in-app multiplayer flow (room creation/joining/quick-match, lobby UI, progress reporting, results) plus config persistence for username/server URL.
Reviewed changes
Copilot reviewed 90 out of 90 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds FAQ entries for online/bots and a contributors section. |
| race_server/go.mod | Defines standalone race server module. |
| race_server/main.go | Adds HTTP endpoints for join/progress/start/online/health. |
| race_server/hub.go | Implements hub + SSE join handler + matchmaking logic. |
| race_server/protocol.go | Defines JSON protocol types and message wrapper. |
| race_server/room.go | Implements room state, countdown/start, progress + finish broadcasting. |
| race_server/words.go | Embeds and loads word/snippet content; generates race text. |
| race_server/data/english/easy.txt | Adds embedded easy English word list for races. |
| race_server/data/english/medium.txt | Adds embedded medium English word list for races. |
| race_server/data/english/hard.txt | Adds embedded hard English word list for races. |
| race_server/data/go/00_basics.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/01_http_server.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/02_concurrency.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/03_error_handling.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/04_interfaces.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/05_maps_slices.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/06_structs.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/07_closures.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/08_json.go | Adds Go snippet content for code-mode races. |
| race_server/data/go/lessons/basics.go | Adds Go lesson snippet content for code-mode races. |
| race_server/data/javascript/00_variables.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/01_fetch_api.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/02_array_methods.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/03_dom_selection.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/04_promises.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/05_classes.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/06_destructuring.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/07_arrow_closures.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/08_events.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/09_error_handling.js | Adds JS snippet content for code-mode races. |
| race_server/data/javascript/10_storage.js | Adds JS snippet content for code-mode races. |
| race_server/data/typescript/01_union_intersection.ts | Adds TS snippet content for code-mode races. |
| race_server/data/typescript/02_generics_constraints.ts | Adds TS snippet content for code-mode races. |
| race_server/data/typescript/03_mapped_conditional.ts | Adds TS snippet content for code-mode races. |
| race_server/data/typescript/04_template_literal.ts | Adds TS snippet content for code-mode races. |
| race_server/data/typescript/05_async_error_handling.ts | Adds TS snippet content for code-mode races. |
| race_server/data/python/01_file_handling.py | Adds Python snippet content for code-mode races. |
| race_server/data/python/02_requests_api.py | Adds Python snippet content for code-mode races. |
| race_server/data/python/03_list_dict_comprehension.py | Adds Python snippet content for code-mode races. |
| race_server/data/python/04_async_io.py | Adds Python snippet content for code-mode races. |
| race_server/data/python/05_data_processing.py | Adds Python snippet content for code-mode races. |
| race_server/data/rust/01_divide.rs | Adds Rust snippet content for code-mode races. |
| race_server/data/rust/02_struct.rs | Adds Rust snippet content for code-mode races. |
| race_server/data/rust/03_vectors.rs | Adds Rust snippet content for code-mode races. |
| race_server/data/rust/04_files.rs | Adds Rust snippet content for code-mode races. |
| race_server/data/rust/05_closures.rs | Adds Rust snippet content for code-mode races. |
| race_server/data/java/01_stream_pipeline.java | Adds Java snippet content for code-mode races. |
| race_server/data/java/02_optional_patterns.java | Adds Java snippet content for code-mode races. |
| race_server/data/java/03_completable_future.java | Adds Java snippet content for code-mode races. |
| race_server/data/java/04_record_pattern.java | Adds Java snippet content for code-mode races. |
| race_server/data/java/05_concurrent_collections.java | Adds Java snippet content for code-mode races. |
| race_server/data/c/01_file_io.c | Adds C snippet content for code-mode races. |
| race_server/data/c/02_pointer_usage.c | Adds C snippet content for code-mode races. |
| race_server/data/c/03_dynamic_memory.c | Adds C snippet content for code-mode races. |
| race_server/data/c/04_struct_patterns.c | Adds C snippet content for code-mode races. |
| race_server/data/c/05_standard_library.c | Adds C snippet content for code-mode races. |
| race_server/data/cpp/01_file_io.cpp | Adds C++ snippet content for code-mode races. |
| race_server/data/cpp/02_vector_patterns.cpp | Adds C++ snippet content for code-mode races. |
| race_server/data/cpp/03_async_thread.cpp | Adds C++ snippet content for code-mode races. |
| race_server/data/cpp/04_modern_features.cpp | Adds C++ snippet content for code-mode races. |
| race_server/data/cpp/05_stl_algorithms.cpp | Adds C++ snippet content for code-mode races. |
| race_server/data/csharp/01_linq_queries.cs | Adds C# snippet content for code-mode races. |
| race_server/data/csharp/02_async_await.cs | Adds C# snippet content for code-mode races. |
| race_server/data/csharp/03_file_handling.cs | Adds C# snippet content for code-mode races. |
| race_server/data/csharp/04_dependency_injection.cs | Adds C# snippet content for code-mode races. |
| race_server/data/csharp/05_collections.cs | Adds C# snippet content for code-mode races. |
| race_server/data/dart/00_classes.dart | Adds Dart snippet content for code-mode races. |
| race_server/data/dart/01_async.dart | Adds Dart snippet content for code-mode races. |
| race_server/data/dart/02_collections.dart | Adds Dart snippet content for code-mode races. |
| race_server/data/dart/03_mixins.dart | Adds Dart snippet content for code-mode races. |
| race_server/data/lua/00_tables.lua | Adds Lua snippet content for code-mode races. |
| race_server/data/lua/01_metatables.lua | Adds Lua snippet content for code-mode races. |
| race_server/data/lua/02_file_io.lua | Adds Lua snippet content for code-mode races. |
| race_server/data/lua/03_coroutines.lua | Adds Lua snippet content for code-mode races. |
| race_server/data/shell/00_boilerplate.sh | Adds shell snippet content for code-mode races. |
| race_server/data/shell/01_conditionals.sh | Adds shell snippet content for code-mode races. |
| race_server/data/shell/02_loops.sh | Adds shell snippet content for code-mode races. |
| race_server/data/shell/03_docker_git.sh | Adds shell snippet content for code-mode races. |
| race_server/data/shell/04_text.sh | Adds shell snippet content for code-mode races. |
| internal/game/bot.go | Adds local bot generation + ticking + placement logic. |
| internal/game/client.go | Adds multiplayer SSE client + HTTP progress/start calls. |
| internal/game/storage.go | Persists multiplayer username/server URL in config. |
| internal/tui/model.go | Adds state + update loop wiring for bots and multiplayer sessions. |
| internal/tui/typing.go | Adds ghost cursors + race bars + keybinds for bots/multiplayer. |
| internal/tui/text.go | Extends text rendering to support ghost cursor styling. |
| internal/tui/race.go | Adds bot picker UI + bot race bar/results rendering. |
| internal/tui/online.go | Adds multiplayer room flow (create/join/quick match) + lobby/results UI. |
| internal/tui/results.go | Adds bot/multiplayer results integration + reset/disconnect logic. |
| internal/tui/picker.go | Updates theme picker to match expanded config format. |
| internal/tui/profile.go | Tweaks PB heading label in profile view. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (c *RaceClient) SendProgress(progress float64, wpm float64) error { | ||
| c.mu.Lock() | ||
| if c.closed { | ||
| c.mu.Unlock() | ||
| return nil | ||
| } | ||
| c.mu.Unlock() | ||
|
|
||
| body := map[string]interface{}{ | ||
| "name": c.name, | ||
| "room": c.room, | ||
| "progress": progress, | ||
| "wpm": wpm, | ||
| } | ||
| data, _ := json.Marshal(body) | ||
|
|
||
| resp, err := c.client.Post(c.serverURL+"/race/progress", "application/json", bytes.NewReader(data)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp.Body.Close() | ||
| return nil | ||
| } |
| // update local player's progress for immediate feedback | ||
| for i := range m.racePlayers { | ||
| if m.racePlayers[i].IsUser { | ||
| m.racePlayers[i].Progress = userProg | ||
| break | ||
| } | ||
| } | ||
| out = append(out, "", viewOnlineRaceBar(p, m.racePlayers, textWidth-4)) |
| case "joined": | ||
| var payload game.LobbyPayload | ||
| if err := json.Unmarshal(msg.Payload, &payload); err != nil { | ||
| return m, nil | ||
| } |
| case "start": | ||
| var payload game.StartPayload | ||
| if err := json.Unmarshal(msg.Payload, &payload); err != nil { | ||
| return m, nil | ||
| } |
| func (h *hub) removeClient(c *client) { | ||
| h.mu.Lock() | ||
| defer h.mu.Unlock() | ||
|
|
| // auto-match in public rooms. | ||
| // no_rooms=true means queue for random 1v1 only (never join custom-sized rooms). | ||
| for _, ex := range h.rooms { | ||
| if ex.started || ex.pin != "" || len(ex.players) >= ex.maxPlayers { | ||
| continue |
| func (c *RaceClient) Join(roomID, pin string, isCreate bool, size int, difficulty, mode, lang string, duration int, autoStart bool) error { | ||
| url := fmt.Sprintf("%s/race/join?name=%s", c.serverURL, c.name) | ||
| if !isCreate && roomID == "" { | ||
| url += "&no_rooms=true" | ||
| } | ||
| if roomID != "" { | ||
| url += "&room=" + roomID | ||
| } | ||
| if pin != "" { | ||
| url += "&pin=" + pin | ||
| } | ||
| if isCreate { | ||
| url += "&is_create=true" | ||
| url += fmt.Sprintf("&size=%d&difficulty=%s&mode=%s&lang=%s&duration=%d&auto_start=%t", size, difficulty, mode, lang, duration, autoStart) | ||
| } |
| func (c *RaceClient) Close() { | ||
| c.mu.Lock() | ||
| defer c.mu.Unlock() | ||
| if c.closed { | ||
| return | ||
| } | ||
| c.closed = true | ||
| close(c.done) | ||
| } |
| if m.raceState == onlineRacing && m.raceClient != nil { | ||
| prog := 0.0 | ||
| if len(m.game.Text()) > 0 { | ||
| prog = float64(len(m.game.Input())) / float64(len(m.game.Text())) | ||
| } | ||
| wpm := m.game.Stats().WPM | ||
| go m.raceClient.SendProgress(prog, wpm) | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@aaravmaloo i am not good with server side things so please check suggestions made by copilot especially those are labeled as high |
|
@vyrx-dev well I think its suggestion are valid, but it does work perfectly and I see no need to consider those changes. If you really want to, feel free to take those suggestions, but its suggestions pretty negligible. |
|
@vyrx-dev @aaravmaloo when is it comin' out guyzzz??? would be so cool af daymmmmm 😭 |
|
It's available...you can just checkout the PR branch and try As you can see it's not a small change so I have to review too many lines and it takes time (actually I am very busy with my freelance work) You have to wait a little bit more 😅 for the package release |
|
@vyrx-dev I thought we were gonna keep this on a |
Yes |
ok np |
|
seems like a good choice. if you do have questions, on how this works, etc, lemme know! |

Competitive Racing (Multiplayer and AI Bots)
This PR implements the complete competitive racing framework for Toofan, including a dedicated multiplayer server and an AI-driven bot system. These features transition the game from a solo typing tool to a real-time competitive platform.
Core Features
1. Multiplayer Racing System
Developed a high-performance, independent race server to handle real-time multiplayer sessions.
2. AI Ghost Racers
Introduced a bot system to provide a competitive experience even in offline mode.
3. TUI Integration
Updated the Bubble Tea model and view dispatch logic to handle complex race states.
Technical Improvements
Verification
Deployment Status
8525and can fully run independently without any extra toofan code.8525on the deployed server was exposed via cloudflare. The base url for the multiplayer server of Toofan is: http://toofan-race.pikapp.in