Compare commits

...

9 Commits

Author SHA1 Message Date
ed5ee33126 Update version to v1.0.0
Some checks failed
Gradle CI / Gradle-Build (push) Has been cancelled
2025-03-04 00:45:37 +08:00
67f9d1ca75 Add rate limit 2025-03-04 00:45:22 +08:00
05c115e3e8 Fix rank bug 2022-07-26 20:33:17 +08:00
b4dd18cc19 Update version to v0.1.3
增加答题统计
增加排行榜
2022-07-25 21:47:20 +08:00
cf51506432 Update version to v0.1.2
修改为以群为单位答题,答对后出下一题,增加答题计时
支持更多中文符号
2022-07-24 20:26:37 +08:00
2bed56e1b5 Update prefix 2022-07-08 20:57:00 +08:00
c3354b2207 Update version to v0.1.1
Add xor operator '^'
Add group switch
2022-07-08 12:27:28 +08:00
338e3badd3 Merge remote-tracking branch 'origin/master' 2022-06-17 11:12:25 +08:00
6b2023bede added >>, <<, &, | operators
ban mod(%)
2022-06-17 11:11:53 +08:00
6 changed files with 304 additions and 39 deletions

View File

@ -1,13 +1,13 @@
plugins {
val kotlinVersion = "1.6.10"
val kotlinVersion = "1.8.10"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
id("net.mamoe.mirai-console") version "2.11.1"
id("net.mamoe.mirai-console") version "2.16.0"
}
group = "top.jie65535"
version = "0.1.0"
version = "1.0.0"
repositories {
maven("https://maven.aliyun.com/repository/public") // 阿里云国内代理仓库

View File

@ -0,0 +1,30 @@
package top.jie65535.j24
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.getGroupOrNull
import net.mamoe.mirai.contact.Group
object PluginCommand : CompositeCommand(
PluginMain, "j24"
) {
@SubCommand
suspend fun CommandSender.enable(group: Group? = getGroupOrNull()) {
if (group == null) {
sendMessage("必须指定群")
return
}
PluginConfig.enabledGroups.add(group.id)
sendMessage("OK")
}
@SubCommand
suspend fun CommandSender.disable(group: Group? = getGroupOrNull()) {
if (group == null) {
sendMessage("必须指定群")
return
}
PluginConfig.enabledGroups.remove(group.id)
sendMessage("OK")
}
}

View File

@ -0,0 +1,8 @@
package top.jie65535.j24
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.value
object PluginConfig : AutoSavePluginConfig("config") {
val enabledGroups: MutableSet<Long> by value()
}

View File

@ -0,0 +1,38 @@
package top.jie65535.j24
import net.mamoe.mirai.console.data.AutoSavePluginData
import net.mamoe.mirai.console.data.value
object PluginData : AutoSavePluginData("data") {
/**
* 统计数据
* -
* - 群员
* - 数据
*/
val stats: MutableMap<Long, MutableMap<Long, PlayerStat>> by value()
}
@kotlinx.serialization.Serializable
data class PlayerStat(
/**
* 总答题数
*/
var totalCount: Int = 0,
/**
* 统计答题数
*/
var count: Int = 0,
/**
* 平均答题时间(S)
*/
var avgTime: Double = 0.0,
/**
* 最快答题时间(S)
*/
var minTime: Double = Double.MAX_VALUE,
)

View File

@ -1,16 +1,22 @@
package top.jie65535.j24
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.event.GlobalEventChannel
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.subscribeGroupMessages
import net.mamoe.mirai.utils.info
import java.text.DecimalFormat
import java.time.*
import kotlin.math.max
object PluginMain : KotlinPlugin(
JvmPluginDescription(
id = "top.jie65535.j24",
name = "J 24点游戏",
version = "0.1.0"
version = "1.0.0"
) {
author("jie65535")
info("24点游戏")
@ -18,34 +24,147 @@ object PluginMain : KotlinPlugin(
) {
private val games: MutableMap<Long, Point24> = mutableMapOf()
private val df = DecimalFormat("0.00")
private const val prefix = "="
private const val maxRound = 10
class GroupState(
var lastGameTimes: Int = 1,
/**
* 游戏计时器
* 为了防止频繁刷屏记录开始时间一定间隔后才可再次触发
*/
var lastStartedAt: LocalDateTime = LocalDateTime.MIN,
/**
* 排行榜计时器
* 为了防止频繁刷屏记录出榜时间一定间隔后才可再次触发
*/
var lastRankedAt: LocalDateTime = LocalDateTime.MIN
)
private val gameStats = mutableMapOf<Long, GroupState>()
override fun onEnable() {
logger.info { "Plugin loaded" }
//配置文件目录 "${dataFolder.absolutePath}/"
val eventChannel = GlobalEventChannel.parentScope(this)
eventChannel.subscribeMessages {
contains("24点") quoteReply {
val game = Point24()
games[this.sender.id] = game
"你抽到了 [${game.points[0]}] [${game.points[1]}] [${game.points[2]}] [${game.points[3]}]\n" +
"请用以上四组数字组合成结果为24的算式以“答”开头验证"
PluginCommand.register()
PluginConfig.reload()
PluginData.reload()
GlobalEventChannel.parentScope(this)
.filter { it is GroupMessageEvent && PluginConfig.enabledGroups.contains(it.group.id) }
.subscribeGroupMessages {
"24点" reply {
var game = games[group.id]
val state = gameStats.getOrPut(group.id) { GroupState() }
if (state.lastStartedAt.plusMinutes(10).isBefore(LocalDateTime.now())) {
state.lastStartedAt = LocalDateTime.now()
state.lastGameTimes = 0
}
startsWith("") quoteReply {
val game = games[sender.id]
if (game == null) {
"你还没有抽数字哦说“24点”来开始游戏吧"
} else {
if (state.lastGameTimes >= maxRound) {
"您参与的太过频繁了,为避免影响他人聊天体验,请稍后再试~"
} else if (game == null || game.time.plusMinutes(1) < LocalDateTime.now()) {
game = Point24()
games[group.id] = game
state.lastGameTimes++
"{${state.lastGameTimes}/$maxRound} 请用 $game 组成结果为24的算式以'$prefix'开头验证"
} else Unit
}
"24点榜" reply aaa@{
val g = PluginData.stats[group.id] ?: return@aaa Unit
val state = gameStats.getOrPut(group.id) { GroupState() }
// 一小时内仅可查询一次
if (state.lastRankedAt.plusHours(1).isBefore(LocalDateTime.now())) {
// 记录查询时间
state.lastRankedAt = LocalDateTime.now()
// 拼接排行榜
val sb = StringBuilder()
sb.appendLine("[均时榜]")
sb.append(g.entries.filter { it.value.avgTime != 0.0 }.sortedBy { it.value.avgTime }.take(3).joinToString("\n") {
"${df.format(it.value.avgTime)}s | ${group[it.key]?.nameCardOrNick ?: "侠名"}"
}).appendLine().appendLine()
sb.appendLine("[速度榜]")
sb.append(g.entries.sortedBy { it.value.minTime }.take(3).joinToString("\n") {
"${df.format(it.value.minTime)}s | ${group[it.key]?.nameCardOrNick ?: "侠名"}"
}).appendLine().appendLine()
sb.appendLine("[答题榜]")
sb.append(g.entries.sortedByDescending { it.value.totalCount }.take(3).joinToString("\n") {
"${it.value.totalCount} 道 | ${group[it.key]?.nameCardOrNick ?: "侠名"}"
})
sb.toString()
} else Unit
}
startsWith(prefix) quoteReply {
val game = games[group.id]
if (game != null) {
try {
val result = game.evaluate(message.contentToString().removePrefix("").trim())
val now = LocalDateTime.now()
val result = game.evaluate(message.contentToString().removePrefix(prefix).trim())
if (result == 24.0) {
games.remove(sender.id)
"恭喜你,答对了!"
val duration = Duration.between(game.time, now)
// Instant.ofEpochSecond(
// this.time.toLong())
// .atZone(ZoneId.systemDefault())
// .toLocalDateTime())
// 群
var g = PluginData.stats[group.id]
if (g == null) {
g = mutableMapOf()
PluginData.stats[group.id] = g
}
// 玩家
var stat = g[sender.id]
if (stat == null) {
stat = PlayerStat()
g[sender.id] = stat
}
// 答题数增加
stat.totalCount += 1
// 用时
val t = duration.seconds + duration.nano / 1000000000.0
if (stat.minTime > t) {
stat.minTime = t
}
// 仅统计一定时间内的均值
val resultText = if (t < 60) {
// 计数增加
stat.count += 1
// 更新均值
stat.avgTime += (t - stat.avgTime) / max(20, stat.count)
"答对了!用时:${df.format(t)}s 平均:${df.format(stat.avgTime)}s 最快:${df.format(stat.minTime)}s\n"
} else {
"回答正确!"
}
val state = gameStats[group.id]!!
val nextTip = if (state.lastGameTimes >= maxRound) {
games.remove(group.id)
"本轮游戏已经结束,感谢参与,请稍作休息~"
} else {
state.lastGameTimes++
val newGame = Point24()
games[group.id] = newGame
"{${state.lastGameTimes}/$maxRound}下一题:$newGame"
}
resultText + nextTip
} else {
"答错了,计算结果为 $result"
}
} catch (e: Throwable) {
// logger.error(e)
"错误:${e.message}"
}
}
} else Unit
}
}
}

View File

@ -1,17 +1,62 @@
package top.jie65535.j24
import net.objecthunter.exp4j.ExpressionBuilder
import net.objecthunter.exp4j.operator.Operator
import net.objecthunter.exp4j.shuntingyard.ShuntingYard
import net.objecthunter.exp4j.tokenizer.NumberToken
import net.objecthunter.exp4j.tokenizer.Token
import java.time.LocalDateTime
import kotlin.random.Random
class Point24 {
var points = genPoints()
fun regenPoints() {
points = genPoints()
companion object {
val myOperators = listOf(
object : Operator(">>", 2, true, Operator.PRECEDENCE_ADDITION - 1) {
override fun apply(vararg args: Double): Double {
return (args[0].toInt() shr args[1].toInt()).toDouble()
}
},
object : Operator("<<", 2, true, Operator.PRECEDENCE_ADDITION - 1) {
override fun apply(vararg args: Double): Double {
return (args[0].toInt() shl args[1].toInt()).toDouble()
}
},
object : Operator("&", 2, true, Operator.PRECEDENCE_ADDITION - 2) {
override fun apply(vararg args: Double): Double {
return (args[0].toInt() and args[1].toInt()).toDouble()
}
},
object : Operator("^", 2, true, Operator.PRECEDENCE_ADDITION - 3) {
override fun apply(vararg args: Double): Double {
return (args[0].toInt() xor args[1].toInt()).toDouble()
}
},
object : Operator("|", 2, true, Operator.PRECEDENCE_ADDITION - 4) {
override fun apply(vararg args: Double): Double {
return (args[0].toInt() or args[1].toInt()).toDouble()
}
},
// 阶乘禁用因为这会让游戏从24点变成4点
// object : Operator("!", 1, true, Operator.PRECEDENCE_POWER) {
// override fun apply(vararg args: Double): Double {
// var sum = 1
// for (i in 2..args[0].toInt())
// sum *= i
// return sum.toDouble()
// }
// },
)
private val myOperatorMap: Map<String, Operator>
init {
val m = mutableMapOf<String, Operator>()
for (opt in myOperators)
m[opt.symbol] = opt
myOperatorMap = m
}
}
var points = genPoints()
var time: LocalDateTime = LocalDateTime.now()
private fun genPoints() = arrayOf(
Random.nextInt(1, 14),
@ -20,17 +65,35 @@ class Point24 {
Random.nextInt(1, 14)
)
override fun toString() = "[${points[0]}] [${points[1]}] [${points[2]}] [${points[3]}]"
fun evaluate(expression: String): Double {
val expr = expression.replace('', '(').replace('', ')')
val expr = expression
.replace('', '(')
.replace('', ')')
.replace('X', '*')
.replace('x', '*')
.replace('×', '*')
.replace('÷', '/')
.replace('', '-')
.replace('', '+')
.replace('', '!')
.replace('', '<')
.replace('', '>')
if (expr.contains('%'))
throw IllegalArgumentException("禁止使用%运算符")
val tokens = ShuntingYard.convertToRPN(
expr,
null,
mapOf(),
myOperatorMap,
null,
false
)
var usedAll = true
val nums = points.toMutableList()
for (token in tokens) {
if (token.type == Token.TOKEN_NUMBER.toInt()) {
@ -43,17 +106,24 @@ class Point24 {
if (i < nums.size)
nums.removeAt(i)
else
throw IllegalArgumentException("不能使用未得到的数值")
usedAll = false
// throw IllegalArgumentException("不能使用未得到的数值")
} else if (token.type == Token.TOKEN_FUNCTION.toInt()) {
throw IllegalArgumentException("禁止使用函数哦")
}
}
if (nums.isNotEmpty())
throw IllegalArgumentException("必须使用所有数值")
usedAll = false
// throw IllegalArgumentException("必须使用所有数值")
return ExpressionBuilder(expr)
val result = ExpressionBuilder(expr)
.operator(myOperators)
.implicitMultiplication(false)
.build()
.evaluate()
if (usedAll)
return result
else
throw IllegalArgumentException("结果为$result,请使用系统生成的数值!")
}
}