Add Connect 4 multiplayer game server

Real-time two-player Connect 4 using Via framework with:
- Game creation and invite links
- SSE-based live updates for both players
- Win detection with animated highlighting
- Session-based nickname persistence
This commit is contained in:
Ryan Hamamura
2026-01-14 12:57:57 -10:00
commit 389fc12bf2
10 changed files with 947 additions and 0 deletions

60
ui/board.go Normal file
View File

@@ -0,0 +1,60 @@
package ui
import (
"github.com/ryanhamamura/c4/game"
"github.com/ryanhamamura/via/h"
)
// ColumnClickFn returns an h.H onClick attribute for a given column index
type ColumnClickFn func(col int) h.H
func BoardComponent(g *game.Game, columnClick ColumnClickFn, myColor int) h.H {
var cols []h.H
for col := 0; col < 7; col++ {
var cells []h.H
for row := 0; row < 6; row++ {
cellColor := g.Board[row][col]
isWinning := g.IsWinningCell(row, col)
cells = append(cells, Cell(cellColor, isWinning))
}
// Column is clickable only if it's player's turn and game is in progress
canClick := g.Status == game.StatusInProgress && g.CurrentTurn == myColor
cols = append(cols, Column(col, cells, columnClick, canClick))
}
boardAttrs := []h.H{h.Class("board")}
boardAttrs = append(boardAttrs, cols...)
return h.Div(boardAttrs...)
}
func Column(colIdx int, cells []h.H, columnClick ColumnClickFn, canClick bool) h.H {
class := "column"
if canClick {
class += " clickable"
}
attrs := []h.H{h.Class(class)}
if canClick && columnClick != nil {
attrs = append(attrs, columnClick(colIdx))
}
attrs = append(attrs, cells...)
return h.Div(attrs...)
}
func Cell(color int, isWinning bool) h.H {
class := "cell"
switch color {
case 1:
class += " red"
case 2:
class += " yellow"
}
if isWinning {
class += " winning"
}
return h.Div(h.Class(class))
}