2018-08-04 01:42:03 +00:00
|
|
|
|
/**
|
|
|
|
|
* -AI-
|
|
|
|
|
* Botのバックエンド(思考を担当)
|
|
|
|
|
*
|
|
|
|
|
* 対話と思考を同じプロセスで行うと、思考時間が長引いたときにストリームから
|
|
|
|
|
* 切断されてしまうので、別々のプロセスで行うようにします
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import * as request from 'request-promise-native';
|
|
|
|
|
import Reversi, { Color } from 'misskey-reversi';
|
2018-08-14 18:43:44 +00:00
|
|
|
|
import config from '../../config';
|
2018-08-28 00:12:59 +00:00
|
|
|
|
import serifs from '../../serifs';
|
2018-08-28 21:30:48 +00:00
|
|
|
|
import { User } from '../../misskey/user';
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
const db = {};
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
|
|
|
|
function getUserName(user) {
|
|
|
|
|
return user.name || user.username;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 09:48:44 +00:00
|
|
|
|
const titles = [
|
|
|
|
|
'さん', 'サン', 'サン', '㌠',
|
|
|
|
|
'ちゃん', 'チャン', 'チャン',
|
|
|
|
|
'君', 'くん', 'クン', 'クン',
|
|
|
|
|
'先生', 'せんせい', 'センセイ', 'センセイ'
|
|
|
|
|
];
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
class Session {
|
2018-08-28 21:30:48 +00:00
|
|
|
|
private account: User;
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private game: any;
|
|
|
|
|
private form: any;
|
|
|
|
|
private o: Reversi;
|
|
|
|
|
private botColor: Color;
|
|
|
|
|
|
|
|
|
|
/**
|
2018-08-06 15:10:02 +00:00
|
|
|
|
* 各マスの強さ (-1.0 ~ 1.0)
|
2018-08-05 11:55:27 +00:00
|
|
|
|
*/
|
2018-08-06 15:10:02 +00:00
|
|
|
|
private cellWeights: number[];
|
2018-08-05 11:55:27 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 対局が開始したことを知らせた投稿
|
|
|
|
|
*/
|
|
|
|
|
private startedNote: any = null;
|
|
|
|
|
|
2018-08-28 21:30:48 +00:00
|
|
|
|
private get user(): User {
|
2018-08-14 18:43:44 +00:00
|
|
|
|
return this.game.user1Id == this.account.id ? this.game.user2 : this.game.user1;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private get userName(): string {
|
2018-08-06 14:26:36 +00:00
|
|
|
|
const name = getUserName(this.user);
|
|
|
|
|
return `?[${name}](${config.host}/@${this.user.username})${titles.some(x => name.endsWith(x)) ? '' : 'さん'}`;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private get strength(): number {
|
|
|
|
|
return this.form.find(i => i.id == 'strength').value;
|
|
|
|
|
}
|
2018-08-04 11:37:26 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private get isSettai(): boolean {
|
|
|
|
|
return this.strength === 0;
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private get allowPost(): boolean {
|
|
|
|
|
return this.form.find(i => i.id == 'publish').value;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private get url(): string {
|
2019-01-22 04:42:04 +00:00
|
|
|
|
return `${config.host}/games/reversi/${this.game.id}`;
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
constructor() {
|
|
|
|
|
process.on('message', this.onMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onMessage = async (msg: any) => {
|
|
|
|
|
switch (msg.type) {
|
2018-10-10 18:00:54 +00:00
|
|
|
|
case '_init_': this.onInit(msg.body); break;
|
|
|
|
|
case 'updateForm': this.onUpdateForn(msg.body); break;
|
|
|
|
|
case 'started': this.onStarted(msg.body); break;
|
|
|
|
|
case 'ended': this.onEnded(msg.body); break;
|
|
|
|
|
case 'set': this.onSet(msg.body); break;
|
2018-08-04 11:37:26 +00:00
|
|
|
|
}
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
2018-08-04 11:37:26 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// 親プロセスからデータをもらう
|
|
|
|
|
private onInit = (msg: any) => {
|
|
|
|
|
this.game = msg.game;
|
|
|
|
|
this.form = msg.form;
|
2018-08-14 18:43:44 +00:00
|
|
|
|
this.account = msg.account;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* フォームが更新されたとき
|
|
|
|
|
*/
|
|
|
|
|
private onUpdateForn = (msg: any) => {
|
2018-10-10 18:00:54 +00:00
|
|
|
|
this.form.find(i => i.id == msg.id).value = msg.value;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* 対局が始まったとき
|
|
|
|
|
*/
|
|
|
|
|
private onStarted = (msg: any) => {
|
2018-10-10 18:00:54 +00:00
|
|
|
|
this.game = msg;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// TLに投稿する
|
|
|
|
|
this.postGameStarted().then(note => {
|
|
|
|
|
this.startedNote = note;
|
|
|
|
|
});
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// リバーシエンジン初期化
|
|
|
|
|
this.o = new Reversi(this.game.settings.map, {
|
|
|
|
|
isLlotheo: this.game.settings.isLlotheo,
|
|
|
|
|
canPutEverywhere: this.game.settings.canPutEverywhere,
|
|
|
|
|
loopedBoard: this.game.settings.loopedBoard
|
|
|
|
|
});
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-06 15:10:02 +00:00
|
|
|
|
//#region 各マスの価値を計算しておく
|
|
|
|
|
|
2018-08-06 16:25:15 +00:00
|
|
|
|
// 標準的な 8*8 のマップなら予め定義した価値マップを使用
|
|
|
|
|
if (this.o.mapWidth == 8 && this.o.mapHeight == 8 && !this.o.map.some(p => p == 'null')) {
|
|
|
|
|
this.cellWeights = [
|
|
|
|
|
1 , -0.4, 0 , -0.1, -0.1, 0 , -0.4, 1 ,
|
|
|
|
|
-0.4, -0.5, -0.2, -0.2, -0.2, -0.2, -0.5, -0.4,
|
|
|
|
|
0 , -0.2, 0 , -0.1, -0.1, 0 , -0.2, 0 ,
|
|
|
|
|
-0.1, -0.2, -0.1, -0.1, -0.1, -0.1, -0.2, -0.1,
|
|
|
|
|
-0.1, -0.2, -0.1, -0.1, -0.1, -0.1, -0.2, -0.1,
|
|
|
|
|
0 , -0.2, 0 , -0.1, -0.1, 0 , -0.2, 0 ,
|
|
|
|
|
-0.4, -0.5, -0.2, -0.2, -0.2, -0.2, -0.5, -0.4,
|
|
|
|
|
1 , -0.4, 0 , -0.1, -0.1, 0 , -0.4, 1
|
|
|
|
|
];
|
|
|
|
|
} else {
|
|
|
|
|
//#region 隅
|
|
|
|
|
this.cellWeights = this.o.map.map((pix, i) => {
|
|
|
|
|
if (pix == 'null') return 0;
|
|
|
|
|
const [x, y] = this.o.transformPosToXy(i);
|
|
|
|
|
let count = 0;
|
|
|
|
|
const get = (x, y) => {
|
|
|
|
|
if (x < 0 || y < 0 || x >= this.o.mapWidth || y >= this.o.mapHeight) return 'null';
|
|
|
|
|
return this.o.mapDataGet(this.o.transformXyToPos(x, y));
|
|
|
|
|
};
|
2018-08-06 15:10:02 +00:00
|
|
|
|
|
2018-08-06 16:25:15 +00:00
|
|
|
|
const isNotSumi = (
|
|
|
|
|
// -
|
|
|
|
|
// +
|
|
|
|
|
// -
|
|
|
|
|
(get(x - 1, y - 1) == 'empty' && get(x + 1, y + 1) == 'empty') ||
|
|
|
|
|
|
|
|
|
|
// -
|
|
|
|
|
// +
|
|
|
|
|
// -
|
|
|
|
|
(get(x, y - 1) == 'empty' && get(x, y + 1) == 'empty') ||
|
|
|
|
|
|
|
|
|
|
// -
|
|
|
|
|
// +
|
|
|
|
|
// -
|
|
|
|
|
(get(x + 1, y - 1) == 'empty' && get(x - 1, y + 1) == 'empty') ||
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// -+-
|
|
|
|
|
//
|
|
|
|
|
(get(x - 1, y) == 'empty' && get(x + 1, y) == 'empty')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const isSumi = !isNotSumi;
|
|
|
|
|
|
|
|
|
|
return isSumi ? 1 : 0;
|
|
|
|
|
});
|
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
|
|
//#region 隅の隣は危険
|
|
|
|
|
this.cellWeights.forEach((cell, i) => {
|
|
|
|
|
const [x, y] = this.o.transformPosToXy(i);
|
|
|
|
|
|
|
|
|
|
if (cell === 1) return;
|
|
|
|
|
if (this.o.mapDataGet(this.o.transformXyToPos(x, y)) == 'null') return;
|
|
|
|
|
|
|
|
|
|
const get = (x, y) => {
|
|
|
|
|
if (x < 0 || y < 0 || x >= this.o.mapWidth || y >= this.o.mapHeight) return 0;
|
|
|
|
|
return this.cellWeights[this.o.transformXyToPos(x, y)];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isSumiNear = (
|
|
|
|
|
(get(x - 1, y - 1) === 1) || // 左上
|
|
|
|
|
(get(x , y - 1) === 1) || // 上
|
|
|
|
|
(get(x + 1, y - 1) === 1) || // 右上
|
|
|
|
|
(get(x + 1, y ) === 1) || // 右
|
|
|
|
|
(get(x + 1, y + 1) === 1) || // 右下
|
|
|
|
|
(get(x , y + 1) === 1) || // 下
|
|
|
|
|
(get(x - 1, y + 1) === 1) || // 左下
|
|
|
|
|
(get(x - 1, y ) === 1) // 左
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (isSumiNear) this.cellWeights[i] = -0.5;
|
|
|
|
|
});
|
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
|
|
}
|
2018-08-06 15:10:02 +00:00
|
|
|
|
|
|
|
|
|
//#endregion
|
2018-08-05 11:55:27 +00:00
|
|
|
|
|
2018-08-14 18:43:44 +00:00
|
|
|
|
this.botColor = this.game.user1Id == this.account.id && this.game.black == 1 || this.game.user2Id == this.account.id && this.game.black == 2;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.botColor) {
|
|
|
|
|
this.think();
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* 対局が終わったとき
|
|
|
|
|
*/
|
2018-08-05 12:19:06 +00:00
|
|
|
|
private onEnded = async (msg: any) => {
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// ストリームから切断
|
|
|
|
|
process.send({
|
2018-08-28 00:12:59 +00:00
|
|
|
|
type: 'ended'
|
2018-08-05 11:55:27 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let text: string;
|
|
|
|
|
|
2018-10-10 18:00:54 +00:00
|
|
|
|
if (msg.game.surrendered) {
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.isSettai) {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.settaiButYouSurrendered(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
} else {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.youSurrendered(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
2018-10-10 18:00:54 +00:00
|
|
|
|
} else if (msg.winnerId) {
|
|
|
|
|
if (msg.winnerId == this.account.id) {
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.isSettai) {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.iWonButSettai(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
} else {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.iWon(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (this.isSettai) {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.iLoseButSettai(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
} else {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.iLose(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (this.isSettai) {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.drawnSettai(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
} else {
|
2018-08-29 07:47:07 +00:00
|
|
|
|
text = serifs.reversi.drawn(this.userName);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 12:19:06 +00:00
|
|
|
|
await this.post(text, this.startedNote);
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
process.exit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 打たれたとき
|
|
|
|
|
*/
|
|
|
|
|
private onSet = (msg: any) => {
|
2018-10-10 18:00:54 +00:00
|
|
|
|
this.o.put(msg.color, msg.pos);
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-10-10 18:00:54 +00:00
|
|
|
|
if (msg.next === this.botColor) {
|
2018-08-05 11:55:27 +00:00
|
|
|
|
this.think();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Botにとってある局面がどれだけ有利か取得する
|
|
|
|
|
*/
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private staticEval = () => {
|
|
|
|
|
let score = this.o.canPutSomewhere(this.botColor).length;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
this.cellWeights.forEach((weight, i) => {
|
2018-08-04 01:42:03 +00:00
|
|
|
|
// 係数
|
|
|
|
|
const coefficient = 30;
|
|
|
|
|
weight = weight * coefficient;
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
const stone = this.o.board[i];
|
|
|
|
|
if (stone === this.botColor) {
|
2018-08-04 01:42:03 +00:00
|
|
|
|
// TODO: 価値のあるマスに設置されている自分の石に縦か横に接するマスは価値があると判断する
|
|
|
|
|
score += weight;
|
|
|
|
|
} else if (stone !== null) {
|
|
|
|
|
score -= weight;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ロセオならスコアを反転
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.game.settings.isLlotheo) score = -score;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
|
|
|
|
// 接待ならスコアを反転
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.isSettai) score = -score;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
|
|
|
|
return score;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
private think = () => {
|
|
|
|
|
console.log('Thinking...');
|
|
|
|
|
console.time('think');
|
|
|
|
|
|
|
|
|
|
// 接待モードのときは、全力(5手先読みくらい)で負けるようにする
|
|
|
|
|
const maxDepth = this.isSettai ? 5 : this.strength;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* αβ法での探索
|
|
|
|
|
*/
|
|
|
|
|
const dive = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => {
|
|
|
|
|
// 試し打ち
|
|
|
|
|
this.o.put(this.o.turn, pos);
|
|
|
|
|
|
|
|
|
|
const key = this.o.board.toString();
|
|
|
|
|
let cache = db[key];
|
|
|
|
|
if (cache) {
|
|
|
|
|
if (alpha >= cache.upper) {
|
|
|
|
|
this.o.undo();
|
|
|
|
|
return cache.upper;
|
|
|
|
|
}
|
|
|
|
|
if (beta <= cache.lower) {
|
|
|
|
|
this.o.undo();
|
|
|
|
|
return cache.lower;
|
|
|
|
|
}
|
|
|
|
|
alpha = Math.max(alpha, cache.lower);
|
|
|
|
|
beta = Math.min(beta, cache.upper);
|
|
|
|
|
} else {
|
|
|
|
|
cache = {
|
|
|
|
|
upper: Infinity,
|
|
|
|
|
lower: -Infinity
|
|
|
|
|
};
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
const isBotTurn = this.o.turn === this.botColor;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// 勝った
|
|
|
|
|
if (this.o.turn === null) {
|
|
|
|
|
const winner = this.o.winner;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// 勝つことによる基本スコア
|
|
|
|
|
const base = 10000;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
let score;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (this.game.settings.isLlotheo) {
|
|
|
|
|
// 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する
|
|
|
|
|
score = this.o.winner ? base - (this.o.blackCount * 100) : base - (this.o.whiteCount * 100);
|
|
|
|
|
} else {
|
|
|
|
|
// 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する
|
|
|
|
|
score = this.o.winner ? base + (this.o.blackCount * 100) : base + (this.o.whiteCount * 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 巻き戻し
|
|
|
|
|
this.o.undo();
|
|
|
|
|
|
|
|
|
|
// 接待なら自分が負けた方が高スコア
|
|
|
|
|
return this.isSettai
|
|
|
|
|
? winner !== this.botColor ? score : -score
|
|
|
|
|
: winner === this.botColor ? score : -score;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (depth === maxDepth) {
|
|
|
|
|
// 静的に評価
|
|
|
|
|
const score = this.staticEval();
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// 巻き戻し
|
|
|
|
|
this.o.undo();
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
return score;
|
|
|
|
|
} else {
|
|
|
|
|
const cans = this.o.canPutSomewhere(this.o.turn);
|
|
|
|
|
|
|
|
|
|
let value = isBotTurn ? -Infinity : Infinity;
|
|
|
|
|
let a = alpha;
|
|
|
|
|
let b = beta;
|
|
|
|
|
|
|
|
|
|
// 次のターンのプレイヤーにとって最も良い手を取得
|
|
|
|
|
for (const p of cans) {
|
|
|
|
|
if (isBotTurn) {
|
|
|
|
|
const score = dive(p, a, beta, depth + 1);
|
|
|
|
|
value = Math.max(value, score);
|
|
|
|
|
a = Math.max(a, value);
|
|
|
|
|
if (value >= beta) break;
|
|
|
|
|
} else {
|
|
|
|
|
const score = dive(p, alpha, b, depth + 1);
|
|
|
|
|
value = Math.min(value, score);
|
|
|
|
|
b = Math.min(b, value);
|
|
|
|
|
if (value <= alpha) break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
// 巻き戻し
|
|
|
|
|
this.o.undo();
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
if (value <= alpha) {
|
|
|
|
|
cache.upper = value;
|
|
|
|
|
} else if (value >= beta) {
|
|
|
|
|
cache.lower = value;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
} else {
|
2018-08-05 11:55:27 +00:00
|
|
|
|
cache.upper = value;
|
|
|
|
|
cache.lower = value;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
db[key] = cache;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
return value;
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
2018-08-05 11:55:27 +00:00
|
|
|
|
};
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
const cans = this.o.canPutSomewhere(this.botColor);
|
|
|
|
|
const scores = cans.map(p => dive(p));
|
|
|
|
|
const pos = cans[scores.indexOf(Math.max(...scores))];
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
console.log('Thinked:', pos);
|
|
|
|
|
console.timeEnd('think');
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-06 16:25:15 +00:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
process.send({
|
|
|
|
|
type: 'put',
|
|
|
|
|
pos
|
|
|
|
|
});
|
|
|
|
|
}, 500);
|
2018-08-05 11:55:27 +00:00
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
/**
|
|
|
|
|
* 対局が始まったことをMisskeyに投稿します
|
|
|
|
|
*/
|
|
|
|
|
private postGameStarted = async () => {
|
|
|
|
|
const text = this.isSettai
|
2018-08-29 07:47:07 +00:00
|
|
|
|
? serifs.reversi.startedSettai(this.userName)
|
|
|
|
|
: serifs.reversi.started(this.userName, this.strength.toString());
|
2018-08-04 01:42:03 +00:00
|
|
|
|
|
2018-08-05 11:55:27 +00:00
|
|
|
|
return await this.post(`${text}\n→[観戦する](${this.url})`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Misskeyに投稿します
|
|
|
|
|
* @param text 投稿内容
|
|
|
|
|
*/
|
|
|
|
|
private post = async (text: string, renote?: any) => {
|
|
|
|
|
if (this.allowPost) {
|
2018-08-05 12:19:06 +00:00
|
|
|
|
const body = {
|
|
|
|
|
i: config.i,
|
2018-08-20 16:00:41 +00:00
|
|
|
|
text: text,
|
|
|
|
|
visibility: 'home'
|
2018-08-05 12:19:06 +00:00
|
|
|
|
} as any;
|
|
|
|
|
|
|
|
|
|
if (renote) {
|
|
|
|
|
body.renoteId = renote.id;
|
|
|
|
|
}
|
2018-08-05 11:55:27 +00:00
|
|
|
|
|
2018-08-05 12:19:06 +00:00
|
|
|
|
try {
|
|
|
|
|
const res = await request.post(`${config.host}/api/notes/create`, {
|
|
|
|
|
json: body
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.createdNote;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2018-08-05 11:55:27 +00:00
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-04 01:42:03 +00:00
|
|
|
|
}
|
2018-08-05 11:55:27 +00:00
|
|
|
|
|
|
|
|
|
new Session();
|