mirror of
https://github.com/syuilo/ai.git
synced 2024-11-22 13:17:59 +00:00
数取りゲーム
This commit is contained in:
parent
e88bffa93c
commit
69f271cae5
|
@ -99,16 +99,21 @@ export default class 藍 {
|
||||||
const mainStream = this.connection.useSharedConnection('main');
|
const mainStream = this.connection.useSharedConnection('main');
|
||||||
|
|
||||||
// メンションされたとき
|
// メンションされたとき
|
||||||
mainStream.on('mention', data => {
|
mainStream.on('mention', async data => {
|
||||||
if (data.userId == this.account.id) return; // 自分は弾く
|
if (data.userId == this.account.id) return; // 自分は弾く
|
||||||
if (data.text && data.text.startsWith('@' + this.account.username)) {
|
if (data.text && data.text.startsWith('@' + this.account.username)) {
|
||||||
|
// Misskeyのバグで投稿が非公開扱いになる
|
||||||
|
if (data.text == null) data = await this.api('notes/show', { noteId: data.id });
|
||||||
this.onReceiveMessage(new Message(this, data, false));
|
this.onReceiveMessage(new Message(this, data, false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 返信されたとき
|
// 返信されたとき
|
||||||
mainStream.on('reply', data => {
|
mainStream.on('reply', async data => {
|
||||||
if (data.userId == this.account.id) return; // 自分は弾く
|
if (data.userId == this.account.id) return; // 自分は弾く
|
||||||
|
if (data.text && data.text.startsWith('@' + this.account.username)) return;
|
||||||
|
// Misskeyのバグで投稿が非公開扱いになる
|
||||||
|
if (data.text == null) data = await this.api('notes/show', { noteId: data.id });
|
||||||
this.onReceiveMessage(new Message(this, data, false));
|
this.onReceiveMessage(new Message(this, data, false));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import PingModule from './modules/ping';
|
||||||
import EmojiModule from './modules/emoji';
|
import EmojiModule from './modules/emoji';
|
||||||
import FortuneModule from './modules/fortune';
|
import FortuneModule from './modules/fortune';
|
||||||
import GuessingGameModule from './modules/guessing-game';
|
import GuessingGameModule from './modules/guessing-game';
|
||||||
|
import KazutoriModule from './modules/kazutori';
|
||||||
import KeywordModule from './modules/keyword';
|
import KeywordModule from './modules/keyword';
|
||||||
import WelcomeModule from './modules/welcome';
|
import WelcomeModule from './modules/welcome';
|
||||||
import TimerModule from './modules/timer';
|
import TimerModule from './modules/timer';
|
||||||
|
@ -46,6 +47,7 @@ promiseRetry(retry => {
|
||||||
new EmojiModule(),
|
new EmojiModule(),
|
||||||
new FortuneModule(),
|
new FortuneModule(),
|
||||||
new GuessingGameModule(),
|
new GuessingGameModule(),
|
||||||
|
new KazutoriModule(),
|
||||||
new ReversiModule(),
|
new ReversiModule(),
|
||||||
new TimerModule(),
|
new TimerModule(),
|
||||||
new DiceModule(),
|
new DiceModule(),
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public async reply(text: string, cw?: string) {
|
public async reply(text: string, cw?: string, renote?: string) {
|
||||||
if (text == null) return;
|
if (text == null) return;
|
||||||
|
|
||||||
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
|
this.ai.log(`>>> Sending reply to ${chalk.underline(this.id)}`);
|
||||||
|
@ -65,7 +65,8 @@ export default class Message {
|
||||||
return await this.ai.post({
|
return await this.ai.post({
|
||||||
replyId: this.messageOrNote.id,
|
replyId: this.messageOrNote.id,
|
||||||
text: text,
|
text: text,
|
||||||
cw: cw
|
cw: cw,
|
||||||
|
renoteId: renote
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ export type User = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
host: string;
|
||||||
isFollowing: boolean;
|
isFollowing: boolean;
|
||||||
isBot: boolean;
|
isBot: boolean;
|
||||||
};
|
};
|
||||||
|
|
176
src/modules/kazutori/index.ts
Normal file
176
src/modules/kazutori/index.ts
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import * as loki from 'lokijs';
|
||||||
|
import Module from '../../module';
|
||||||
|
import Message from '../../message';
|
||||||
|
import serifs from '../../serifs';
|
||||||
|
import getCollection from '../../utils/get-collection';
|
||||||
|
import { User } from '../../misskey/user';
|
||||||
|
|
||||||
|
type Game = {
|
||||||
|
votes: {
|
||||||
|
user: User;
|
||||||
|
number: number;
|
||||||
|
}[];
|
||||||
|
isEnded: boolean;
|
||||||
|
startedAt: number;
|
||||||
|
postId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class extends Module {
|
||||||
|
public readonly name = 'kazutori';
|
||||||
|
|
||||||
|
private games: loki.Collection<Game>;
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public install() {
|
||||||
|
this.games = getCollection(this.ai.db, 'kazutori');
|
||||||
|
|
||||||
|
this.crawleGameEnd();
|
||||||
|
setInterval(this.crawleGameEnd, 1000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mentionHook: this.mentionHook,
|
||||||
|
contextHook: this.contextHook
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async mentionHook(msg: Message) {
|
||||||
|
if (!msg.includes(['数取り'])) return false;
|
||||||
|
|
||||||
|
const games = this.games.find({});
|
||||||
|
|
||||||
|
const recentGame = games.length == 0 ? null : games[games.length - 1];
|
||||||
|
|
||||||
|
if (recentGame) {
|
||||||
|
// 現在アクティブなゲームがある場合
|
||||||
|
if (!recentGame.isEnded) {
|
||||||
|
msg.reply(serifs.kazutori.alreadyStarted, null, recentGame.postId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直近のゲームから1時間経ってない場合
|
||||||
|
if (Date.now() - recentGame.startedAt < 1000 * 60 * 60) {
|
||||||
|
msg.reply(serifs.kazutori.matakondo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await this.ai.post({
|
||||||
|
text: serifs.kazutori.intro
|
||||||
|
});
|
||||||
|
|
||||||
|
this.games.insertOne({
|
||||||
|
votes: [],
|
||||||
|
isEnded: false,
|
||||||
|
startedAt: Date.now(),
|
||||||
|
postId: post.id
|
||||||
|
});
|
||||||
|
|
||||||
|
this.subscribeReply(null, false, post.id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private async contextHook(msg: Message) {
|
||||||
|
if (msg.text == null) return;
|
||||||
|
|
||||||
|
const game = this.games.findOne({
|
||||||
|
isEnded: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 既に数字を取っていたら
|
||||||
|
if (game.votes.some(x => x.user.id == msg.userId)) return;
|
||||||
|
|
||||||
|
const match = msg.text.match(/[0-9]+/);
|
||||||
|
if (match == null) return;
|
||||||
|
|
||||||
|
const num = parseInt(match[0], 10);
|
||||||
|
|
||||||
|
// 整数じゃない
|
||||||
|
if (!Number.isInteger(num)) return;
|
||||||
|
|
||||||
|
// 範囲外
|
||||||
|
if (num < 0 || num > 100) return;
|
||||||
|
|
||||||
|
this.log(`Voted ${num} by ${msg.user.id}`);
|
||||||
|
|
||||||
|
game.votes.push({
|
||||||
|
user: msg.user,
|
||||||
|
number: num
|
||||||
|
});
|
||||||
|
|
||||||
|
this.games.update(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 終了すべきゲームがないかチェック
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private crawleGameEnd() {
|
||||||
|
const game = this.games.findOne({
|
||||||
|
isEnded: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (game == null) return;
|
||||||
|
|
||||||
|
// ゲーム開始から3分以上経過していたら
|
||||||
|
if (Date.now() - game.startedAt >= 1000 * 60 * 3) {
|
||||||
|
this.finish(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ゲームを終わらせる
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private finish(game: Game) {
|
||||||
|
game.isEnded = true;
|
||||||
|
this.games.update(game);
|
||||||
|
|
||||||
|
// お流れ
|
||||||
|
if (game.votes.length <= 1) {
|
||||||
|
this.ai.post({
|
||||||
|
text: serifs.kazutori.onagare,
|
||||||
|
renoteId: game.postId
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function acct(user: User): string {
|
||||||
|
return user.host ? `@${user.username}@${user.host}` : `@${user.username}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let results: string[] = [];
|
||||||
|
|
||||||
|
let winner: User = null;
|
||||||
|
|
||||||
|
for (let i = 100; i >= 0; i--) {
|
||||||
|
const users = game.votes.filter(x => x.number == i).map(x => x.user);
|
||||||
|
if (users.length == 1) {
|
||||||
|
if (winner == null) {
|
||||||
|
winner = users[0];
|
||||||
|
results.push(`${i == 100 ? '💯' : '🎉'} ${i}: ${acct(users[0])}`);
|
||||||
|
} else {
|
||||||
|
results.push(`➖ ${i}: ${acct(users[0])}`);
|
||||||
|
}
|
||||||
|
} else if (users.length > 1) {
|
||||||
|
results.push(`❌ ${i}: ${users.map(u => acct(u)).join(' ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = results.join('\n') + '\n\n' + (winner
|
||||||
|
? serifs.kazutori.finishWithWinner(acct(winner))
|
||||||
|
: serifs.kazutori.finishWithNoWinner);
|
||||||
|
|
||||||
|
this.ai.post({
|
||||||
|
text: text,
|
||||||
|
cw: serifs.kazutori.finish,
|
||||||
|
renote: game.postId
|
||||||
|
});
|
||||||
|
|
||||||
|
this.unsubscribeReply(null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -235,6 +235,25 @@ export default {
|
||||||
congrats: tries => `正解です🎉 (${tries}回目で当てました)`,
|
congrats: tries => `正解です🎉 (${tries}回目で当てました)`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数取りゲーム
|
||||||
|
*/
|
||||||
|
kazutori: {
|
||||||
|
alreadyStarted: '今ちょうどやってますよ~',
|
||||||
|
|
||||||
|
matakondo: 'また今度やりましょう!',
|
||||||
|
|
||||||
|
intro: 'みなさん、数取りゲームしましょう!\n0~100の中で最も大きい数字を取った人が勝ちです。他の人と被ったらだめですよ~\n制限時間は3分です。数字はこの投稿にリプライで送ってくださいね!',
|
||||||
|
|
||||||
|
finish: 'ゲームの結果発表です!',
|
||||||
|
|
||||||
|
finishWithWinner: user => `今回は${user}さんの勝ちです!またやりましょう♪`,
|
||||||
|
|
||||||
|
finishWithNoWinner: '今回は勝者はいませんでした... またやりましょう♪',
|
||||||
|
|
||||||
|
onagare: '参加者が集まらなかったのでお流れになりました...'
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 絵文字生成
|
* 絵文字生成
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue