From 0ef0077cf5ee6023d421e8aab772888be15b7a56 Mon Sep 17 00:00:00 2001 From: Ryan Yeske Date: Fri, 1 Aug 2025 18:50:29 -0700 Subject: [PATCH 1/3] Remove debug line --- handlers/fun/chess/page.go | 1 - 1 file changed, 1 deletion(-) diff --git a/handlers/fun/chess/page.go b/handlers/fun/chess/page.go index ababff5..b0094d2 100644 --- a/handlers/fun/chess/page.go +++ b/handlers/fun/chess/page.go @@ -232,7 +232,6 @@ func (s *service) HandleSelect(w http.ResponseWriter, r *http.Request) { http.Error(w, "userMatchColor: "+err.Error(), http.StatusInternalServerError) return } - fmt.Println("turn=", state.Game.Position().Turn(), "user=", currentUserColor) uiGameState := UIGameState{gameState: state, flip: match.BlackUserID == currentUser.ID} From 1bbf3facd41ef1cc30421eb15948c93354aa263b Mon Sep 17 00:00:00 2001 From: Ryan Yeske Date: Fri, 1 Aug 2025 18:54:48 -0700 Subject: [PATCH 2/3] Reorg chess code --- handlers/fun/chess/game_state.go | 105 ++++++++++++ handlers/fun/chess/{page.go => handlers.go} | 173 +++++--------------- 2 files changed, 143 insertions(+), 135 deletions(-) create mode 100644 handlers/fun/chess/game_state.go rename handlers/fun/chess/{page.go => handlers.go} (76%) diff --git a/handlers/fun/chess/game_state.go b/handlers/fun/chess/game_state.go new file mode 100644 index 0000000..457fa6a --- /dev/null +++ b/handlers/fun/chess/game_state.go @@ -0,0 +1,105 @@ +package chess + +import ( + "errors" + "fmt" + "oj/api" + "strings" + + "github.com/notnil/chess" +) + +type GameState struct { + Board [8][8]*UISquare + Game *chess.Game + ValidMoves []*chess.Move + MatchID int64 +} + +func gameStateFromMatch(match api.ChessMatch) (*GameState, error) { + reader := strings.NewReader(match.Pgn) + fn, err := chess.PGN(reader) + if err != nil { + return nil, err + } + game := chess.NewGame(fn) + + pos := game.Position() + + svgPiece := [13]string{ + "", // empty piece + "/assets/chess/wK.svg", + "/assets/chess/wQ.svg", + "/assets/chess/wR.svg", + "/assets/chess/wB.svg", + "/assets/chess/wN.svg", + "/assets/chess/wP.svg", + "/assets/chess/bK.svg", + "/assets/chess/bQ.svg", + "/assets/chess/bR.svg", + "/assets/chess/bB.svg", + "/assets/chess/bN.svg", + "/assets/chess/bP.svg", + } + + state := GameState{Game: game} + + squareMap := pos.Board().SquareMap() + + for i := 0; i < 64; i++ { + piece := squareMap[chess.Square(i)] + rank := 7 - i/8 + file := i % 8 + state.Board[rank][file] = &UISquare{ + SVGPiece: svgPiece[piece], + Rank: rank, + File: file, + MatchID: match.ID, + Action: fmt.Sprintf("select?rank=%d&file=%d", rank, file), + } + } + + return &state, nil +} + +var ErrSquareNotOccupied = errors.New("no piece on square") + +func (s *GameState) selectSquare(rank, file int) error { + var selected *UISquare + + for _, row := range s.Board { + for _, square := range row { + if square.Rank == rank && square.File == file { + if square.Occupied() { + selected = square + square.Selected = true + square.Action = "unselect" + } + } + } + } + + if selected == nil { + return ErrSquareNotOccupied + } + + // add the dots to the places the peice on the selected square can move to + moves := s.Game.ValidMoves() + + selectedSquare := chess.Square((7-selected.Rank)*8 + selected.File) + + for _, move := range moves { + if move.S1() == selectedSquare { + s.ValidMoves = append(s.ValidMoves, move) + } + } + + for _, move := range s.ValidMoves { + target := move.S2() + square := s.Board[7-target/8][target%8] + square.Dot = true + square.Action = fmt.Sprintf("move?s1=%d&s2=%d", move.S1(), move.S2()) + } + + return nil +} diff --git a/handlers/fun/chess/page.go b/handlers/fun/chess/handlers.go similarity index 76% rename from handlers/fun/chess/page.go rename to handlers/fun/chess/handlers.go index b0094d2..e6d894d 100644 --- a/handlers/fun/chess/page.go +++ b/handlers/fun/chess/handlers.go @@ -1,7 +1,6 @@ package chess import ( - _ "embed" "errors" "fmt" "io" @@ -18,23 +17,47 @@ import ( "github.com/go-chi/chi/v5" "github.com/notnil/chess" - g "maragu.dev/gomponents" h "maragu.dev/gomponents/html" ) -type GameState struct { - Board [8][8]*UISquare - Game *chess.Game - ValidMoves []*chess.Move - MatchID int64 -} - type UIGameState struct { gameState *GameState flip bool } +func (s UIGameState) Render(w io.Writer) error { + rows := make([][]g.Node, 0, 8) + + if s.flip { + for rank := 7; rank >= 0; rank -= 1 { + row := make([]g.Node, 0, 8) + for file := 0; file < 8; file += 1 { + row = append(row, s.gameState.Board[rank][file]) + } + rows = append(rows, row) + } + } else { + for rank := 0; rank < 8; rank += 1 { + row := make([]g.Node, 0, 8) + for file := 0; file < 8; file += 1 { + row = append(row, s.gameState.Board[rank][file]) + } + rows = append(rows, row) + } + } + return h.Div( + h.Class("board"), + h.Div(h.Style("height:100%; width:100%; display:flex; flex-direction:column"), + g.Map(rows, func(row []g.Node) g.Node { + return h.Div(h.Style("flex:1; display:flex"), + g.Map(row, func(square g.Node) g.Node { + return h.Div(h.Style("flex:1"), square) + })) + })), + ).Render(w) +} + type UISquare struct { SVGPiece string Selected bool @@ -91,124 +114,14 @@ func (s UISquare) Render(w io.Writer) error { ).Render(w) } -func (s UIGameState) Render(w io.Writer) error { - rows := make([][]g.Node, 0, 8) - - if s.flip { - for rank := 7; rank >= 0; rank -= 1 { - row := make([]g.Node, 0, 8) - for file := 0; file < 8; file += 1 { - row = append(row, s.gameState.Board[rank][file]) - } - rows = append(rows, row) - } - } else { - for rank := 0; rank < 8; rank += 1 { - row := make([]g.Node, 0, 8) - for file := 0; file < 8; file += 1 { - row = append(row, s.gameState.Board[rank][file]) - } - rows = append(rows, row) - } - } - return h.Div( - h.Class("board"), - h.Div(h.Style("height:100%; width:100%; display:flex; flex-direction:column"), - g.Map(rows, func(row []g.Node) g.Node { - return h.Div(h.Style("flex:1; display:flex"), - g.Map(row, func(square g.Node) g.Node { - return h.Div(h.Style("flex:1"), square) - })) - })), - ).Render(w) -} - -func gameStateFromMatch(match api.ChessMatch) (*GameState, error) { - reader := strings.NewReader(match.Pgn) - fn, err := chess.PGN(reader) - if err != nil { - return nil, err - } - game := chess.NewGame(fn) - - pos := game.Position() - - svgPiece := [13]string{ - "", // empty piece - "/assets/chess/wK.svg", - "/assets/chess/wQ.svg", - "/assets/chess/wR.svg", - "/assets/chess/wB.svg", - "/assets/chess/wN.svg", - "/assets/chess/wP.svg", - "/assets/chess/bK.svg", - "/assets/chess/bQ.svg", - "/assets/chess/bR.svg", - "/assets/chess/bB.svg", - "/assets/chess/bN.svg", - "/assets/chess/bP.svg", - } - - state := GameState{Game: game} - - squareMap := pos.Board().SquareMap() - - for i := 0; i < 64; i++ { - piece := squareMap[chess.Square(i)] - rank := 7 - i/8 - file := i % 8 - state.Board[rank][file] = &UISquare{ - SVGPiece: svgPiece[piece], - Rank: rank, - File: file, - MatchID: match.ID, - Action: fmt.Sprintf("select?rank=%d&file=%d", rank, file), - } - } - - return &state, nil -} - -var ErrSquareNotOccupied = errors.New("no piece on square") - -func (s *GameState) selectSquare(rank, file int) error { - var selected *UISquare - - for _, row := range s.Board { - for _, square := range row { - if square.Rank == rank && square.File == file { - if square.Occupied() { - selected = square - square.Selected = true - square.Action = "unselect" - } - } - } - } - - if selected == nil { - return ErrSquareNotOccupied - } - - // add the dots to the places the peice on the selected square can move to - moves := s.Game.ValidMoves() - - selectedSquare := chess.Square((7-selected.Rank)*8 + selected.File) - - for _, move := range moves { - if move.S1() == selectedSquare { - s.ValidMoves = append(s.ValidMoves, move) - } +func userMatchColor(user api.User, match api.ChessMatch) (chess.Color, error) { + if user.ID == match.BlackUserID { + return chess.Black, nil } - - for _, move := range s.ValidMoves { - target := move.S2() - square := s.Board[7-target/8][target%8] - square.Dot = true - square.Action = fmt.Sprintf("move?s1=%d&s2=%d", move.S1(), move.S2()) + if user.ID == match.WhiteUserID { + return chess.White, nil } - - return nil + return chess.NoColor, fmt.Errorf("current user not part of match") } func (s *service) HandleSelect(w http.ResponseWriter, r *http.Request) { @@ -254,16 +167,6 @@ func (s *service) HandleSelect(w http.ResponseWriter, r *http.Request) { uiGameState.Render(w) } -func userMatchColor(user api.User, match api.ChessMatch) (chess.Color, error) { - if user.ID == match.BlackUserID { - return chess.Black, nil - } - if user.ID == match.WhiteUserID { - return chess.White, nil - } - return chess.NoColor, fmt.Errorf("current user not part of match") -} - func (s *service) HandleDeselect(w http.ResponseWriter, r *http.Request) { ctx := r.Context() currentUser := auth.FromContext(ctx) From 57e34fbee9bc49e7a3c2970b0c5e3db3d040600b Mon Sep 17 00:00:00 2001 From: Ryan Yeske Date: Fri, 1 Aug 2025 20:28:21 -0700 Subject: [PATCH 3/3] Fix missing formatting code --- handlers/fun/chess/match.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/fun/chess/match.go b/handlers/fun/chess/match.go index e944443..703b5c8 100644 --- a/handlers/fun/chess/match.go +++ b/handlers/fun/chess/match.go @@ -63,7 +63,7 @@ func matchOpponent(ctx context.Context, qtx *api.Queries, match api.ChessMatch, } opponent, err := qtx.UserByID(ctx, opponentID) if err != nil { - return nil, fmt.Errorf("UserByID", err) + return nil, fmt.Errorf("UserByID: %w", err) } return &opponent, nil }