diff --git a/packages/backend/src/core/MahjongService.ts b/packages/backend/src/core/MahjongService.ts index a10720bf79..ca8c2f685e 100644 --- a/packages/backend/src/core/MahjongService.ts +++ b/packages/backend/src/core/MahjongService.ts @@ -280,28 +280,18 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { room.gameState = Mmj.MasterGameEngine.createInitialState(); room.isStarted = true; - await this.saveRoom(room); this.globalEventService.publishMahjongRoomStream(room.id, 'started', { room: room }); - return room; + this.kyokuStarted(room); } @bindThis - public async packRoom(room: Room, me: MiUser) { - if (room.gameState) { - const mj = new Mmj.MasterGameEngine(room.gameState); - const myIndex = room.user1Id === me.id ? 1 : room.user2Id === me.id ? 2 : room.user3Id === me.id ? 3 : 4; - return { - ...room, - gameState: mj.createPlayerState(myIndex), - }; - } else { - return { - ...room, - }; - } + private kyokuStarted(room: Room) { + const mj = new Mmj.MasterGameEngine(room.gameState); + + this.waitForTurn(room, mj.turn, mj); } @bindThis @@ -379,6 +369,17 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { }, 2000); } + @bindThis + private async nextKyoku(room: Room, mj: Mmj.MasterGameEngine) { + const res = mj.commit_nextKyoku(); + room.gameState = mj.getState(); + await this.saveRoom(room); + this.globalEventService.publishMahjongRoomStream(room.id, 'nextKyoku', { + room: room, + }); + this.kyokuStarted(room); + } + @bindThis private async dahai(room: Room, mj: Mmj.MasterGameEngine, house: Mmj.House, tile: Mmj.TileId, riichi = false) { const res = mj.commit_dahai(house, tile, riichi); @@ -551,6 +552,8 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { await this.saveRoom(room); this.globalEventService.publishMahjongRoomStream(room.id, 'tsumoHora', { house: myHouse, handTiles: res.handTiles, tsumoTile: res.tsumoTile }); + + this.endKyoku(room, mj); } @bindThis @@ -700,6 +703,27 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { await this.redisClient.del(`mahjong:gameTurnWaiting:${roomId}`); } + @bindThis + public packState(room: Room, me: MiUser) { + const mj = new Mmj.MasterGameEngine(room.gameState); + const myIndex = room.user1Id === me.id ? 1 : room.user2Id === me.id ? 2 : room.user3Id === me.id ? 3 : 4; + return mj.createPlayerState(myIndex); + } + + @bindThis + public async packRoom(room: Room, me: MiUser) { + if (room.gameState) { + return { + ...room, + gameState: this.packState(room, me), + }; + } else { + return { + ...room, + }; + } + } + @bindThis public dispose(): void { } diff --git a/packages/backend/src/server/api/stream/channels/mahjong-room.ts b/packages/backend/src/server/api/stream/channels/mahjong-room.ts index de7fc4f467..7daac7839c 100644 --- a/packages/backend/src/server/api/stream/channels/mahjong-room.ts +++ b/packages/backend/src/server/api/stream/channels/mahjong-room.ts @@ -40,6 +40,11 @@ class MahjongRoomChannel extends Channel { this.send('started', { room: packed, }); + } else if (message.type === 'nextKyoku') { + const packed = this.mahjongService.packState(message.body.room, this.user!); + this.send('nextKyoku', { + state: packed, + }); } else { this.send(message.type, message.body); } diff --git a/packages/frontend/assets/mahjong/kaisi.png b/packages/frontend/assets/mahjong/kaisi.png new file mode 100644 index 0000000000..322d2e08e3 Binary files /dev/null and b/packages/frontend/assets/mahjong/kaisi.png differ diff --git a/packages/frontend/src/pages/mahjong/hand-tiles.vue b/packages/frontend/src/pages/mahjong/hand-tiles.vue index ebfb6e8cc1..81ad21b1b2 100644 --- a/packages/frontend/src/pages/mahjong/hand-tiles.vue +++ b/packages/frontend/src/pages/mahjong/hand-tiles.vue @@ -40,12 +40,12 @@ import { computed } from 'vue'; import * as Mmj from 'misskey-mahjong'; //#region syntax suger -function mj$(tileId: Mmj.TileId): Mmj.TileInstance { - return Mmj.findTileByIdOrFail(tileId); +function mj$(tid: Mmj.TileId): Mmj.TileInstance { + return Mmj.findTileByIdOrFail(tid); } -function mj$type(tileId: Mmj.TileId): Mmj.TileType { - return mj$(tileId).t; +function mj$type(tid: Mmj.TileId): Mmj.TileType { + return mj$(tid).t; } //#endregion diff --git a/packages/frontend/src/pages/mahjong/room.game.vue b/packages/frontend/src/pages/mahjong/room.game.vue index 2f8b7af8cb..f51d192a0d 100644 --- a/packages/frontend/src/pages/mahjong/room.game.vue +++ b/packages/frontend/src/pages/mahjong/room.game.vue @@ -168,6 +168,17 @@ SPDX-License-Identifier: AGPL-3.0-only +
+ + + +
+
{{ res.pointDeltas.e }} / {{ res.pointDeltas.s }} / {{ res.pointDeltas.w }} / {{ res.pointDeltas.n }}
+ OK @@ -235,12 +247,12 @@ import * as os from '@/os.js'; import { confetti } from '@/scripts/confetti.js'; //#region syntax suger -function mj$(tileId: Mmj.TileId): Mmj.TileInstance { - return Mmj.findTileByIdOrFail(tileId); +function mj$(tid: Mmj.TileId): Mmj.TileInstance { + return Mmj.findTileByIdOrFail(tid); } -function mj$type(tileId: Mmj.TileId): Mmj.TileType { - return mj$(tileId).t; +function mj$type(tid: Mmj.TileId): Mmj.TileType { + return mj$(tid).t; } //#endregion @@ -256,11 +268,11 @@ const myUserNumber = computed(() => room.value.user1Id === $i.id ? 1 : room.valu const mj = shallowRef(new Mmj.PlayerGameEngine(myUserNumber.value, room.value.gameState)); const isMyTurn = computed(() => { - return mj.value.state.turn === mj.value.myHouse; + return mj.value.turn === mj.value.myHouse; }); const canHora = computed(() => { - return Mmj.getHoraSets(mj.value.myHandTiles).length > 0; + return Mmj.getHoraSets(mj.value.myHandTileTypes).length > 0; }); const selectableTiles = ref(null); @@ -286,6 +298,7 @@ const kyokuResults = ref { + ronSerifHouses[caller] = false; + }, 2000); } console.log('ronned', res); @@ -620,6 +641,9 @@ function onStreamTsumoHora(log) { }, 1500); tsumoSerifHouses[log.house] = true; + window.setTimeout(() => { + tsumoSerifHouses[log.house] = false; + }, 2000); console.log('tsumohora', res); } @@ -634,6 +658,23 @@ function onStreamRyuukyoku(log) { }, 1500); } +function onStreamNextKyoku(log) { + console.log('onStreamNextKyoku', log); + + const res = mj.value.commit_nextKyoku(log.state); + triggerRef(mj); + + iTsumoed.value = false; + showKyokuResults.value = false; + kyokuResults.value = { + e: null, + s: null, + w: null, + n: null, + }; + ryuukyokued.value = false; +} + function restoreRoom(_room) { room.value = deepClone(_room); @@ -653,6 +694,7 @@ onMounted(() => { props.connection.on('ronned', onStreamRonned); props.connection.on('tsumoHora', onStreamTsumoHora); props.connection.on('ryuukyoku', onStreamRyuukyoku); + props.connection.on('nextKyoku', onStreamNextKyoku); } }); @@ -669,6 +711,7 @@ onActivated(() => { props.connection.on('ronned', onStreamRonned); props.connection.on('tsumoHora', onStreamTsumoHora); props.connection.on('ryuukyoku', onStreamRyuukyoku); + props.connection.on('nextKyoku', onStreamNextKyoku); } }); @@ -685,6 +728,7 @@ onDeactivated(() => { props.connection.off('ronned', onStreamRonned); props.connection.off('tsumoHora', onStreamTsumoHora); props.connection.off('ryuukyoku', onStreamRyuukyoku); + props.connection.off('nextKyoku', onStreamNextKyoku); } }); @@ -701,6 +745,7 @@ onUnmounted(() => { props.connection.off('ronned', onStreamRonned); props.connection.off('tsumoHora', onStreamTsumoHora); props.connection.off('ryuukyoku', onStreamRyuukyoku); + props.connection.off('nextKyoku', onStreamNextKyoku); } }); diff --git a/packages/misskey-mahjong/src/common.ts b/packages/misskey-mahjong/src/common.ts index cb88faa8d2..52d6421750 100644 --- a/packages/misskey-mahjong/src/common.ts +++ b/packages/misskey-mahjong/src/common.ts @@ -92,14 +92,14 @@ export const TILE_ID_MAP = new Map([ /* eslint-enable no-multi-spaces */ ]); -export function findTileByIdOrFail(tileId: TileId): TileInstance { - const tile = TILE_ID_MAP.get(tileId); - if (tile == null) throw new Error(`tile not found: ${tileId}`); +export function findTileByIdOrFail(tid: TileId): TileInstance { + const tile = TILE_ID_MAP.get(tid); + if (tile == null) throw new Error(`tile not found: ${tid}`); return tile; } -export function findTileById(tileId: TileId): TileInstance | null { - return TILE_ID_MAP.get(tileId) ?? null; +export function findTileById(tid: TileId): TileInstance | null { + return TILE_ID_MAP.get(tid) ?? null; } export type House = 'e' | 's' | 'w' | 'n'; @@ -300,6 +300,13 @@ export const YAKU_DEFINITIONS = [{ calc: (state: EnvForCalcYaku) => { return state.tsumoTile != null; }, +}, { + name: 'ippatsu', + fan: 1, + isYakuman: false, + calc: (state: EnvForCalcYaku) => { + + }, }, { name: 'red', fan: 1, diff --git a/packages/misskey-mahjong/src/engine.master.ts b/packages/misskey-mahjong/src/engine.master.ts index 8361aaf34f..3e2025cb75 100644 --- a/packages/misskey-mahjong/src/engine.master.ts +++ b/packages/misskey-mahjong/src/engine.master.ts @@ -9,12 +9,12 @@ import * as Common from './common.js'; import { PlayerState } from './engine.player.js'; //#region syntax suger -function $(tileId: TileId): Common.TileInstance { - return Common.findTileByIdOrFail(tileId); +function $(tid: TileId): Common.TileInstance { + return Common.findTileByIdOrFail(tid); } -function $type(tileId: TileId): TileType { - return $(tileId).t; +function $type(tid: TileId): TileType { + return $(tid).t; } //#endregion @@ -26,7 +26,7 @@ export type MasterState = { round: 'e' | 's' | 'w' | 'n'; kyoku: number; - + turnCount: number; tiles: TileId[]; kingTiles: TileId[]; activatedDorasCount: number; @@ -59,6 +59,12 @@ export type MasterState = { w: boolean; n: boolean; }; + ippatsus: { + e: boolean; + s: boolean; + w: boolean; + n: boolean; + }; points: { e: number; s: number; @@ -176,6 +182,10 @@ export class MasterGameEngine { return this.state.user4House; } + public get turn(): House | null { + return this.state.turn; + } + public static createInitialState(): MasterState { const ikasama: TileId[] = [125, 129, 9, 56, 57, 61, 77, 81, 85, 133, 134, 135, 121, 122]; @@ -201,6 +211,7 @@ export class MasterGameEngine { user4House: 'n', round: 'e', kyoku: 1, + turnCount: 0, tiles, kingTiles, activatedDorasCount: 1, @@ -253,12 +264,12 @@ export class MasterGameEngine { return tile; } - private canRon(house: House, tileId: TileId): boolean { + private canRon(house: House, tid: TileId): boolean { // フリテン // TODO: ポンされるなどして自分の河にない場合の考慮 - if (this.hoTileTypes[house].includes($type(tileId))) return false; + if (this.hoTileTypes[house].includes($type(tid))) return false; - const horaSets = Common.getHoraSets(this.handTileTypes[house].concat($type(tileId))); + const horaSets = Common.getHoraSets(this.handTileTypes[house].concat($type(tid))); if (horaSets.length === 0) return false; // 完成形じゃない // TODO @@ -268,15 +279,19 @@ export class MasterGameEngine { return true; } - private canPon(house: House, tileId: TileId): boolean { - return this.handTileTypes[house].filter(t => t === $type(tileId)).length === 2; + private canPon(house: House, tid: TileId): boolean { + return this.handTileTypes[house].filter(t => t === $type(tid)).length === 2; } - private canCii(caller: House, callee: House, tileId: TileId): boolean { + private canDaiminkan(caller: House, tid: TileId): boolean { + return this.handTileTypes[caller].filter(t => t === $type(tid)).length === 3; + } + + private canCii(caller: House, callee: House, tid: TileId): boolean { if (callee !== Common.prevHouse(caller)) return false; const hand = this.handTileTypes[caller]; return Common.SHUNTU_PATTERNS.some(pattern => - pattern.includes($type(tileId)) && + pattern.includes($type(tid)) && pattern.filter(t => hand.includes(t)).length >= 2); } @@ -325,68 +340,104 @@ export class MasterGameEngine { this.endKyoku(); } - public commit_dahai(house: House, tileId: TileId, riichi = false) { + public startTransaction() { + const newState = structuredClone(this.state); + return { + state: newState, + commit: () => { + this.state = newState; + }, + }; + } + + public commit_nextKyoku() { + const newState = MasterGameEngine.createInitialState(); + newState.kyoku = this.state.kyoku + 1; + newState.points = this.state.points; + newState.turn = 'e'; + newState.user1House = Common.nextHouse(this.state.user1House); + newState.user2House = Common.nextHouse(this.state.user2House); + newState.user3House = Common.nextHouse(this.state.user3House); + newState.user4House = Common.nextHouse(this.state.user4House); + this.state = newState; + } + + public commit_dahai(house: House, tid: TileId, riichi = false) { + const { state, commit } = this.startTransaction(); + if (this.state.turn !== house) throw new Error('Not your turn'); if (riichi) { + if (this.state.riichis[house]) throw new Error('Already riichi'); const tempHandTiles = [...this.handTileTypes[house]]; - tempHandTiles.splice(tempHandTiles.indexOf($type(tileId)), 1); + tempHandTiles.splice(tempHandTiles.indexOf($type(tid)), 1); if (Common.getHoraTiles(tempHandTiles).length === 0) throw new Error('Not tenpai'); if (this.state.points[house] < 1000) throw new Error('Not enough points'); } const handTiles = this.state.handTiles[house]; - if (!handTiles.includes(tileId)) throw new Error('No such tile in your hand'); - handTiles.splice(handTiles.indexOf(tileId), 1); - this.state.hoTiles[house].push(tileId); + if (!handTiles.includes(tid)) throw new Error('No such tile in your hand'); + handTiles.splice(handTiles.indexOf(tid), 1); + this.state.hoTiles[house].push(tid); + + if (this.state.riichis[house]) { + this.state.ippatsus[house] = false; + } if (riichi) { this.state.riichis[house] = true; + this.state.ippatsus[house] = true; } const canRonHouses: House[] = []; switch (house) { case 'e': - if (this.canRon('s', tileId)) canRonHouses.push('s'); - if (this.canRon('w', tileId)) canRonHouses.push('w'); - if (this.canRon('n', tileId)) canRonHouses.push('n'); + if (this.canRon('s', tid)) canRonHouses.push('s'); + if (this.canRon('w', tid)) canRonHouses.push('w'); + if (this.canRon('n', tid)) canRonHouses.push('n'); break; case 's': - if (this.canRon('e', tileId)) canRonHouses.push('e'); - if (this.canRon('w', tileId)) canRonHouses.push('w'); - if (this.canRon('n', tileId)) canRonHouses.push('n'); + if (this.canRon('e', tid)) canRonHouses.push('e'); + if (this.canRon('w', tid)) canRonHouses.push('w'); + if (this.canRon('n', tid)) canRonHouses.push('n'); break; case 'w': - if (this.canRon('e', tileId)) canRonHouses.push('e'); - if (this.canRon('s', tileId)) canRonHouses.push('s'); - if (this.canRon('n', tileId)) canRonHouses.push('n'); + if (this.canRon('e', tid)) canRonHouses.push('e'); + if (this.canRon('s', tid)) canRonHouses.push('s'); + if (this.canRon('n', tid)) canRonHouses.push('n'); break; case 'n': - if (this.canRon('e', tileId)) canRonHouses.push('e'); - if (this.canRon('s', tileId)) canRonHouses.push('s'); - if (this.canRon('w', tileId)) canRonHouses.push('w'); + if (this.canRon('e', tid)) canRonHouses.push('e'); + if (this.canRon('s', tid)) canRonHouses.push('s'); + if (this.canRon('w', tid)) canRonHouses.push('w'); break; } - const canKanHouse: House | null = null; + let canKanHouse: House | null = null; + switch (house) { + case 'e': canKanHouse = this.canDaiminkan('s', tid) ? 's' : this.canDaiminkan('w', tid) ? 'w' : this.canDaiminkan('n', tid) ? 'n' : null; break; + case 's': canKanHouse = this.canDaiminkan('e', tid) ? 'e' : this.canDaiminkan('w', tid) ? 'w' : this.canDaiminkan('n', tid) ? 'n' : null; break; + case 'w': canKanHouse = this.canDaiminkan('e', tid) ? 'e' : this.canDaiminkan('s', tid) ? 's' : this.canDaiminkan('n', tid) ? 'n' : null; break; + case 'n': canKanHouse = this.canDaiminkan('e', tid) ? 'e' : this.canDaiminkan('s', tid) ? 's' : this.canDaiminkan('w', tid) ? 'w' : null; break; + } let canPonHouse: House | null = null; switch (house) { - case 'e': canPonHouse = this.canPon('s', tileId) ? 's' : this.canPon('w', tileId) ? 'w' : this.canPon('n', tileId) ? 'n' : null; break; - case 's': canPonHouse = this.canPon('e', tileId) ? 'e' : this.canPon('w', tileId) ? 'w' : this.canPon('n', tileId) ? 'n' : null; break; - case 'w': canPonHouse = this.canPon('e', tileId) ? 'e' : this.canPon('s', tileId) ? 's' : this.canPon('n', tileId) ? 'n' : null; break; - case 'n': canPonHouse = this.canPon('e', tileId) ? 'e' : this.canPon('s', tileId) ? 's' : this.canPon('w', tileId) ? 'w' : null; break; + case 'e': canPonHouse = this.canPon('s', tid) ? 's' : this.canPon('w', tid) ? 'w' : this.canPon('n', tid) ? 'n' : null; break; + case 's': canPonHouse = this.canPon('e', tid) ? 'e' : this.canPon('w', tid) ? 'w' : this.canPon('n', tid) ? 'n' : null; break; + case 'w': canPonHouse = this.canPon('e', tid) ? 'e' : this.canPon('s', tid) ? 's' : this.canPon('n', tid) ? 'n' : null; break; + case 'n': canPonHouse = this.canPon('e', tid) ? 'e' : this.canPon('s', tid) ? 's' : this.canPon('w', tid) ? 'w' : null; break; } let canCiiHouse: House | null = null; switch (house) { - case 'e': canCiiHouse = this.canCii('s', house, tileId) ? 's' : this.canCii('w', house, tileId) ? 'w' : this.canCii('n', house, tileId) ? 'n' : null; break; - case 's': canCiiHouse = this.canCii('e', house, tileId) ? 'e' : this.canCii('w', house, tileId) ? 'w' : this.canCii('n', house, tileId) ? 'n' : null; break; - case 'w': canCiiHouse = this.canCii('e', house, tileId) ? 'e' : this.canCii('s', house, tileId) ? 's' : this.canCii('n', house, tileId) ? 'n' : null; break; - case 'n': canCiiHouse = this.canCii('e', house, tileId) ? 'e' : this.canCii('s', house, tileId) ? 's' : this.canCii('w', house, tileId) ? 'w' : null; break; + case 'e': canCiiHouse = this.canCii('s', house, tid) ? 's' : this.canCii('w', house, tid) ? 'w' : this.canCii('n', house, tid) ? 'n' : null; break; + case 's': canCiiHouse = this.canCii('e', house, tid) ? 'e' : this.canCii('w', house, tid) ? 'w' : this.canCii('n', house, tid) ? 'n' : null; break; + case 'w': canCiiHouse = this.canCii('e', house, tid) ? 'e' : this.canCii('s', house, tid) ? 's' : this.canCii('n', house, tid) ? 'n' : null; break; + case 'n': canCiiHouse = this.canCii('e', house, tid) ? 'e' : this.canCii('s', house, tid) ? 's' : this.canCii('w', house, tid) ? 'w' : null; break; } - if (canRonHouses.length > 0 || canPonHouse != null || canCiiHouse != null) { + if (canRonHouses.length > 0 || canKanHouse != null || canPonHouse != null || canCiiHouse != null) { if (canRonHouses.length > 0) { this.state.askings.ron = { callee: house, @@ -445,13 +496,18 @@ export class MasterGameEngine { }; } - public commit_kakan(house: House, tileId: TileId) { - const pon = this.state.huros[house].find(h => h.type === 'pon' && $type(h.tiles[0]) === $type(tileId)); + public commit_kakan(house: House, tid: TileId) { + const pon = this.state.huros[house].find(h => h.type === 'pon' && $type(h.tiles[0]) === $type(tid)); if (pon == null) throw new Error('No such pon'); - this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(tileId), 1); - const tiles = [tileId, ...pon.tiles]; + this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(tid), 1); + const tiles = [tid, ...pon.tiles]; this.state.huros[house].push({ type: 'minkan', tiles: tiles, from: pon.from }); + this.state.ippatsus.e = false; + this.state.ippatsus.s = false; + this.state.ippatsus.w = false; + this.state.ippatsus.n = false; + this.state.activatedDorasCount++; const rinsyan = this.tsumo(); @@ -463,14 +519,14 @@ export class MasterGameEngine { }; } - public commit_ankan(house: House, tileId: TileId) { - const t1 = this.state.handTiles[house].filter(t => $type(t) === $type(tileId)).at(0); + public commit_ankan(house: House, tid: TileId) { + const t1 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(0); if (t1 == null) throw new Error('No such tile'); - const t2 = this.state.handTiles[house].filter(t => $type(t) === $type(tileId)).at(1); + const t2 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(1); if (t2 == null) throw new Error('No such tile'); - const t3 = this.state.handTiles[house].filter(t => $type(t) === $type(tileId)).at(2); + const t3 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(2); if (t3 == null) throw new Error('No such tile'); - const t4 = this.state.handTiles[house].filter(t => $type(t) === $type(tileId)).at(3); + const t4 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(3); if (t4 == null) throw new Error('No such tile'); this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t1), 1); this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t2), 1); @@ -479,6 +535,11 @@ export class MasterGameEngine { const tiles = [t1, t2, t3, t4]; this.state.huros[house].push({ type: 'ankan', tiles: tiles }); + this.state.ippatsus.e = false; + this.state.ippatsus.s = false; + this.state.ippatsus.w = false; + this.state.ippatsus.n = false; + this.state.activatedDorasCount++; const rinsyan = this.tsumo(); @@ -567,6 +628,11 @@ export class MasterGameEngine { const tiles = [tile, t1, t2, t3]; this.state.huros[kan.caller].push({ type: 'minkan', tiles: tiles, from: kan.callee }); + this.state.ippatsus.e = false; + this.state.ippatsus.s = false; + this.state.ippatsus.w = false; + this.state.ippatsus.n = false; + this.state.activatedDorasCount++; const rinsyan = this.tsumo(); @@ -594,6 +660,11 @@ export class MasterGameEngine { const tiles = [tile, t1, t2]; this.state.huros[pon.caller].push({ type: 'pon', tiles: tiles, from: pon.callee }); + this.state.ippatsus.e = false; + this.state.ippatsus.s = false; + this.state.ippatsus.w = false; + this.state.ippatsus.n = false; + this.state.turn = pon.caller; return { @@ -654,6 +725,11 @@ export class MasterGameEngine { this.state.huros[cii.caller].push({ type: 'cii', tiles: tiles, from: cii.callee }); + this.state.ippatsus.e = false; + this.state.ippatsus.s = false; + this.state.ippatsus.w = false; + this.state.ippatsus.n = false; + this.state.turn = cii.caller; return { @@ -699,6 +775,7 @@ export class MasterGameEngine { user4House: this.state.user4House, round: this.state.round, kyoku: this.state.kyoku, + turnCount: this.state.turnCount, tilesCount: this.state.tiles.length, doraIndicateTiles: this.state.kingTiles.slice(0, this.state.activatedDorasCount), handTiles: { @@ -756,3 +833,7 @@ export class MasterGameEngine { return structuredClone(this.state); } } + +function commit_dahai(state: MasterState): MasterState { + +} diff --git a/packages/misskey-mahjong/src/engine.player.ts b/packages/misskey-mahjong/src/engine.player.ts index f2ba137eff..d88e43df38 100644 --- a/packages/misskey-mahjong/src/engine.player.ts +++ b/packages/misskey-mahjong/src/engine.player.ts @@ -8,12 +8,12 @@ import { TileType, House, Huro, TileId, YAKU_DEFINITIONS } from './common.js'; import * as Common from './common.js'; //#region syntax suger -function $(tileId: TileId): Common.TileInstance { - return Common.findTileByIdOrFail(tileId); +function $(tid: TileId): Common.TileInstance { + return Common.findTileByIdOrFail(tid); } -function $type(tileId: TileId): TileType { - return $(tileId).t; +function $type(tid: TileId): TileType { + return $(tid).t; } //#endregion @@ -26,6 +26,7 @@ export type PlayerState = { round: 'e' | 's' | 'w' | 'n'; kyoku: number; + turnCount: number; tilesCount: number; doraIndicateTiles: TileId[]; @@ -111,6 +112,10 @@ export class PlayerGameEngine { return this.state.huros; } + public get turnCount(): number { + return this.state.turnCount; + } + public get tilesCount(): number { return this.state.tilesCount; } @@ -131,6 +136,26 @@ export class PlayerGameEngine { return this.state.canCii; } + public get turn(): House | null { + return this.state.turn; + } + + public get user1House(): House { + return this.state.user1House; + } + + public get user2House(): House { + return this.state.user2House; + } + + public get user3House(): House { + return this.state.user3House; + } + + public get user4House(): House { + return this.state.user4House; + } + public get myHouse(): House { switch (this.myUserNumber) { case 1: return this.state.user1House; @@ -152,19 +177,23 @@ export class PlayerGameEngine { return this.state.riichis[this.myHouse]; } - public commit_tsumo(house: House, tileId: TileId) { - console.log('commit_tsumo', this.state.turn, house, tileId); + public commit_nextKyoku(state: PlayerState) { + this.state = state; + } + + public commit_tsumo(house: House, tid: TileId) { + console.log('commit_tsumo', this.state.turn, house, tid); this.state.tilesCount--; this.state.turn = house; if (house === this.myHouse) { - this.myHandTiles.push(tileId); + this.myHandTiles.push(tid); } else { this.state.handTiles[house].push(0); } } - public commit_dahai(house: House, tileId: TileId, riichi = false) { - console.log('commit_dahai', this.state.turn, house, tileId, riichi); + public commit_dahai(house: House, tid: TileId, riichi = false) { + console.log('commit_dahai', this.state.turn, house, tid, riichi); if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError(); if (riichi) { @@ -172,23 +201,23 @@ export class PlayerGameEngine { } if (house === this.myHouse) { - this.myHandTiles.splice(this.myHandTiles.indexOf(tileId), 1); - this.state.hoTiles[this.myHouse].push(tileId); + this.myHandTiles.splice(this.myHandTiles.indexOf(tid), 1); + this.state.hoTiles[this.myHouse].push(tid); } else { this.state.handTiles[house].pop(); - this.state.hoTiles[house].push(tileId); + this.state.hoTiles[house].push(tid); } this.state.turn = null; if (house === this.myHouse) { } else { - const canRon = Common.getHoraSets(this.myHandTiles.concat(tileId).map(id => $type(id))).length > 0; - const canPon = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tileId)).length === 2; - const canKan = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tileId)).length === 3; + const canRon = Common.getHoraSets(this.myHandTiles.concat(tid).map(id => $type(id))).length > 0; + const canPon = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tid)).length === 2; + const canKan = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tid)).length === 3; const canCii = !this.isMeRiichi && house === Common.prevHouse(this.myHouse) && Common.SHUNTU_PATTERNS.some(pattern => - pattern.includes($type(tileId)) && + pattern.includes($type(tid)) && pattern.filter(t => this.myHandTileTypes.includes(t)).length >= 2); this.state.canRon = canRon ? { callee: house } : null;