mirror of
https://github.com/TriM-Organization/LiteyukiBot-TriM.git
synced 2026-07-31 07:24:40 +00:00
Ⓜ️手动从旧梦 81a191f merge
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
|
||||
## 版权声明
|
||||
|
||||
本插件由 汉钰律许可协议 授权开源,兼容并继承自 MIT 许可协议。
|
||||
|
||||
Copyright (c) 2022 MeetWq
|
||||
|
||||
版权所有 © 2024 EillesWan & MeetWq
|
||||
|
||||
猜成语-睿乐特别版(trimo_plugin_handle)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
||||
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
@@ -0,0 +1,274 @@
|
||||
import asyncio
|
||||
from asyncio import TimerHandle
|
||||
from typing import Any, Dict
|
||||
|
||||
import nonebot
|
||||
from nonebot import on_regex, require, on_command
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexDict
|
||||
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.utils import run_sync
|
||||
from nonebot.permission import SUPERUSER
|
||||
from typing_extensions import Annotated
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_session")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
Alconna,
|
||||
AlconnaQuery,
|
||||
Image,
|
||||
Option,
|
||||
Query,
|
||||
Text,
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
store_true,
|
||||
Args,
|
||||
Arparma,
|
||||
)
|
||||
from nonebot_plugin_session import SessionId, SessionIdType
|
||||
|
||||
from .config import Config, handle_config
|
||||
from .data_source import GuessResult, Handle
|
||||
from .utils import random_idiom
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="猜成语",
|
||||
description="猜成语-睿乐特别版",
|
||||
usage=(
|
||||
"@我 + “猜成语”开始游戏;\n"
|
||||
"你有十次的机会猜一个四字词语;\n"
|
||||
"每次猜测后,汉字与拼音的颜色将会标识其与正确答案的区别;\n"
|
||||
"青色 表示其出现在答案中且在正确的位置;\n"
|
||||
"橙色 表示其出现在答案中但不在正确的位置;\n"
|
||||
"每个格子的 汉字、声母、韵母、声调 都会独立进行颜色的指示。\n"
|
||||
"当四个格子都为青色时,你便赢得了游戏!\n"
|
||||
"可发送“结束”结束游戏;可发送“提示”查看提示。\n"
|
||||
"使用 --strict 选项开启非默认的成语检查,即猜测的短语必须是成语,\n"
|
||||
"如:@我 猜成语 --strict"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/noneplugin/nonebot-plugin-handle",
|
||||
config=Config,
|
||||
supported_adapters=inherit_supported_adapters(
|
||||
"nonebot_plugin_alconna", "nonebot_plugin_session"
|
||||
),
|
||||
extra={
|
||||
"example": "@小Q 猜成语",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
games: Dict[str, Handle] = {}
|
||||
timers: Dict[str, TimerHandle] = {}
|
||||
|
||||
UserId = Annotated[str, SessionId(SessionIdType.GROUP)]
|
||||
|
||||
|
||||
def game_is_running(user_id: UserId) -> bool:
|
||||
return user_id in games
|
||||
|
||||
|
||||
def game_not_running(user_id: UserId) -> bool:
|
||||
return user_id not in games
|
||||
|
||||
|
||||
handle = on_alconna(
|
||||
Alconna(
|
||||
"handle",
|
||||
Option("-s|--strict", default=False, action=store_true),
|
||||
Option("-d|--hard", default=False, action=store_true),
|
||||
),
|
||||
aliases=("猜成语",),
|
||||
rule=to_me() & game_not_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
handle_hint = on_alconna(
|
||||
"提示",
|
||||
rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
handle_stop = on_alconna(
|
||||
"结束",
|
||||
aliases=("结束游戏", "结束猜成语"),
|
||||
rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
|
||||
handle_answer = on_alconna(
|
||||
Alconna(
|
||||
"答案",
|
||||
Option(
|
||||
"-g|--group",
|
||||
default="Now",
|
||||
args=Args["group", str, "Now"],
|
||||
),
|
||||
Option(
|
||||
"-l|--list",
|
||||
default=False,
|
||||
action=store_true,
|
||||
),
|
||||
),
|
||||
# rule=game_is_running,
|
||||
use_cmd_start=True,
|
||||
permission=SUPERUSER,
|
||||
block=True,
|
||||
priority=13,
|
||||
)
|
||||
|
||||
# handle_update = on_alconna(
|
||||
# "更新词库",
|
||||
# aliases=("刷新词库", "猜成语刷新词库"),
|
||||
# rule=to_me(),
|
||||
# use_cmd_start=True,
|
||||
# block=True,
|
||||
# priority=13,
|
||||
# )
|
||||
|
||||
|
||||
handle_idiom = on_regex(
|
||||
r"^(?P<idiom>[\u4e00-\u9fa5]{4})$",
|
||||
rule=game_is_running,
|
||||
block=True,
|
||||
priority=14,
|
||||
)
|
||||
|
||||
|
||||
def stop_game(user_id: str):
|
||||
if timer := timers.pop(user_id, None):
|
||||
timer.cancel()
|
||||
games.pop(user_id, None)
|
||||
|
||||
|
||||
async def stop_game_timeout(matcher: Matcher, user_id: str):
|
||||
game = games.get(user_id, None)
|
||||
stop_game(user_id)
|
||||
if game:
|
||||
msg = "猜成语超时,游戏结束。"
|
||||
if len(game.guessed_idiom) >= 1:
|
||||
msg += f"\n{game.result}"
|
||||
await matcher.finish(msg)
|
||||
|
||||
|
||||
def set_timeout(matcher: Matcher, user_id: str, timeout: float = 300):
|
||||
if timer := timers.get(user_id, None):
|
||||
timer.cancel()
|
||||
loop = asyncio.get_running_loop()
|
||||
timer = loop.call_later(
|
||||
timeout, lambda: asyncio.ensure_future(stop_game_timeout(matcher, user_id))
|
||||
)
|
||||
timers[user_id] = timer
|
||||
|
||||
|
||||
@handle.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
matcher: Matcher,
|
||||
user_id: UserId,
|
||||
):
|
||||
nonebot.logger.info(result.options)
|
||||
is_strict = handle_config.handle_strict_mode or result.options["strict"].value
|
||||
idiom, explanation = random_idiom(result.options["hard"].value)
|
||||
game = Handle(idiom, explanation, strict=is_strict)
|
||||
|
||||
games[user_id] = game
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
msg = Text(
|
||||
f"你有{game.times}次机会猜一个四字成语,"
|
||||
+ ("发送有效成语以参与游戏。" if is_strict else "发送任意四字词语以参与游戏。")
|
||||
) + Image(raw=await run_sync(game.draw)())
|
||||
await msg.send()
|
||||
|
||||
|
||||
@handle_hint.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId):
|
||||
game = games[user_id]
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
await UniMessage.image(raw=await run_sync(game.draw_hint)()).send()
|
||||
|
||||
|
||||
@handle_stop.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId):
|
||||
game = games[user_id]
|
||||
stop_game(user_id)
|
||||
|
||||
msg = "游戏已结束"
|
||||
if len(game.guessed_idiom) >= 1:
|
||||
msg += f"\n{game.result}"
|
||||
await matcher.finish(msg)
|
||||
|
||||
|
||||
# @handle_update.handle()
|
||||
|
||||
|
||||
@handle_idiom.handle()
|
||||
async def _(matcher: Matcher, user_id: UserId, matched: Dict[str, Any] = RegexDict()):
|
||||
game = games[user_id]
|
||||
set_timeout(matcher, user_id)
|
||||
|
||||
idiom = str(matched["idiom"])
|
||||
result = game.guess(idiom)
|
||||
|
||||
if result in [GuessResult.WIN, GuessResult.LOSS]:
|
||||
stop_game(user_id)
|
||||
msg = Text(
|
||||
(
|
||||
"恭喜你猜出了成语!"
|
||||
if result == GuessResult.WIN
|
||||
else "很遗憾,没有人猜出来呢"
|
||||
)
|
||||
+ f"\n{game.result}"
|
||||
) + Image(raw=await run_sync(game.draw)())
|
||||
await msg.send()
|
||||
|
||||
elif result == GuessResult.DUPLICATE:
|
||||
await matcher.finish("你已经猜过这个成语了呢")
|
||||
|
||||
elif result == GuessResult.ILLEGAL:
|
||||
await matcher.finish(f"你确定“{idiom}”是个成语吗?")
|
||||
|
||||
else:
|
||||
await UniMessage.image(raw=await run_sync(game.draw)()).send()
|
||||
|
||||
|
||||
@handle_answer.handle()
|
||||
async def _(
|
||||
result: Arparma,
|
||||
matcher: Matcher,
|
||||
user_id: UserId,
|
||||
):
|
||||
|
||||
if result.options["list"].value:
|
||||
await handle_answer.finish(
|
||||
UniMessage.text(
|
||||
"\n".join("{}-{}".format(i, j.idiom) for i, j in games.items())
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if result.options["group"].args["group"] == "Now":
|
||||
session_numstr = user_id
|
||||
else:
|
||||
session_numstr = "qq_OneBot V11_2378756507_{}".format(
|
||||
result.options["group"].args["group"]
|
||||
)
|
||||
except:
|
||||
session_numstr = user_id
|
||||
|
||||
if session_numstr in games.keys():
|
||||
await handle_answer.finish(UniMessage.text(games[session_numstr].idiom))
|
||||
else:
|
||||
await handle_answer.finish(
|
||||
UniMessage.text("{} 不存在开局的游戏".format(session_numstr))
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
from nonebot import get_plugin_config
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
handle_strict_mode: bool = False
|
||||
handle_color_enhance: bool = False
|
||||
|
||||
|
||||
handle_config = get_plugin_config(Config)
|
||||
@@ -0,0 +1,284 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from PIL.Image import Image as IMG
|
||||
|
||||
from .config import handle_config
|
||||
from .utils import get_pinyin, legal_idiom, load_font, save_jpg
|
||||
|
||||
|
||||
class GuessResult(Enum):
|
||||
WIN = 0 # 猜出正确成语
|
||||
LOSS = 1 # 达到最大可猜次数,未猜出正确成语
|
||||
DUPLICATE = 2 # 成语重复
|
||||
ILLEGAL = 3 # 成语不合法
|
||||
|
||||
|
||||
class GuessState(Enum):
|
||||
CORRECT = 0 # 存在且位置正确
|
||||
EXIST = 1 # 存在但位置不正确
|
||||
WRONG = 2 # 不存在
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorGroup:
|
||||
bg_color: str # 背景颜色
|
||||
block_color: str # 方块颜色
|
||||
correct_color: str # 存在且位置正确时的颜色
|
||||
exist_color: str # 存在但位置不正确时的颜色
|
||||
wrong_color_pinyin: str # 不存在时的颜色
|
||||
wrong_color_char: str # 不存在时的颜色
|
||||
|
||||
|
||||
NORMAL_COLOR = ColorGroup(
|
||||
"#ffffff", "#f7f8f9", "#1d9c9c", "#de7525", "#b4b8be", "#5d6673"
|
||||
)
|
||||
|
||||
ENHANCED_COLOR = ColorGroup(
|
||||
"#ffffff", "#f7f8f9", "#5ba554", "#ff46ff", "#b4b8be", "#5d6673"
|
||||
)
|
||||
|
||||
|
||||
class Handle:
|
||||
def __init__(self, idiom: str, explanation: str, strict: bool = False):
|
||||
self.idiom: str = idiom # 成语
|
||||
self.explanation: str = explanation # 释义
|
||||
self.strict: bool = strict # 是否判断输入词语为成语
|
||||
self.result = f"【成语】:{idiom}\n【释义】:{explanation}"
|
||||
self.pinyin: List[Tuple[str, str, str]] = get_pinyin(idiom) # 拼音
|
||||
self.length = 4
|
||||
self.times: int = 10 # 可猜次数
|
||||
self.guessed_idiom: List[str] = [] # 记录已猜成语
|
||||
self.guessed_pinyin: List[List[Tuple[str, str, str]]] = [] # 记录已猜成语的拼音
|
||||
|
||||
self.block_size = (160, 160) # 文字块尺寸
|
||||
self.block_padding = (20, 20) # 文字块之间间距
|
||||
self.padding = (40, 40) # 边界间距
|
||||
font_size_char = 60 # 汉字字体大小
|
||||
font_size_pinyin = 30 # 拼音字体大小
|
||||
font_size_tone = 22 # 声调字体大小
|
||||
self.font_char = load_font("NotoSerifSC-Regular.otf", font_size_char)
|
||||
self.font_pinyin = load_font("NotoSansMono-Regular.ttf", font_size_pinyin)
|
||||
self.font_tone = load_font("NotoSansMono-Regular.ttf", font_size_tone)
|
||||
|
||||
self.colors = (
|
||||
ENHANCED_COLOR if handle_config.handle_color_enhance else NORMAL_COLOR
|
||||
)
|
||||
|
||||
def guess(self, idiom: str) -> Optional[GuessResult]:
|
||||
if self.strict and not legal_idiom(idiom):
|
||||
return GuessResult.ILLEGAL
|
||||
if idiom in self.guessed_idiom:
|
||||
return GuessResult.DUPLICATE
|
||||
self.guessed_idiom.append(idiom)
|
||||
self.guessed_pinyin.append(get_pinyin(idiom))
|
||||
if idiom == self.idiom:
|
||||
return GuessResult.WIN
|
||||
if len(self.guessed_idiom) == self.times:
|
||||
return GuessResult.LOSS
|
||||
|
||||
def draw_block(
|
||||
self,
|
||||
block_color: str,
|
||||
char: str = "",
|
||||
char_color: str = "",
|
||||
initial: str = "",
|
||||
initial_color: str = "",
|
||||
final: str = "",
|
||||
final_color: str = "",
|
||||
tone: str = "",
|
||||
tone_color: str = "",
|
||||
underline: bool = False,
|
||||
underline_color: str = "",
|
||||
) -> IMG:
|
||||
block = Image.new("RGB", self.block_size, block_color)
|
||||
if not char:
|
||||
return block
|
||||
draw = ImageDraw.Draw(block)
|
||||
|
||||
char_size = self.font_char.getbbox(char)[2:]
|
||||
x = (self.block_size[0] - char_size[0]) / 2
|
||||
y = (self.block_size[1] - char_size[1]) / 5 * 3
|
||||
draw.text((x, y), char, font=self.font_char, fill=char_color)
|
||||
|
||||
space = 5
|
||||
need_space = bool(initial and final)
|
||||
py_length = self.font_pinyin.getlength(initial + final)
|
||||
if need_space:
|
||||
py_length += space
|
||||
py_start = (self.block_size[0] - py_length) / 2
|
||||
x = py_start
|
||||
y = self.block_size[0] / 8
|
||||
draw.text((x, y), initial, font=self.font_pinyin, fill=initial_color)
|
||||
x += self.font_pinyin.getlength(initial)
|
||||
if need_space:
|
||||
x += space
|
||||
draw.text((x, y), final, font=self.font_pinyin, fill=final_color)
|
||||
|
||||
tone_size = self.font_tone.getbbox(tone)[2:]
|
||||
x = (self.block_size[0] + py_length) / 2 + tone_size[0] / 3
|
||||
y -= tone_size[1] / 3
|
||||
draw.text((x, y), tone, font=self.font_tone, fill=tone_color)
|
||||
|
||||
if underline:
|
||||
x = py_start
|
||||
py_size = self.font_pinyin.getbbox(initial + final)[2:]
|
||||
y = self.block_size[0] / 8 + py_size[1] + 2
|
||||
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||
y += 3
|
||||
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||
|
||||
return block
|
||||
|
||||
def draw(self) -> BytesIO:
|
||||
rows = min(len(self.guessed_idiom) + 1, self.times)
|
||||
board_w = self.length * self.block_size[0]
|
||||
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||
board_h = rows * self.block_size[1]
|
||||
board_h += (rows - 1) * self.block_padding[1] + 2 * self.padding[1]
|
||||
board_size = (board_w, board_h)
|
||||
board = Image.new("RGB", board_size, self.colors.bg_color)
|
||||
|
||||
def get_states(guessed: List[str], answer: List[str]) -> List[GuessState]:
|
||||
states = []
|
||||
incorrect = []
|
||||
for i in range(self.length):
|
||||
if guessed[i] != answer[i]:
|
||||
incorrect.append(answer[i])
|
||||
else:
|
||||
incorrect.append("_")
|
||||
for i in range(self.length):
|
||||
if guessed[i] == answer[i]:
|
||||
states.append(GuessState.CORRECT)
|
||||
elif guessed[i] in incorrect:
|
||||
states.append(GuessState.EXIST)
|
||||
incorrect[incorrect.index(guessed[i])] = "_"
|
||||
else:
|
||||
states.append(GuessState.WRONG)
|
||||
return states
|
||||
|
||||
def get_pinyin_color(state: GuessState) -> str:
|
||||
if state == GuessState.CORRECT:
|
||||
return self.colors.correct_color
|
||||
elif state == GuessState.EXIST:
|
||||
return self.colors.exist_color
|
||||
else:
|
||||
return self.colors.wrong_color_pinyin
|
||||
|
||||
def get_char_color(state: GuessState) -> str:
|
||||
if state == GuessState.CORRECT:
|
||||
return self.colors.correct_color
|
||||
elif state == GuessState.EXIST:
|
||||
return self.colors.exist_color
|
||||
else:
|
||||
return self.colors.wrong_color_char
|
||||
|
||||
def block_pos(row: int, col: int) -> Tuple[int, int]:
|
||||
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * col
|
||||
y = self.padding[1] + (self.block_size[1] + self.block_padding[1]) * row
|
||||
return x, y
|
||||
|
||||
for i in range(len(self.guessed_idiom)):
|
||||
idiom = self.guessed_idiom[i]
|
||||
pinyin = self.guessed_pinyin[i]
|
||||
char_states = get_states(list(idiom), list(self.idiom))
|
||||
initial_states = get_states(
|
||||
[p[0] for p in pinyin], [p[0] for p in self.pinyin]
|
||||
)
|
||||
final_states = get_states(
|
||||
[p[1] for p in pinyin], [p[1] for p in self.pinyin]
|
||||
)
|
||||
tone_states = get_states(
|
||||
[p[2] for p in pinyin], [p[2] for p in self.pinyin]
|
||||
)
|
||||
underline_states = get_states(
|
||||
[p[0] + p[1] for p in pinyin], [p[0] + p[1] for p in self.pinyin]
|
||||
)
|
||||
for j in range(self.length):
|
||||
char = idiom[j]
|
||||
i2, f2, t2 = pinyin[j]
|
||||
if char == self.idiom[j]:
|
||||
block_color = self.colors.correct_color
|
||||
char_color = initial_color = final_color = tone_color = (
|
||||
self.colors.bg_color
|
||||
)
|
||||
underline = False
|
||||
underline_color = ""
|
||||
else:
|
||||
block_color = self.colors.block_color
|
||||
char_color = get_char_color(char_states[j])
|
||||
initial_color = get_pinyin_color(initial_states[j])
|
||||
final_color = get_pinyin_color(final_states[j])
|
||||
tone_color = get_pinyin_color(tone_states[j])
|
||||
underline_color = get_pinyin_color(underline_states[j])
|
||||
underline = underline_color in (
|
||||
self.colors.correct_color,
|
||||
self.colors.exist_color,
|
||||
)
|
||||
block = self.draw_block(
|
||||
block_color,
|
||||
char,
|
||||
char_color,
|
||||
i2,
|
||||
initial_color,
|
||||
f2,
|
||||
final_color,
|
||||
t2,
|
||||
tone_color,
|
||||
underline,
|
||||
underline_color,
|
||||
)
|
||||
board.paste(block, block_pos(i, j))
|
||||
|
||||
for i in range(len(self.guessed_idiom), rows):
|
||||
for j in range(self.length):
|
||||
block = self.draw_block(self.colors.block_color)
|
||||
board.paste(block, block_pos(i, j))
|
||||
|
||||
return save_jpg(board)
|
||||
|
||||
def draw_hint(self) -> BytesIO:
|
||||
guessed_char = set("".join(self.guessed_idiom))
|
||||
guessed_initial = set()
|
||||
guessed_final = set()
|
||||
guessed_tone = set()
|
||||
for pinyin in self.guessed_pinyin:
|
||||
for p in pinyin:
|
||||
guessed_initial.add(p[0])
|
||||
guessed_final.add(p[1])
|
||||
guessed_tone.add(p[2])
|
||||
|
||||
board_w = self.length * self.block_size[0]
|
||||
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||
board_h = self.block_size[1] + 2 * self.padding[1]
|
||||
board = Image.new("RGB", (board_w, board_h), self.colors.bg_color)
|
||||
|
||||
for i in range(self.length):
|
||||
char = self.idiom[i]
|
||||
hi, hf, ht = self.pinyin[i]
|
||||
color = char_c = initial_c = final_c = tone_c = self.colors.correct_color
|
||||
if char not in guessed_char:
|
||||
char = "?"
|
||||
color = self.colors.block_color
|
||||
char_c = self.colors.wrong_color_char
|
||||
else:
|
||||
char_c = initial_c = final_c = tone_c = self.colors.bg_color
|
||||
if hi not in guessed_initial:
|
||||
hi = "?"
|
||||
initial_c = self.colors.wrong_color_pinyin
|
||||
if hf not in guessed_final:
|
||||
hf = "?"
|
||||
final_c = self.colors.wrong_color_pinyin
|
||||
if ht not in guessed_tone:
|
||||
ht = "?"
|
||||
tone_c = self.colors.wrong_color_pinyin
|
||||
block = self.draw_block(
|
||||
color, char, char_c, hi, initial_c, hf, final_c, ht, tone_c
|
||||
)
|
||||
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * i
|
||||
y = self.padding[1]
|
||||
board.paste(block, (x, y))
|
||||
return save_jpg(board)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 MeetWq
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,118 @@
|
||||
import json
|
||||
import random
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
# from watchdog.observers import Observer
|
||||
# from watchdog.events import FileSystemEventHandler, FileModifiedEvent
|
||||
|
||||
from PIL import ImageFont
|
||||
from PIL.Image import Image as IMG
|
||||
from PIL.ImageFont import FreeTypeFont
|
||||
from pypinyin import Style, pinyin
|
||||
|
||||
resource_dir = Path(__file__).parent / "resources"
|
||||
fonts_dir = resource_dir / "fonts"
|
||||
data_dir = resource_dir / "data"
|
||||
idiom_path = data_dir / "idioms.txt"
|
||||
answer_path = data_dir / "answers.json"
|
||||
answer_hard_path = data_dir / "answers_hard.json"
|
||||
|
||||
|
||||
LEGAL_PHRASES = [
|
||||
idiom.strip() for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||
]
|
||||
ANSWER_PHRASES: List[Dict[str, str]] = json.load(
|
||||
answer_path.open("r", encoding="utf-8")
|
||||
)
|
||||
HARD_ANSWER_PHRASES: List[Dict[str, str]] = json.load(
|
||||
answer_hard_path.open("r", encoding="utf-8")
|
||||
)
|
||||
|
||||
# class LegalPhrasesModifiedHandler(FileSystemEventHandler):
|
||||
# """
|
||||
# Handler for resource file changes
|
||||
# """
|
||||
|
||||
# def on_modified(self, event):
|
||||
# print(f"{event.src_path} modified, reloading resource...")
|
||||
# if "idioms.txt" in event.src_path:
|
||||
# global LEGAL_PHRASES
|
||||
# LEGAL_PHRASES = [
|
||||
# idiom.strip()
|
||||
# for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||
# ]
|
||||
# elif "answers.json" in event.src_path:
|
||||
# global ANSWER_PHRASES
|
||||
# ANSWER_PHRASES = json.load(
|
||||
# answer_path.open("r", encoding="utf-8")
|
||||
# )
|
||||
|
||||
|
||||
# Observer().schedule(
|
||||
# LegalPhrasesModifiedHandler(),
|
||||
# data_dir,
|
||||
# recursive=False,
|
||||
# event_filter=FileModifiedEvent,
|
||||
# )
|
||||
|
||||
|
||||
def legal_idiom(word: str) -> bool:
|
||||
return word in LEGAL_PHRASES
|
||||
|
||||
|
||||
def random_idiom(is_hard: bool = False) -> Tuple[str, str]:
|
||||
answer = random.choice(HARD_ANSWER_PHRASES if is_hard else ANSWER_PHRASES)
|
||||
return answer["word"], answer["explanation"]
|
||||
|
||||
|
||||
# fmt: off
|
||||
# 声母
|
||||
INITIALS = [
|
||||
"zh", "z", "y", "x", "w", "t", "sh", "s", "r", "q", "p",
|
||||
"n", "m", "l", "k", "j", "h", "g", "f", "d", "ch", "c", "b"
|
||||
]
|
||||
# 韵母
|
||||
FINALS = [
|
||||
"ün", "üe", "üan", "ü", "uo", "un", "ui", "ue", "uang",
|
||||
"uan", "uai","ua", "ou", "iu", "iong", "ong", "io", "ing",
|
||||
"in", "ie", "iao", "iang", "ian", "ia", "er", "eng", "en",
|
||||
"ei", "ao", "ang", "an", "ai", "u", "o", "i", "e", "a"
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
|
||||
def get_pinyin(idiom: str) -> List[Tuple[str, str, str]]:
|
||||
pys = pinyin(idiom, style=Style.TONE3, v_to_u=True)
|
||||
results = []
|
||||
for p in pys:
|
||||
py = p[0]
|
||||
if py[-1].isdigit():
|
||||
tone = py[-1]
|
||||
py = py[:-1]
|
||||
else:
|
||||
tone = ""
|
||||
initial = ""
|
||||
for i in INITIALS:
|
||||
if py.startswith(i):
|
||||
initial = i
|
||||
break
|
||||
final = ""
|
||||
for f in FINALS:
|
||||
if py.endswith(f):
|
||||
final = f
|
||||
break
|
||||
results.append((initial, final, tone)) # 声母,韵母,声调
|
||||
return results
|
||||
|
||||
|
||||
def save_jpg(frame: IMG) -> BytesIO:
|
||||
output = BytesIO()
|
||||
frame = frame.convert("RGB")
|
||||
frame.save(output, format="jpeg")
|
||||
return output
|
||||
|
||||
|
||||
def load_font(name: str, fontsize: int) -> FreeTypeFont:
|
||||
return ImageFont.truetype(str(fonts_dir / name), fontsize, encoding="utf-8")
|
||||
Reference in New Issue
Block a user