fix(misc): label wheel beta slices

This commit is contained in:
2026-07-06 12:26:53 +07:00
parent ea99b6c119
commit 9dc99d4abc
3 changed files with 164 additions and 70 deletions
+47
View File
@@ -3,7 +3,9 @@ package misc
import (
"bytes"
"context"
"image"
"image/gif"
"math"
"strings"
"testing"
"time"
@@ -290,6 +292,51 @@ func TestWheelOfNamesBeta_CurrentOptionTracksPointer(t *testing.T) {
}
}
func TestWheelOfNamesBeta_PointerPointsIntoWheel(t *testing.T) {
img := renderWheelBetaFrame([]string{"Alice", "Bob"}, 0, finalWheelRotation(2, 0), false)
cx := wheelBetaSize / 2
tipY := wheelBetaSize/2 - wheelBetaRadius
if got := img.ColorIndexAt(cx, tipY); got != 1 {
t.Fatalf("pointer tip color = %d, want 1", got)
}
if got := img.ColorIndexAt(cx+5, tipY-10); got != 1 {
t.Fatalf("pointer shoulder color = %d, want 1", got)
}
if got := img.ColorIndexAt(cx+5, tipY); got == 1 {
t.Fatalf("pointer tip is too wide at color index %d", got)
}
if got := img.ColorIndexAt(cx+12, tipY+20); got == 1 {
t.Fatalf("pointer widens below wheel edge at color index %d", got)
}
}
func TestWheelOfNamesBeta_DrawsOptionLabelsInsideSlices(t *testing.T) {
options := []string{"Student", "Teacher", "Parent", "Staff"}
rotation := 0.0
img := renderWheelBetaFrame(options, 0, rotation, false)
segment := 2 * math.Pi / float64(len(options))
angle := rotation + segment/2
centerX := wheelBetaSize/2 + int(math.Round(math.Cos(angle)*float64(wheelBetaSliceLabelRadius)))
baselineY := wheelBetaSize/2 + int(math.Round(math.Sin(angle)*float64(wheelBetaSliceLabelRadius))) + wheelBetaSliceLabelBaselineOffset
bounds := image.Rect(centerX-28, baselineY-14, centerX+28, baselineY+2)
if got := countColorIndex(img, bounds, 1); got == 0 {
t.Fatalf("slice label dark pixels = %d, want > 0", got)
}
}
func countColorIndex(img *image.Paletted, bounds image.Rectangle, colorIndex byte) int {
bounds = bounds.Intersect(img.Bounds())
count := 0
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
if img.ColorIndexAt(x, y) == colorIndex {
count++
}
}
}
return count
}
func TestWheelOfNamesBeta_SendsAnimationWithoutSpoilingCaption(t *testing.T) {
rb, _ := installMisc(t, 999)
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/wheelofnamesbeta Alice"))
+3 -70
View File
@@ -8,11 +8,6 @@ import (
"image/draw"
"image/gif"
"math"
"strings"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
const (
@@ -102,14 +97,15 @@ func renderWheelBetaFrame(options []string, winner int, rotation float64, reveal
}
}
drawWheelSliceLabels(img, options, rotation)
drawCircle(img, cx, cy, 24, 2)
drawCircle(img, cx, cy, 18, 1)
drawPointer(img, cx, cy-wheelBetaRadius-10)
drawPointer(img, cx, cy-wheelBetaRadius)
drawCenteredText(img, "WHEELOFNAMES BETA", cy+wheelBetaRadius+34, 1)
label := "CURRENT"
value := asciiWheelText(options[currentWheelBetaIndex(len(options), rotation)], 28)
if reveal {
label = "WINNER"
label = "RESULT"
value = asciiWheelText(options[winner], 28)
}
drawStatusBand(img, label, value)
@@ -133,66 +129,3 @@ func normalizeAngle(theta float64) float64 {
}
return theta
}
func drawCircle(img *image.Paletted, cx, cy, radius int, colorIndex byte) {
r2 := radius * radius
for y := cy - radius; y <= cy+radius; y++ {
for x := cx - radius; x <= cx+radius; x++ {
dx, dy := x-cx, y-cy
if dx*dx+dy*dy <= r2 {
img.SetColorIndex(x, y, colorIndex)
}
}
}
}
func drawPointer(img *image.Paletted, cx, tipY int) {
for y := 0; y < 34; y++ {
half := y / 2
for x := cx - half; x <= cx+half; x++ {
img.SetColorIndex(x, tipY+y, 1)
}
}
}
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, label, 250, 1)
drawCenteredText(img, value, 270, 1)
}
func drawCenteredText(img *image.Paletted, text string, baselineY int, colorIndex byte) {
face := basicfont.Face7x13
width := font.MeasureString(face, text).Ceil()
x := (wheelBetaSize - width) / 2
drawer := font.Drawer{
Dst: img,
Src: image.NewUniform(wheelBetaPalette[colorIndex]),
Face: face,
Dot: fixed.P(x, baselineY),
}
drawer.DrawString(text)
}
func asciiWheelText(s string, limit int) string {
var b strings.Builder
for _, r := range s {
if b.Len() >= limit {
break
}
if r < 32 || r > 126 {
b.WriteByte('?')
continue
}
b.WriteRune(r)
}
out := strings.TrimSpace(b.String())
if out == "" {
return "winner"
}
return out
}
@@ -0,0 +1,114 @@
package misc
import (
"image"
"math"
"strings"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
const (
wheelBetaSliceLabelRadius = 64
wheelBetaSliceLabelBaselineOffset = 5
)
func drawWheelSliceLabels(img *image.Paletted, options []string, rotation float64) {
if len(options) == 0 {
return
}
cx, cy := wheelBetaSize/2, wheelBetaSize/2
segment := 2 * math.Pi / float64(len(options))
limit := wheelBetaSliceLabelLimit(len(options))
for idx, option := range options {
angle := rotation + (float64(idx)+0.5)*segment
centerX := cx + int(math.Round(math.Cos(angle)*float64(wheelBetaSliceLabelRadius)))
baselineY := cy + int(math.Round(math.Sin(angle)*float64(wheelBetaSliceLabelRadius))) + wheelBetaSliceLabelBaselineOffset
text := asciiWheelText(option, limit)
drawCenteredTextAt(img, text, centerX+1, baselineY+1, 2)
drawCenteredTextAt(img, text, centerX, baselineY, 1)
}
}
func wheelBetaSliceLabelLimit(optionCount int) int {
switch {
case optionCount <= 2:
return 14
case optionCount <= 4:
return 11
case optionCount <= 6:
return 8
default:
return 6
}
}
func drawCircle(img *image.Paletted, cx, cy, radius int, colorIndex byte) {
r2 := radius * radius
for y := cy - radius; y <= cy+radius; y++ {
for x := cx - radius; x <= cx+radius; x++ {
dx, dy := x-cx, y-cy
if dx*dx+dy*dy <= r2 {
img.SetColorIndex(x, y, colorIndex)
}
}
}
}
func drawPointer(img *image.Paletted, cx, tipY int) {
for y := 0; y < 34; y++ {
half := y / 2
rowY := tipY - y
for x := cx - half; x <= cx+half; x++ {
img.SetColorIndex(x, rowY, 1)
}
}
}
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, label, 250, 1)
drawCenteredText(img, value, 270, 1)
}
func drawCenteredText(img *image.Paletted, text string, baselineY int, colorIndex byte) {
drawCenteredTextAt(img, text, wheelBetaSize/2, baselineY, colorIndex)
}
func drawCenteredTextAt(img *image.Paletted, text string, centerX, baselineY int, colorIndex byte) {
face := basicfont.Face7x13
width := font.MeasureString(face, text).Ceil()
x := centerX - width/2
drawer := font.Drawer{
Dst: img,
Src: image.NewUniform(wheelBetaPalette[colorIndex]),
Face: face,
Dot: fixed.P(x, baselineY),
}
drawer.DrawString(text)
}
func asciiWheelText(s string, limit int) string {
var b strings.Builder
for _, r := range s {
if b.Len() >= limit {
break
}
if r < 32 || r > 126 {
b.WriteByte('?')
continue
}
b.WriteRune(r)
}
out := strings.TrimSpace(b.String())
if out == "" {
return "winner"
}
return out
}