From 7c239cd97678dd8133b364d696a97c7ddff585a0 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 6 Jul 2026 11:47:41 +0700 Subject: [PATCH] fix(misc): improve wheel beta animation --- internal/modules/misc/handlers_test.go | 18 +++++++++++--- internal/modules/misc/wheelofnames_beta.go | 24 ++++++++++++++----- .../modules/misc/wheelofnames_beta_command.go | 2 +- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/internal/modules/misc/handlers_test.go b/internal/modules/misc/handlers_test.go index 17df0a7..da0f23a 100644 --- a/internal/modules/misc/handlers_test.go +++ b/internal/modules/misc/handlers_test.go @@ -271,7 +271,16 @@ func TestWheelOfNamesBeta_RenderGIFTiming(t *testing.T) { } } -func TestWheelOfNamesBeta_SendsAnimationWithWinnerCaption(t *testing.T) { +func TestWheelOfNamesBeta_CurrentOptionTracksPointer(t *testing.T) { + for winner := range []string{"Alice", "Bob", "Carol", "Dana"} { + rotation := finalWheelRotation(4, winner) + if got := currentWheelBetaIndex(4, rotation); got != winner { + t.Fatalf("currentWheelBetaIndex at final rotation = %d, want %d", got, winner) + } + } +} + +func TestWheelOfNamesBeta_SendsAnimationWithoutSpoilingCaption(t *testing.T) { rb, _ := installMisc(t, 999) rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/wheelofnamesbeta Alice")) @@ -279,8 +288,11 @@ func TestWheelOfNamesBeta_SendsAnimationWithWinnerCaption(t *testing.T) { if call.Method != "sendAnimation" { t.Fatalf("method = %q, want sendAnimation", call.Method) } - if got := call.Form["caption"]; got != "Winner: Alice" { - t.Fatalf("caption = %q, want Winner: Alice", got) + if got := call.Form["caption"]; got != "Spinning..." { + t.Fatalf("caption = %q, want Spinning...", got) + } + if strings.Contains(call.Form["caption"], "Alice") { + t.Fatalf("caption spoils winner: %q", call.Form["caption"]) } if got := call.Form["duration"]; got != "7" { t.Fatalf("duration = %q, want 7", got) diff --git a/internal/modules/misc/wheelofnames_beta.go b/internal/modules/misc/wheelofnames_beta.go index d2fac80..dd55e75 100644 --- a/internal/modules/misc/wheelofnames_beta.go +++ b/internal/modules/misc/wheelofnames_beta.go @@ -37,6 +37,8 @@ var wheelBetaPalette = color.Palette{ color.RGBA{R: 235, G: 117, B: 164, A: 255}, } +var wheelBetaSliceColorIndexes = []byte{3, 4, 5, 6, 7, 8, 9} + func renderWheelOfNamesBetaGIF(options []string, winner int) ([]byte, error) { if len(options) == 0 { return nil, fmt.Errorf("no options") @@ -51,7 +53,8 @@ func renderWheelOfNamesBetaGIF(options []string, winner int) ([]byte, error) { startRotation := finalRotation - 8*2*math.Pi for i := 0; i < wheelBetaSpinFrames; i++ { t := float64(i) / float64(wheelBetaSpinFrames-1) - progress := 1 - math.Pow(1-t, 3) + remaining := 1 - t + progress := 1 - remaining*remaining*remaining rotation := startRotation + (finalRotation-startRotation)*progress frames = append(frames, renderWheelBetaFrame(options, winner, rotation, false)) delays = append(delays, wheelBetaSpinDelay) @@ -92,7 +95,7 @@ func renderWheelBetaFrame(options []string, winner int, rotation float64, reveal } theta := normalizeAngle(math.Atan2(float64(dy), float64(dx)) - rotation) idx := int(theta / segment) - colorIndex := byte(3 + idx%7) + colorIndex := wheelBetaSliceColorIndexes[idx%len(wheelBetaSliceColorIndexes)] img.SetColorIndex(x, y, colorIndex) } } @@ -101,9 +104,13 @@ func renderWheelBetaFrame(options []string, winner int, rotation float64, reveal drawCircle(img, cx, cy, 18, 1) drawPointer(img, cx, cy-wheelBetaRadius-10) drawCenteredText(img, "WHEELOFNAMES BETA", cy+wheelBetaRadius+34, 1) + label := "CURRENT" + value := asciiWheelText(options[currentWheelBetaIndex(len(options), rotation)], 28) if reveal { - drawResultBand(img, asciiWheelText(options[winner], 28)) + label = "WINNER" + value = asciiWheelText(options[winner], 28) } + drawStatusBand(img, label, value) return img } @@ -112,6 +119,11 @@ func finalWheelRotation(optionCount, winner int) float64 { return -math.Pi/2 - (float64(winner)+0.5)*segment } +func currentWheelBetaIndex(optionCount int, rotation float64) int { + segment := 2 * math.Pi / float64(optionCount) + return int(normalizeAngle(-math.Pi/2-rotation) / segment) +} + func normalizeAngle(theta float64) float64 { theta = math.Mod(theta, 2*math.Pi) if theta < 0 { @@ -141,14 +153,14 @@ func drawPointer(img *image.Paletted, cx, tipY int) { } } -func drawResultBand(img *image.Paletted, winner string) { +func drawStatusBand(img *image.Paletted, label, value string) { for y := 230; y < 282; y++ { for x := 28; x < wheelBetaSize-28; x++ { img.SetColorIndex(x, y, 2) } } - drawCenteredText(img, "WINNER", 250, 1) - drawCenteredText(img, winner, 270, 1) + drawCenteredText(img, label, 250, 1) + drawCenteredText(img, value, 270, 1) } func drawCenteredText(img *image.Paletted, text string, baselineY int, colorIndex byte) { diff --git a/internal/modules/misc/wheelofnames_beta_command.go b/internal/modules/misc/wheelofnames_beta_command.go index 9cdbf02..c78d6aa 100644 --- a/internal/modules/misc/wheelofnames_beta_command.go +++ b/internal/modules/misc/wheelofnames_beta_command.go @@ -46,7 +46,7 @@ func wheelOfNamesBetaCommand() modules.Command { Duration: wheelBetaDuration, Width: wheelBetaSize, Height: wheelBetaSize, - Caption: "Winner: " + options[winner], + Caption: "Spinning...", }) if err != nil { log.Warn("wheelofnamesbeta send animation failed", "chat", update.Message.Chat.ID, "err", err)