dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 79629fa300022a89071c99e7bebf2e926827dc76
parent fa56e693ce1d1d666be5e05f267a90f20320249d
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Sun, 11 Jun 2023 11:38:49 -0700

insanity

Diffstat:
Mpkg/web/handlers/handlers.go | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mpkg/web/handlers/interceptors/chess.go | 184+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 215 insertions(+), 79 deletions(-)

diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -4871,6 +4871,42 @@ html, body { } </style>` +func chessGamefoolMate(g *interceptors.ChessGame) { + g.MoveStr("f3") + g.MoveStr("e5") + g.MoveStr("g4") +} + +func chessGameCheck(g *interceptors.ChessGame) { + g.MoveStr("Nc3") + g.MoveStr("h6") + g.MoveStr("Nb5") + g.MoveStr("h5") +} + +func chessGamePromoW(g *interceptors.ChessGame) { + g.MoveStr("h4") + g.MoveStr("g5") + g.MoveStr("hxg5") + g.MoveStr("h5") + g.MoveStr("g6") + g.MoveStr("h4") + g.MoveStr("g7") + g.MoveStr("h3") +} + +func chessGamePromoB(g *interceptors.ChessGame) { + g.MoveStr("a3") + g.MoveStr("c5") + g.MoveStr("a4") + g.MoveStr("c4") + g.MoveStr("a5") + g.MoveStr("c3") + g.MoveStr("a6") + g.MoveStr("cxb2") + g.MoveStr("axb7") +} + func ChessGameHandler(c echo.Context) error { authUser := c.Get("authUser").(*database.User) db := c.Get("database").(*database.DkfDB) @@ -4883,6 +4919,7 @@ func ChessGameHandler(c echo.Context) error { user2, _ := db.GetUserByID(30814) interceptors.ChessInstance.NewGame(key, user1, user2) g = interceptors.ChessInstance.GetGame(key) + chessGamePromoB(g) //return c.Redirect(http.StatusFound, "/") } @@ -4893,7 +4930,7 @@ func ChessGameHandler(c echo.Context) error { if msg == "resign" { resignColor := utils.Ternary(isFlipped, chess.Black, chess.White) g.Game.Resign(resignColor) - interceptors.ChessPubSub.Pub(key, true) + interceptors.ChessPubSub.Pub(key, interceptors.ChessMove{}) } else { if err := interceptors.ChessInstance.SendMove(key, authUser.ID, g, c); err != nil { logrus.Error(err) @@ -4914,7 +4951,7 @@ func ChessGameHandler(c echo.Context) error { isYourTurn := isYourTurnFn() // If you are not a spectator, and it's your turn to play, we just render the form directly. - if isYourTurn { + if isYourTurn || g.Game.Outcome() != chess.NoOutcome { c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) c.Response().WriteHeader(http.StatusOK) _, _ = c.Response().Write([]byte(cssReset)) @@ -4961,10 +4998,11 @@ Loop: } if g.Game.Outcome() != chess.NoOutcome { + _, _ = c.Response().Write([]byte(`<meta http-equiv="refresh" content="0" />`)) break } - _, _, err := sub.ReceiveTimeout2(1*time.Second, quit) + _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) if err != nil { if err == pubsub.ErrCancelled { break Loop @@ -4974,14 +5012,68 @@ Loop: i++ - var card1 string - if isSpectator { - card1 = g.DrawSpectatorCard(isFlipped) + //id1 := g.PiecesCache[payload.Move.S1()] + x1 := int(payload.Move.S1().File()) + y1 := int(payload.Move.S1().Rank()) + x := int(payload.Move.S2().File()) + y := int(payload.Move.S2().Rank()) + if isFlipped { + x1 = 7 - x1 + x = 7 - x } else { - card1 = g.DrawPlayerCard(isFlipped, isYourTurnFn()) + y1 = 7 - y1 + y = 7 - y + } + + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>@keyframes move_anim_%d { from { left: calc(%d*12.5%%); top: calc(%d*12.5%%); } to { left: calc(%d*12.5%%); top: calc(%d*12.5%%); } }</style>`, i, x1, y1, x, y))) + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#%s { animation-name: move_anim_%d; animation-duration: 400ms; animation-fill-mode: forwards; }</style>`, payload.IDStr1, i))) + if payload.IDStr2 != "" { + _, _ = c.Response().Write([]byte(`<style>#` + payload.IDStr2 + ` { display: none; }</style>`)) + } + + if payload.Move.Promo() != chess.NoPieceType { + go func(payload interceptors.ChessMove, c echo.Context) { + select { + case <-time.After(400 * time.Millisecond): + case <-quit: + return + } + cTurn := utils.Ternary(payload.Turn == chess.White, "w", "b") + promoImg := "/public/img/chess/" + cTurn + "Q.png" + switch payload.Move.Promo() { + case chess.Queen: + promoImg = "/public/img/chess/" + cTurn + "Q.png" + case chess.Rook: + promoImg = "/public/img/chess/" + cTurn + "R.png" + case chess.Bishop: + promoImg = "/public/img/chess/" + cTurn + "B.png" + case chess.Knight: + promoImg = "/public/img/chess/" + cTurn + "N.png" + } + _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#%s { background-image: url("%s") !important; }</style>`, payload.IDStr1, promoImg))) + c.Response().Flush() + }(payload, c) } - _, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#div_%d { display: none; }</style>`, i-1))) - _, _ = c.Response().Write([]byte(fmt.Sprintf(`<div id="div_%d">%s</div>`, i, card1))) + + _, _ = c.Response().Write([]byte(`<style>.square { background-color: transparent !important; }</style>`)) + _, _ = c.Response().Write([]byte(`<style>.square_` + strconv.Itoa(int(payload.Move.S1())) + `, .square_` + strconv.Itoa(int(payload.Move.S2())) + ` { background-color: rgba(0, 255, 0, 0.2) !important; }</style>`)) + + _, _ = c.Response().Write([]byte(`<style>#img_4 { background-color: transparent !important; }</style>`)) + _, _ = c.Response().Write([]byte(`<style>#img_60 { background-color: transparent !important; }</style>`)) + if payload.CheckW { + _, _ = c.Response().Write([]byte(`<style>#img_4 { background-color: rgba(255, 0, 0, 0.4) !important; }</style>`)) + } else if payload.CheckB { + _, _ = c.Response().Write([]byte(`<style>#img_60 { background-color: rgba(255, 0, 0, 0.4) !important; }</style>`)) + } + + //var card1 string + //if isSpectator { + // card1 = g.DrawSpectatorCard(isFlipped) + //} else { + // card1 = g.DrawPlayerCard(isFlipped, isYourTurnFn()) + //} + //_, _ = c.Response().Write([]byte(fmt.Sprintf(`<style>#div_%d { display: none; }</style>`, i-1))) + //_, _ = c.Response().Write([]byte(fmt.Sprintf(`<div id="div_%d">%s</div>`, i, card1))) c.Response().Flush() } return nil diff --git a/pkg/web/handlers/interceptors/chess.go b/pkg/web/handlers/interceptors/chess.go @@ -41,6 +41,7 @@ type ChessGame struct { Player1 *ChessPlayer Player2 *ChessPlayer CreatedAt time.Time + PiecesCache map[chess.Square]string } func newChessPlayer(player database.User) *ChessPlayer { @@ -54,12 +55,19 @@ func newChessPlayer(player database.User) *ChessPlayer { func newChessGame(gameKey string, player1, player2 database.User) *ChessGame { g := new(ChessGame) + g.PiecesCache = make(map[chess.Square]string) g.CreatedAt = time.Now() g.Key = gameKey g.Game = chess.NewGame() g.lastUpdated = time.Now() g.Player1 = newChessPlayer(player1) g.Player2 = newChessPlayer(player2) + for i := 0; i < 64; i++ { + sq := chess.Square(i) + if g.Game.Position().Board().Piece(sq) != chess.NoPiece { + g.PiecesCache[sq] = "img_" + strconv.Itoa(int(sq)) + } + } return g } @@ -99,7 +107,12 @@ const ( boardSize = 8 * sqSize ) -func renderBoardHTML(position *chess.Position, isFlipped bool, imgB64 string, spectator bool) string { +func (g *ChessGame) renderBoardHTML1(position *chess.Position, isFlipped bool, imgB64 string, spectator bool) string { + moves := g.Game.Moves() + var last *chess.Move + if len(moves) > 0 { + last = moves[len(moves)-1] + } boardMap := position.Board().SquareMap() out := `<style> .newBoard { @@ -113,10 +126,11 @@ func renderBoardHTML(position *chess.Position, isFlipped bool, imgB64 string, sp } .newBoard td { } -.newBoard img { +.newBoard .img { position: absolute; width: 12.5%; height: 12.5%; + background-size: 100%; } .idk { width: 100%; @@ -177,17 +191,29 @@ input[type=checkbox]:checked + label { } out += "<tr>" } - out += "<td>" sq := chess.Square(id) + idStr := strconv.Itoa(id) + out += `<td class="square square_` + idStr + `"` + if last != nil && (last.S1() == sq || last.S2() == sq) { + out += ` style="background-color: rgba(0, 255, 0, 0.2);"` + } + out += ">" sqPiece := boardMap[sq] p := sqPiece - out += `<div class="idk">` if p != chess.NoPiece { - out += `<img src="/public/img/chess/` + p.Color().String() + pieceTypeMap[p.Type()] + `.png" alt="" />` + x := i % 8 + y := i / 8 + pidStr := g.PiecesCache[sq] + //out += `<img id="` + pidStr + `" src="/public/img/chess/` + p.Color().String() + pieceTypeMap[p.Type()] + `.png" alt="" style="left: calc(` + strconv.Itoa(x) + `*12.5%); top: calc(` + strconv.Itoa(y) + `*12.5%);` + out += `<div id="` + pidStr + `" class="img" style="left: calc(` + strconv.Itoa(x) + `*12.5%); top: calc(` + strconv.Itoa(y) + `*12.5%); background-image: url(/public/img/chess/` + p.Color().String() + pieceTypeMap[p.Type()] + `.png);` + if last != nil && p.Color() == g.Game.Position().Turn() && p.Type() == chess.King && last.HasTag(chess.Check) { + out += ` background-color: rgba(255, 0, 0, 0.4);` + } + out += `"></div>` } + out += `<div class="idk">` if !spectator { - idStr := strconv.Itoa(id) - out += `<input name="sq_` + idStr + `" ID="sq_` + idStr + `" type="checkbox" value="1" /><label for="sq_` + idStr + `"></label>` + out += `<input name="sq_` + idStr + `" id="sq_` + idStr + `" type="checkbox" value="1" /><label for="sq_` + idStr + `"></label>` } out += "</div>" out += "</td>" @@ -236,26 +262,15 @@ func renderSquare(ctx *gg.Context, sq chess.Square, last *chess.Move, turn chess ctx.Fill() ctx.Pop() // Draw previous move - if last != nil { - if last.S1() == sq || last.S2() == sq { - ctx.Push() - ctx.SetRGBA(0, 1, 0, 0.1) - ctx.DrawRectangle(float64(x), float64(y), sqSize, sqSize) - ctx.Fill() - ctx.Pop() - } - // Draw check - p := sqPiece - if p != chess.NoPiece { - if p.Type() == chess.King && p.Color() == turn && last.HasTag(chess.Check) { - ctx.Push() - ctx.SetRGBA(1, 0, 0, 0.4) - ctx.DrawRectangle(float64(x), float64(y), sqSize, sqSize) - ctx.Fill() - ctx.Pop() - } - } - } + //if last != nil { + // if last.S1() == sq || last.S2() == sq { + // ctx.Push() + // ctx.SetRGBA(0, 1, 0, 0.1) + // ctx.DrawRectangle(float64(x), float64(y), sqSize, sqSize) + // ctx.Fill() + // ctx.Pop() + // } + //} ctx.Push() ctx.SetColor(color.RGBA{R: 0, G: 0, B: 0, A: 180}) @@ -266,15 +281,6 @@ func renderSquare(ctx *gg.Context, sq chess.Square, last *chess.Move, turn chess ctx.DrawString(sq.Rank().String(), float64(x+1), float64(y+11)) } ctx.Pop() - - //// draw piece - //p := sqPiece - //if p != chess.NoPiece { - // img := getFile("img/chess/" + p.Color().String() + pieceTypeMap[p.Type()] + ".png") - // ctx.Push() - // ctx.DrawImage(img, x, y) - // ctx.Pop() - //} } var pieceTypeMap = map[chess.PieceType]string{ @@ -359,7 +365,7 @@ input[type=checkbox]:checked + label { func (g *ChessGame) renderBoardHTML(isFlipped bool, imgB64 string, spectator bool) string { position := g.Game.Position() - out := renderBoardHTML(position, isFlipped, imgB64, spectator) + out := g.renderBoardHTML1(position, isFlipped, imgB64, spectator) return out } @@ -414,34 +420,30 @@ func (g *ChessGame) drawPlayerCard(roomName string, isBlack, isYourTurn bool) st <button type="submit" style="background-color: #aaa; margin: 5px 0;">Resign</button> </form> <div> - {{ if .IsYourTurn }} - <form method="post"{{ if .InChat }} action="/api/v1/chess"{{ end }} style="aspect-ratio: 1/1; height: 70%;"> - {{ .Table }} - {{ if .InChat }} - <input type="hidden" name="room" value="{{ .RoomName }}" /> - <input type="hidden" name="enemyUsername" value="{{ .Username }}" /> - <input type="hidden" name="move" value="move" /> - {{ else }} - <input type="hidden" name="message" value="/pm {{ .Username }} /c move" /> - {{ end }} - <div style="width: 100%; display: flex; margin: 5px 0;"> - <div> - <button type="submit" style="background-color: #aaa;">Move</button> - </div> - <div style="margin-left: auto;"> - <span style="color: #aaa; margin-left: 20px;">Promo:</span> - <select name="promotion" style="background-color: #aaa;"> - <option value="queen">Queen</option> - <option value="rook">Rook</option> - <option value="knight">Knight</option> - <option value="bishop">Bishop</option> - </select> - </div> - </div> - </form> - {{ else }} + <form method="post"{{ if .InChat }} action="/api/v1/chess"{{ end }} style="aspect-ratio: 1/1; height: 70%;"> {{ .Table }} - {{ end }} + {{ if .InChat }} + <input type="hidden" name="room" value="{{ .RoomName }}" /> + <input type="hidden" name="enemyUsername" value="{{ .Username }}" /> + <input type="hidden" name="move" value="move" /> + {{ else }} + <input type="hidden" name="message" value="/pm {{ .Username }} /c move" /> + {{ end }} + <div style="width: 100%; display: flex; margin: 5px 0;"> + <div> + <button type="submit" style="background-color: #aaa;">Move</button> + </div> + <div style="margin-left: auto;"> + <span style="color: #aaa; margin-left: 20px;">Promo:</span> + <select name="promotion" style="background-color: #aaa;"> + <option value="queen">Queen</option> + <option value="rook">Rook</option> + <option value="knight">Knight</option> + <option value="bishop">Bishop</option> + </select> + </div> + </div> + </form> </div> {{ end }} </td> @@ -470,7 +472,7 @@ func (g *ChessGame) drawPlayerCard(roomName string, isBlack, isYourTurn bool) st "White": g.Player1, "Black": g.Player2, "Username": enemy.Username, - "Table": template.HTML(g.renderBoardHTML(isBlack, imgB64, !isYourTurn)), + "Table": template.HTML(g.renderBoardHTML(isBlack, imgB64, false)), "ImgB64": imgB64, "Outcome": g.Game.Outcome().String(), "GameOver": g.Game.Outcome() != chess.NoOutcome, @@ -636,11 +638,13 @@ func (b *Chess) SendMove(gameKey string, userID database.UserID, g *ChessGame, c var moveStr string validMoves := g.Game.Position().ValidMoves() var found bool + var mov chess.Move for _, move := range validMoves { if (move.S1() == selectedSquares[0] && move.S2() == selectedSquares[1] && (move.Promo() == chess.NoPieceType || move.Promo() == promo)) || (move.S1() == selectedSquares[1] && move.S2() == selectedSquares[0] && (move.Promo() == chess.NoPieceType || move.Promo() == promo)) { moveStr = chess.AlgebraicNotation{}.Encode(g.Game.Position(), move) found = true + mov = *move break } } @@ -649,13 +653,26 @@ func (b *Chess) SendMove(gameKey string, userID database.UserID, g *ChessGame, c return fmt.Errorf("invalid move %s %s", selectedSquares[0], selectedSquares[1]) } + //fmt.Println(moveStr) + + turn := g.Game.Position().Turn() _ = g.Game.MoveStr(moveStr) g.lastUpdated = time.Now() - if g.Game.Outcome() != chess.NoOutcome { - //delete(b.games, gameKey) - } - ChessPubSub.Pub(gameKey, true) + idStr1 := g.PiecesCache[mov.S1()] + idStr2 := g.PiecesCache[mov.S2()] + chessMov := ChessMove{ + IDStr1: idStr1, + IDStr2: idStr2, + CheckW: g.Game.Position().Turn() == chess.White && mov.HasTag(chess.Check), + CheckB: g.Game.Position().Turn() == chess.Black && mov.HasTag(chess.Check), + Move: mov, + Turn: turn, + } + delete(g.PiecesCache, mov.S1()) + delete(g.PiecesCache, mov.S2()) + g.PiecesCache[mov.S2()] = idStr1 + ChessPubSub.Pub(gameKey, chessMov) // Notify (pm) the opponent that you made a move if opponent.NotifyChessMove { @@ -671,7 +688,34 @@ func (b *Chess) SendMove(gameKey string, userID database.UserID, g *ChessGame, c return nil } -var ChessPubSub = pubsub.NewPubSub[bool]() +func (g *ChessGame) MoveStr(m string) { + validMoves := g.Game.Position().ValidMoves() + var mov chess.Move + for _, move := range validMoves { + moveStr := chess.AlgebraicNotation{}.Encode(g.Game.Position(), move) + if moveStr == m { + mov = *move + break + } + } + idStr1 := g.PiecesCache[mov.S1()] + //idStr2 := g.PiecesCache[mov.S2()] + delete(g.PiecesCache, mov.S1()) + delete(g.PiecesCache, mov.S2()) + g.PiecesCache[mov.S2()] = idStr1 + _ = g.Game.MoveStr(m) +} + +type ChessMove struct { + IDStr1 string + IDStr2 string + CheckW bool + CheckB bool + Move chess.Move + Turn chess.Color +} + +var ChessPubSub = pubsub.NewPubSub[ChessMove]() func (b *Chess) InterceptMsg(cmd *command.Command) { m := cRgx.FindStringSubmatch(cmd.Message)