数取りゲーム

This commit is contained in:
syuilo 2019-01-23 22:18:02 +09:00
parent e88bffa93c
commit 69f271cae5
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
6 changed files with 208 additions and 4 deletions

View file

@ -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));
}); });

View file

@ -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(),

View file

@ -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
}); });
} }
} }

View file

@ -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;
}; };

View 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);
}
}

View file

@ -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: '参加者が集まらなかったのでお流れになりました...'
},
/** /**
* *
*/ */