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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user