ZeroBot-Plugin/plugin/wordle/wordle.go
2022-06-04 14:55:15 +08:00

275 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package wordle 猜单词
package wordle
import (
"errors"
"fmt"
"image/color"
"math/rand"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/FloatTech/AnimeAPI/tl"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/writer"
"github.com/fogleman/gg"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/extension/single"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
errLengthNotEnough = errors.New("length not enough")
errUnknownWord = errors.New("unknown word")
errTimesRunOut = errors.New("times run out")
)
const (
match = iota
exist
notexist
undone
)
var colors = [...]color.RGBA{
{125, 166, 108, 255},
{199, 183, 96, 255},
{123, 123, 123, 255},
{219, 219, 219, 255},
}
var classdict = map[string]int{
"": 5,
"五阶": 5,
"六阶": 6,
"七阶": 7,
}
type dictionary map[int]struct {
dict []string
cet4 []string
}
var words = make(dictionary)
func init() {
en := control.Register("wordle", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "猜单词\n" +
"- 个人猜单词\n" +
"- 团队猜单词",
PublicDataFolder: "Wordle",
}).ApplySingle(single.New(
single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }),
single.WithPostFn[int64](func(ctx *zero.Ctx) {
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("已经有正在进行的游戏..."),
),
)
}),
))
en.OnRegex(`^(个人|团队)(五阶|六阶|七阶)?猜单词$`, zero.OnlyGroup, ctxext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
var errcnt uint32
var wg sync.WaitGroup
var mu sync.Mutex
for i := 5; i <= 7; i++ {
wg.Add(2)
go func(i int) {
defer wg.Done()
dc, err := en.GetLazyData(fmt.Sprintf("cet-4_%d.txt", i), true)
if err != nil {
atomic.AddUint32(&errcnt, 1)
return
}
c := strings.Split(binary.BytesToString(dc), "\n")
sort.Strings(c)
mu.Lock()
tmp := words[i]
tmp.cet4 = c
words[i] = tmp
mu.Unlock()
}(i)
go func(i int) {
defer wg.Done()
dd, err := en.GetLazyData(fmt.Sprintf("dict_%d.txt", i), true)
if err != nil {
atomic.AddUint32(&errcnt, 1)
return
}
d := strings.Split(binary.BytesToString(dd), "\n")
sort.Strings(d)
mu.Lock()
tmp := words[i]
tmp.dict = d
words[i] = tmp
mu.Unlock()
}(i)
}
wg.Wait()
if errcnt > 0 {
ctx.SendChain(message.Text("ERROR:下载字典时发生", errcnt, "个错误"))
return false
}
return true
},
)).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
class := classdict[ctx.State["regex_matched"].([]string)[2]]
target := words[class].cet4[rand.Intn(len(words[class].cet4))]
tt, err := tl.Translate(target)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
game := newWordleGame(target)
_, img, cl, _ := game("")
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
message.ImageBytes(img),
message.Text("你有", class+1, "次机会猜出单词,单词长度为", class, ",请发送单词"),
),
)
cl()
var next *zero.FutureEvent
if ctx.State["regex_matched"].([]string)[1] == "个人" {
next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)),
zero.OnlyGroup, ctx.CheckSession())
} else {
next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)),
zero.OnlyGroup, zero.CheckGroup(ctx.Event.GroupID))
}
var win bool
recv, cancel := next.Repeat()
defer cancel()
tick := time.NewTimer(105 * time.Second)
after := time.NewTimer(120 * time.Second)
for {
select {
case <-tick.C:
ctx.SendChain(message.Text("猜单词你还有15s作答时间"))
case <-after.C:
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("猜单词超时,游戏结束...答案是: ", target, "(", tt, ")"),
),
)
return
case c := <-recv:
tick.Reset(105 * time.Second)
after.Reset(120 * time.Second)
win, img, cl, err = game(c.Event.Message.String())
switch {
case win:
tick.Stop()
after.Stop()
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.ImageBytes(img),
message.Text("太棒了,你猜出来了!答案是: ", target, "(", tt, ")"),
),
)
cl()
return
case err == errTimesRunOut:
tick.Stop()
after.Stop()
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.ImageBytes(img),
message.Text("游戏结束...答案是: ", target, "(", tt, ")"),
),
)
cl()
return
case err == errLengthNotEnough:
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("单词长度错误"),
),
)
case err == errUnknownWord:
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("你确定存在这样的单词吗?"),
),
)
default:
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.ImageBytes(img),
),
)
cl()
}
}
}
})
}
func newWordleGame(target string) func(string) (bool, []byte, func(), error) {
var class = len(target)
record := make([]string, 0, len(target)+1)
return func(s string) (win bool, data []byte, cl func(), err error) {
if s != "" {
s = strings.ToLower(s)
if target == s {
win = true
} else {
if len(s) != len(target) {
err = errLengthNotEnough
return
}
i := sort.SearchStrings(words[class].dict, s)
if i >= len(words[class].dict) || words[class].dict[i] != s {
err = errUnknownWord
return
}
}
record = append(record, s)
if len(record) >= cap(record) {
err = errTimesRunOut
}
}
var side = 20
var space = 10
ctx := gg.NewContext((side+4)*class+space*2-4, (side+4)*(class+1)+space*2-4)
ctx.SetColor(color.RGBA{255, 255, 255, 255})
ctx.Clear()
for i := 0; i < class+1; i++ {
for j := 0; j < class; j++ {
if len(record) > i {
ctx.DrawRectangle(float64(space+j*(side+4)), float64(space+i*(side+4)), float64(side), float64(side))
switch {
case record[i][j] == target[j]:
ctx.SetColor(colors[match])
case strings.IndexByte(target, record[i][j]) != -1:
ctx.SetColor(colors[exist])
default:
ctx.SetColor(colors[notexist])
}
ctx.Fill()
ctx.SetColor(color.RGBA{255, 255, 255, 255})
ctx.DrawString(strings.ToUpper(string(record[i][j])), float64(10+j*(side+4)+7), float64(10+i*(side+4)+15))
} else {
ctx.DrawRectangle(float64(10+j*(side+4)+1), float64(10+i*(side+4)+1), float64(side-2), float64(side-2))
ctx.SetLineWidth(1)
ctx.SetColor(colors[undone])
ctx.Stroke()
}
}
}
data, cl = writer.ToBytes(ctx.Image())
return
}
}