1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-24 16:14:11 +02:00

feature(mahjong): 搶槓/ドラ以外の麻雀の役を実装 (#14346)

* ビルドによる自動的なソース更新

* 麻雀関連のキーバリューペアを追加

* 役の定義をまとめてエクスポート

* タイポ修正

* Revert "麻雀関連のキーバリューペアを追加"

This reverts commit c349cdf70c.

* misskey-jsのビルドによる自動更新

* 型エラーに対処

* riichiがtrueの場合に門前であるかを確認

* EnvForCalcYakuのhouseプロパティを廃止

* 風牌の役の共通部分をクラスで定義

* タイポ修正

* 役牌をクラスで共通化

* 一盃口と二盃口のテストを通す

* 一盃口・二盃口判定関数の調整

* 一気通貫の判定にチーによる順子も考慮する

* 混全帯幺九の実装

* 純全帯幺九の実装

* 七対子の実装とテストの修正

* tsumoTileまたはronTileを必須に

* 待ちを確認して平和の判定を可能に

* 三暗刻と四暗刻、四暗刻単騎の実装

* 四暗刻であるために通常の役を判定できない牌姿のテストを修正

* 混老頭と清老頭を実装

* 三槓子と四槓子を実装

* 平和の実装とテストを修正

* 小三元のテストを修正

* 国士無双に対子の確認を追加

* 国士無双十三面待ちを実装し、テストを修正

* 一部の役の七対子形を認め、テストを追加

* 手牌の数を確認

* 役の定義をカプセル化して型エラーの対処

* ツモ・ロンの判定を修正

* calcYakusの引数のhandTilesを修正

* calcYakusに渡す風をseatWindに修正

* 嶺上開花の実装

* 海底摸月の実装

* FourMentsuOneJyantouWithWait型の作成

* 河底撈魚の実装

* ダブル立直の実装

* 天和・地和の実装

* エンジンのテストを作成

* エンジンによる地和のテストを追加

* 嶺上開花のテスト

* ライセンスの記述を追加

* ダブル立直一発ツモのテスト

* ダブル立直海底ツモのテスト

* ダブル立直河底のテスト

* 役満も処理できるように

* 点数のテスト

* 打牌時にrinshanFlags[house]をfalseに

* 七対子形の字一色を認める

* typo
This commit is contained in:
Take-John
2024-08-15 12:29:31 +09:00
committed by GitHub
parent f32b11ba12
commit bf818a6656
8 changed files with 1417 additions and 492 deletions

View File

@@ -6,7 +6,7 @@
import CRC32 from 'crc-32';
import { TileType, House, Huro, TileId } from './common.js';
import * as Common from './common.js';
import { YAKU_DEFINITIONS } from './common.yaku.js';
import { calcYakusWithDetail, convertHuroForCalcYaku } from './common.yaku.js';
//#region syntax suger
function $(tid: TileId): Common.TileInstance {
@@ -53,18 +53,36 @@ export type PlayerState = {
w: Huro[];
n: Huro[];
};
firstTurnFlags: {
e: boolean;
s: boolean;
w: boolean;
n: boolean;
};
riichis: {
e: boolean;
s: boolean;
w: boolean;
n: boolean;
};
doubleRiichis: {
e: boolean;
s: boolean;
w: boolean;
n: boolean;
};
ippatsus: {
e: boolean;
s: boolean;
w: boolean;
n: boolean;
};
rinshanFlags: {
e: boolean;
s: boolean;
w: boolean;
n: boolean;
}
points: {
e: number;
s: number;
@@ -80,7 +98,7 @@ export type PlayerState = {
};
export type KyokuResult = {
yakus: { name: string; fan: number; isYakuman: boolean; }[];
yakus: { name: string; fan: number | null; isYakuman: boolean; }[];
doraCount: number;
pointDeltas: { e: number; s: number; w: number; n: number; };
};
@@ -241,31 +259,30 @@ export class PlayerGameEngine {
public commit_tsumoHora(house: House, handTiles: TileId[], tsumoTile: TileId): KyokuResult {
console.log('commit_tsumoHora', this.state.turn, house);
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
house: house,
const yakus = calcYakusWithDetail({
seatWind: house,
handTiles: handTiles.map(id => $type(id)),
huros: this.state.huros[house],
huros: this.state.huros[house].map(convertHuroForCalcYaku),
tsumoTile: $type(tsumoTile),
ronTile: null,
firstTurn: this.state.firstTurnFlags[house],
riichi: this.state.riichis[house],
doubleRiichi: this.state.doubleRiichis[house],
ippatsu: this.state.ippatsus[house],
}));
rinshan: this.state.rinshanFlags[house],
haitei: this.state.tilesCount == 0,
});
const doraCount =
Common.calcOwnedDoraCount(handTiles.map(id => $type(id)), this.state.huros[house], this.doras) +
Common.calcRedDoraCount(handTiles, this.state.huros[house]);
const fans = yakus.map(yaku => yaku.fan).reduce((a, b) => a + b, 0) + doraCount;
const pointDeltas = Common.calcTsumoHoraPointDeltas(house, fans);
const pointDeltas = Common.calcTsumoHoraPointDeltas(house, yakus);
this.state.points.e += pointDeltas.e;
this.state.points.s += pointDeltas.s;
this.state.points.w += pointDeltas.w;
this.state.points.n += pointDeltas.n;
return {
yakus: yakus.map(yaku => ({
name: yaku.name,
fan: yaku.fan,
isYakuman: yaku.isYakuman,
})),
yakus: yakus.yakus,
doraCount,
pointDeltas,
};
@@ -293,24 +310,27 @@ export class PlayerGameEngine {
n: { yakus: [], doraCount: 0, pointDeltas: { e: 0, s: 0, w: 0, n: 0 } },
};
const ronTile = $type(this.state.hoTiles[callee].at(-1)!);
for (const house of callers) {
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
house: house,
handTiles: handTiles[house].map(id => $type(id)),
huros: this.state.huros[house],
const yakus = calcYakusWithDetail({
seatWind: house,
handTiles: handTiles[house].map(id => $type(id)).concat([ronTile]),
huros: this.state.huros[house].map(convertHuroForCalcYaku),
tsumoTile: null,
ronTile: $type(this.state.hoTiles[callee].at(-1)!),
ronTile: ronTile,
firstTurn: this.state.firstTurnFlags[house],
riichi: this.state.riichis[house],
doubleRiichi: this.state.doubleRiichis[house],
ippatsu: this.state.ippatsus[house],
}));
hotei: this.state.tilesCount == 0,
});
const doraCount =
Common.calcOwnedDoraCount(handTiles[house].map(id => $type(id)), this.state.huros[house], this.doras) +
Common.calcRedDoraCount(handTiles[house], this.state.huros[house]);
const fans = yakus.map(yaku => yaku.fan).reduce((a, b) => a + b, 0) + doraCount;
const point = Common.fanToPoint(fans, house === 'e');
const point = Common.calcPoint(yakus, house === 'e');
this.state.points[callee] -= point;
this.state.points[house] += point;
resultMap[house].yakus = yakus.map(yaku => ({ name: yaku.name, fan: yaku.fan, isYakuman: yaku.isYakuman }));
resultMap[house].yakus = yakus.yakus;
resultMap[house].doraCount = doraCount;
resultMap[house].pointDeltas[callee] = -point;
resultMap[house].pointDeltas[house] = point;
@@ -329,7 +349,7 @@ export class PlayerGameEngine {
* @param caller ポンした人
* @param callee 牌を捨てた人
*/
public commit_pon(caller: House, callee: House, tiles: TileId[]) {
public commit_pon(caller: House, callee: House, tiles: readonly [TileId, TileId, TileId]) {
this.state.canPon = null;
this.state.hoTiles[callee].pop();
@@ -351,7 +371,7 @@ export class PlayerGameEngine {
* @param caller 大明槓した人
* @param callee 牌を捨てた人
*/
public commit_kan(caller: House, callee: House, tiles: TileId[], rinsyan: TileId) {
public commit_kan(caller: House, callee: House, tiles: readonly [TileId, TileId, TileId, TileId], rinsyan: TileId) {
this.state.canKan = null;
this.state.hoTiles[callee].pop();
@@ -383,7 +403,7 @@ export class PlayerGameEngine {
* @param caller チーした人
* @param callee 牌を捨てた人
*/
public commit_cii(caller: House, callee: House, tiles: TileId[]) {
public commit_cii(caller: House, callee: House, tiles: readonly [TileId, TileId, TileId]) {
this.state.canCii = null;
this.state.hoTiles[callee].pop();