you are better than you think

一次小的工程实践-看图猜成语

· by thur · Read in about 2 min · (241 Words)
工程实践

一 背景

偶尔看到其他机器人有很多插件,有个看图猜成语的很有意思。连续一段时间都在处理客户的问题,刚好周五晚上,顺道做个适合当前框架的插件。

看图猜成语是一个适合群聊的游戏,它是通过调用api实现的。这个api会返回一个图片链接和对应的成语, 再没有回答对正确答案之前,会逐渐做出提示。 idiom.jpg

分析

背景交代完毕。网上找到了一个比较接近的插件, 看了下跟群里用得还不太一样。不过好在是找到了这个聚合的API。 分析一下实现路径:

  1. 用户发出命令提示,游戏开始, 给出游戏开始提示(比如用户发出看图猜成语,则游戏开始)
  2. 调用api后,得到答案和图片链接
  3. 设置一个定时器给出逐步提示
  4. 如果有人给出了正确答案,终止提示,游戏结束
  5. 如果没人回答出来,则最多给到3个字的提示
  6. 2分钟超时后,给出正确答案,提示游戏结束

提示的部分,比如四字成语一帆风顺,第一次提示:?帆??, 第二次提示:一帆??, 第三次提示:一帆?顺。群里的插件其实细节处理更好,比如第一次提示不会带出最关键的字。我们先做个简单提示,随机一个成语长度-1的位置列表,定时器每次启动把对应位置的字暴露出来。

  • 处理中文要转为[]rune ,否则长度计算不准。
  • 这里有个洗牌算法,是一个保证公平性和效率的算法。
func generateRandomIndexes(s string) []int {
	// 将字符串转换为[]rune以正确处理Unicode字符
	runes := []rune(s)
	length := len(runes)

	if length == 0 {
		return []int{}
	}

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	indexes := make([]int, length)
	for i := range indexes {
		indexes[i] = i
	}

	// Fisher-Yates洗牌
	for i := length - 1; i > 0; i-- {
		j := r.Intn(i + 1)
		indexes[i], indexes[j] = indexes[j], indexes[i]
	}

	return indexes
}

接下来就是图片处理,不管是wine还是win下跑的机器人要求图片必须是本地的,所以得有一个下载图片到本地的功能。我的server又是在Linux跑的,所以得额外挂一个模块,接收图片地址和指定目录,实现下载图片和写入指定目录。这样发送图片的控制逻辑,依然在server上来完成。

func imageHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "仅支持GET请求", http.StatusMethodNotAllowed)
		return
	}

	imageURL := r.URL.Query().Get("url")
	directory := r.URL.Query().Get("directory")

	if imageURL == "" || directory == "" {
		http.Error(w, "缺少必要参数:url和directory", http.StatusBadRequest)
		return
	}

	if err := os.MkdirAll(directory, 0755); err != nil {
		log.Printf("创建目录失败: %v", err)
		http.Error(w, "创建目录失败", http.StatusInternalServerError)
		return
	}

	urlParts := strings.Split(imageURL, "/")
	fileName := urlParts[len(urlParts)-1]
	if fileName == "" {
		fileName = "image.jpg"
	}

	filePath := filepath.Join(directory, fileName)

	resp, err := http.Get(imageURL)
	if err != nil {
		log.Printf("下载图片失败: %v", err)
		http.Error(w, "下载图片失败", http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Printf("获取图片失败,状态码: %d", resp.StatusCode)
		http.Error(w, fmt.Sprintf("获取图片失败,状态码: %d", resp.StatusCode), http.StatusInternalServerError)
		return
	}

	out, err := os.Create(filePath)
	if err != nil {
		log.Printf("创建文件失败: %v", err)
		http.Error(w, "创建文件失败", http.StatusInternalServerError)
		return
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		log.Printf("写入文件失败: %v", err)
		http.Error(w, "写入文件失败", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "图片成功下载到: %s", filePath)
}

这个是适合在win下跑的,如果是在wine下跑,根据runtime.GOOS==«linux»稍微再对写入的路径做点改动就可以了。

好久没更新博客了,一是一直被客户需求推着跑,再就是确实没太多可发的。这次的小功能,准备休息时才发现到了凌晨一点,第二天起来才补了wine环境下的处理。也不纯粹是出于兴趣,只是很久没有这样的体验了, 记录一下。

Comments