feat: add in-game chat to Connect 4
All checks were successful
Deploy c4 / deploy (push) Successful in 43s

Add real-time chat alongside the game board, mirroring the snake chat
implementation. Fix mobile layout for both C4 and snake chats — expand
chat to full width and reduce history height on small screens.
This commit is contained in:
Ryan Hamamura
2026-02-13 10:54:19 -10:00
parent e45559ecb3
commit 9069530e47
4 changed files with 250 additions and 1 deletions

56
main.go
View File

@@ -353,6 +353,9 @@ func main() {
nickname := c.Signal(sessionNickname)
colSignal := c.Signal(0)
showGuestPrompt := c.Signal(false)
chatMsg := c.Signal("")
var chatMessages []ui.C4ChatMessage
var chatMu sync.Mutex
goToLogin := c.Action(func() {
c.Session().Set("return_url", "/game/"+gameID)
@@ -434,8 +437,53 @@ func main() {
}
})
sendChat := c.Action(func() {
msg := chatMsg.String()
if msg == "" || gi == nil {
return
}
color := gi.GetPlayerColor(playerID)
if color == 0 {
return
}
g := gi.GetGame()
nick := ""
for _, p := range g.Players {
if p != nil && p.ID == playerID {
nick = p.Nickname
break
}
}
cm := ui.C4ChatMessage{
Nickname: nick,
Color: color,
Message: msg,
Time: time.Now().UnixMilli(),
}
data, err := json.Marshal(cm)
if err != nil {
return
}
c.Publish("game.chat."+gameID, data)
chatMsg.SetValue("")
})
if gameExists {
c.Subscribe("game."+gameID, func(data []byte) { c.Sync() })
c.Subscribe("game.chat."+gameID, func(data []byte) {
var cm ui.C4ChatMessage
if err := json.Unmarshal(data, &cm); err != nil {
return
}
chatMu.Lock()
chatMessages = append(chatMessages, cm)
if len(chatMessages) > 50 {
chatMessages = chatMessages[len(chatMessages)-50:]
}
chatMu.Unlock()
c.Sync()
})
}
if gameExists && sessionNickname != "" && gi.GetPlayerColor(playerID) == 0 {
@@ -480,13 +528,19 @@ func main() {
return dropPiece.OnClick(via.WithSignalInt(colSignal, col))
}
chatMu.Lock()
msgs := make([]ui.C4ChatMessage, len(chatMessages))
copy(msgs, chatMessages)
chatMu.Unlock()
chat := ui.C4Chat(msgs, chatMsg.Bind(), sendChat.OnClick(), sendChat.OnKeyDown("Enter"))
var content []h.H
content = append(content,
ui.BackToLobby(),
ui.StealthTitle("text-3xl font-bold"),
ui.PlayerInfo(g, myColor),
ui.StatusBanner(g, myColor, createRematch.OnClick()),
ui.BoardComponent(g, columnClick, myColor),
h.Div(h.Class("c4-game-area"), ui.BoardComponent(g, columnClick, myColor), chat),
)
if g.Status == game.StatusWaitingForPlayer {