wip
This commit is contained in:
parent
2d6f9b083f
commit
166aeb631e
4 changed files with 416 additions and 236 deletions
|
@ -32,6 +32,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span :class="$style.centerPanelPoint">{{ mj.points[mj.myHouse] }}</span>
|
<span :class="$style.centerPanelPoint">{{ mj.points[mj.myHouse] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div :class="$style.centerPanelIndicatorContainerToi">
|
||||||
|
<div style="position: absolute; left: 0; right: 0; bottom: 0;">
|
||||||
|
<div :class="[$style.centerPanelIndicator, { [$style.centerPanelIndicatorIndicated]: mj.turn === Mmj.prevHouse(Mmj.prevHouse(mj.myHouse)) }]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.centerPanelIndicatorContainerKami">
|
||||||
|
<div style="position: absolute; left: 0; right: 0; bottom: 0;">
|
||||||
|
<div :class="[$style.centerPanelIndicator, { [$style.centerPanelIndicatorIndicated]: mj.turn === Mmj.prevHouse(mj.myHouse) }]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.centerPanelIndicatorContainerSimo">
|
||||||
|
<div style="position: absolute; left: 0; right: 0; bottom: 0;">
|
||||||
|
<div :class="[$style.centerPanelIndicator, { [$style.centerPanelIndicatorIndicated]: mj.turn === Mmj.nextHouse(mj.myHouse) }]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.centerPanelIndicatorContainerMe">
|
||||||
|
<div style="position: absolute; left: 0; right: 0; bottom: 0;">
|
||||||
|
<div :class="[$style.centerPanelIndicator, { [$style.centerPanelIndicatorIndicated]: mj.turn === mj.myHouse }]"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ mj.tilesCount }}</div>
|
<div>{{ mj.tilesCount }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -866,6 +886,63 @@ onUnmounted(() => {
|
||||||
.centerPanelPoint {
|
.centerPanelPoint {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
.centerPanelIndicatorContainerToi {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
rotate: 180deg;
|
||||||
|
}
|
||||||
|
.centerPanelIndicatorContainerKami {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
rotate: 90deg;
|
||||||
|
}
|
||||||
|
.centerPanelIndicatorContainerSimo {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
rotate: -90deg;
|
||||||
|
}
|
||||||
|
.centerPanelIndicatorContainerMe {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.centerPanelIndicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: 100px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
.centerPanelIndicatorIndicated {
|
||||||
|
background: #ff0;
|
||||||
|
}
|
||||||
|
|
||||||
.handTilesOfToimen {
|
.handTilesOfToimen {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -284,6 +284,11 @@ type EnvForCalcYaku = {
|
||||||
* リーチしたかどうか
|
* リーチしたかどうか
|
||||||
*/
|
*/
|
||||||
riichi: boolean;
|
riichi: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一巡目以内かどうか
|
||||||
|
*/
|
||||||
|
ippatsu: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const YAKU_DEFINITIONS = [{
|
export const YAKU_DEFINITIONS = [{
|
||||||
|
@ -305,7 +310,7 @@ export const YAKU_DEFINITIONS = [{
|
||||||
fan: 1,
|
fan: 1,
|
||||||
isYakuman: false,
|
isYakuman: false,
|
||||||
calc: (state: EnvForCalcYaku) => {
|
calc: (state: EnvForCalcYaku) => {
|
||||||
|
return state.ippatsu;
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: 'red',
|
name: 'red',
|
||||||
|
|
|
@ -18,6 +18,114 @@ function $type(tid: TileId): TileType {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
class StateManager {
|
||||||
|
public state: MasterState;
|
||||||
|
private commitCallback: (state: MasterState) => void;
|
||||||
|
|
||||||
|
constructor(state: MasterState, commitCallback: (state: MasterState) => void) {
|
||||||
|
this.state = structuredClone(state);
|
||||||
|
this.commitCallback = commitCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public commit() {
|
||||||
|
this.commitCallback(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get doras(): TileType[] {
|
||||||
|
return this.state.kingTiles.slice(0, this.state.activatedDorasCount)
|
||||||
|
.map(id => Common.nextTileForDora($type(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get handTiles(): Record<House, TileId[]> {
|
||||||
|
return this.state.handTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get handTileTypes(): Record<House, TileType[]> {
|
||||||
|
return {
|
||||||
|
e: this.state.handTiles.e.map(id => $type(id)),
|
||||||
|
s: this.state.handTiles.s.map(id => $type(id)),
|
||||||
|
w: this.state.handTiles.w.map(id => $type(id)),
|
||||||
|
n: this.state.handTiles.n.map(id => $type(id)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hoTileTypes(): Record<House, TileType[]> {
|
||||||
|
return {
|
||||||
|
e: this.state.hoTiles.e.map(id => $type(id)),
|
||||||
|
s: this.state.hoTiles.s.map(id => $type(id)),
|
||||||
|
w: this.state.hoTiles.w.map(id => $type(id)),
|
||||||
|
n: this.state.hoTiles.n.map(id => $type(id)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get riichis(): Record<House, boolean> {
|
||||||
|
return this.state.riichis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get askings(): MasterState['askings'] {
|
||||||
|
return this.state.askings;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 turn(): House | null {
|
||||||
|
return this.state.turn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canRon(house: House, tid: TileId): boolean {
|
||||||
|
// フリテン
|
||||||
|
// TODO: ポンされるなどして自分の河にない場合の考慮
|
||||||
|
if (this.hoTileTypes[house].includes($type(tid))) return false;
|
||||||
|
|
||||||
|
const horaSets = Common.getHoraSets(this.handTileTypes[house].concat($type(tid)));
|
||||||
|
if (horaSets.length === 0) return false; // 完成形じゃない
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc(this.state, { tsumoTile: null, ronTile: tile }));
|
||||||
|
//if (yakus.length === 0) return false; // 役がない
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canPon(house: House, tid: TileId): boolean {
|
||||||
|
return this.handTileTypes[house].filter(t => t === $type(tid)).length === 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canDaiminkan(caller: House, tid: TileId): boolean {
|
||||||
|
return this.handTileTypes[caller].filter(t => t === $type(tid)).length === 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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(tid)) &&
|
||||||
|
pattern.filter(t => hand.includes(t)).length >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public tsumo(): TileId {
|
||||||
|
const tile = this.state.tiles.pop();
|
||||||
|
if (tile == null) throw new Error('No tiles left');
|
||||||
|
if (this.state.turn == null) throw new Error('Not your turn');
|
||||||
|
this.state.handTiles[this.state.turn].push(tile);
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type MasterState = {
|
export type MasterState = {
|
||||||
user1House: House;
|
user1House: House;
|
||||||
user2House: House;
|
user2House: House;
|
||||||
|
@ -239,6 +347,12 @@ export class MasterGameEngine {
|
||||||
w: false,
|
w: false,
|
||||||
n: false,
|
n: false,
|
||||||
},
|
},
|
||||||
|
ippatsus: {
|
||||||
|
e: false,
|
||||||
|
s: false,
|
||||||
|
w: false,
|
||||||
|
n: false,
|
||||||
|
},
|
||||||
points: {
|
points: {
|
||||||
e: 25000,
|
e: 25000,
|
||||||
s: 25000,
|
s: 25000,
|
||||||
|
@ -256,45 +370,6 @@ export class MasterGameEngine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private tsumo(): TileId {
|
|
||||||
const tile = this.state.tiles.pop();
|
|
||||||
if (tile == null) throw new Error('No tiles left');
|
|
||||||
if (this.state.turn == null) throw new Error('Not your turn');
|
|
||||||
this.state.handTiles[this.state.turn].push(tile);
|
|
||||||
return tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private canRon(house: House, tid: TileId): boolean {
|
|
||||||
// フリテン
|
|
||||||
// TODO: ポンされるなどして自分の河にない場合の考慮
|
|
||||||
if (this.hoTileTypes[house].includes($type(tid))) return false;
|
|
||||||
|
|
||||||
const horaSets = Common.getHoraSets(this.handTileTypes[house].concat($type(tid)));
|
|
||||||
if (horaSets.length === 0) return false; // 完成形じゃない
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
//const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc(this.state, { tsumoTile: null, ronTile: tile }));
|
|
||||||
//if (yakus.length === 0) return false; // 役がない
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private canPon(house: House, tid: TileId): boolean {
|
|
||||||
return this.handTileTypes[house].filter(t => t === $type(tid)).length === 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(tid)) &&
|
|
||||||
pattern.filter(t => hand.includes(t)).length >= 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getHouse(index: 1 | 2 | 3 | 4): House {
|
public getHouse(index: 1 | 2 | 3 | 4): House {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 1: return this.state.user1House;
|
case 1: return this.state.user1House;
|
||||||
|
@ -304,50 +379,10 @@ export class MasterGameEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private endKyoku() {
|
|
||||||
console.log('endKyoku');
|
|
||||||
const newState = MasterGameEngine.createInitialState();
|
|
||||||
newState.kyoku = this.state.kyoku + 1;
|
|
||||||
newState.points = this.state.points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ロン和了
|
|
||||||
* @param callers ロンする人
|
|
||||||
* @param callee ロンされる人
|
|
||||||
*/
|
|
||||||
private ronHora(callers: House[], callee: House) {
|
|
||||||
for (const house of callers) {
|
|
||||||
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
|
|
||||||
house: house,
|
|
||||||
handTiles: this.handTileTypes[house],
|
|
||||||
huros: this.state.huros[house],
|
|
||||||
tsumoTile: null,
|
|
||||||
ronTile: this.hoTileTypes[callee].at(-1)!,
|
|
||||||
riichi: this.state.riichis[house],
|
|
||||||
}));
|
|
||||||
const doraCount =
|
|
||||||
Common.calcOwnedDoraCount(this.handTileTypes[house], this.state.huros[house], this.doras) +
|
|
||||||
Common.calcRedDoraCount(this.state.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');
|
|
||||||
this.state.points[callee] -= point;
|
|
||||||
this.state.points[house] += point;
|
|
||||||
console.log('fans point', fans, point);
|
|
||||||
console.log('yakus', house, yakus);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.endKyoku();
|
|
||||||
}
|
|
||||||
|
|
||||||
public startTransaction() {
|
public startTransaction() {
|
||||||
const newState = structuredClone(this.state);
|
return new StateManager(this.state, (newState) => {
|
||||||
return {
|
|
||||||
state: newState,
|
|
||||||
commit: () => {
|
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
},
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public commit_nextKyoku() {
|
public commit_nextKyoku() {
|
||||||
|
@ -363,107 +398,109 @@ export class MasterGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public commit_dahai(house: House, tid: TileId, riichi = false) {
|
public commit_dahai(house: House, tid: TileId, riichi = false) {
|
||||||
const { state, commit } = this.startTransaction();
|
const tx = this.startTransaction();
|
||||||
|
|
||||||
if (this.state.turn !== house) throw new Error('Not your turn');
|
if (tx.state.turn !== house) throw new Error('Not your turn');
|
||||||
|
|
||||||
if (riichi) {
|
if (riichi) {
|
||||||
if (this.state.riichis[house]) throw new Error('Already riichi');
|
if (tx.state.riichis[house]) throw new Error('Already riichi');
|
||||||
const tempHandTiles = [...this.handTileTypes[house]];
|
const tempHandTiles = [...tx.handTileTypes[house]];
|
||||||
tempHandTiles.splice(tempHandTiles.indexOf($type(tid)), 1);
|
tempHandTiles.splice(tempHandTiles.indexOf($type(tid)), 1);
|
||||||
if (Common.getHoraTiles(tempHandTiles).length === 0) throw new Error('Not tenpai');
|
if (Common.getHoraTiles(tempHandTiles).length === 0) throw new Error('Not tenpai');
|
||||||
if (this.state.points[house] < 1000) throw new Error('Not enough points');
|
if (tx.state.points[house] < 1000) throw new Error('Not enough points');
|
||||||
}
|
}
|
||||||
|
|
||||||
const handTiles = this.state.handTiles[house];
|
const handTiles = tx.state.handTiles[house];
|
||||||
if (!handTiles.includes(tid)) throw new Error('No such tile in your hand');
|
if (!handTiles.includes(tid)) throw new Error('No such tile in your hand');
|
||||||
handTiles.splice(handTiles.indexOf(tid), 1);
|
handTiles.splice(handTiles.indexOf(tid), 1);
|
||||||
this.state.hoTiles[house].push(tid);
|
tx.state.hoTiles[house].push(tid);
|
||||||
|
|
||||||
if (this.state.riichis[house]) {
|
if (tx.state.riichis[house]) {
|
||||||
this.state.ippatsus[house] = false;
|
tx.state.ippatsus[house] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (riichi) {
|
if (riichi) {
|
||||||
this.state.riichis[house] = true;
|
tx.state.riichis[house] = true;
|
||||||
this.state.ippatsus[house] = true;
|
tx.state.ippatsus[house] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRonHouses: House[] = [];
|
const canRonHouses: House[] = [];
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e':
|
case 'e':
|
||||||
if (this.canRon('s', tid)) canRonHouses.push('s');
|
if (tx.canRon('s', tid)) canRonHouses.push('s');
|
||||||
if (this.canRon('w', tid)) canRonHouses.push('w');
|
if (tx.canRon('w', tid)) canRonHouses.push('w');
|
||||||
if (this.canRon('n', tid)) canRonHouses.push('n');
|
if (tx.canRon('n', tid)) canRonHouses.push('n');
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
if (this.canRon('e', tid)) canRonHouses.push('e');
|
if (tx.canRon('e', tid)) canRonHouses.push('e');
|
||||||
if (this.canRon('w', tid)) canRonHouses.push('w');
|
if (tx.canRon('w', tid)) canRonHouses.push('w');
|
||||||
if (this.canRon('n', tid)) canRonHouses.push('n');
|
if (tx.canRon('n', tid)) canRonHouses.push('n');
|
||||||
break;
|
break;
|
||||||
case 'w':
|
case 'w':
|
||||||
if (this.canRon('e', tid)) canRonHouses.push('e');
|
if (tx.canRon('e', tid)) canRonHouses.push('e');
|
||||||
if (this.canRon('s', tid)) canRonHouses.push('s');
|
if (tx.canRon('s', tid)) canRonHouses.push('s');
|
||||||
if (this.canRon('n', tid)) canRonHouses.push('n');
|
if (tx.canRon('n', tid)) canRonHouses.push('n');
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
if (this.canRon('e', tid)) canRonHouses.push('e');
|
if (tx.canRon('e', tid)) canRonHouses.push('e');
|
||||||
if (this.canRon('s', tid)) canRonHouses.push('s');
|
if (tx.canRon('s', tid)) canRonHouses.push('s');
|
||||||
if (this.canRon('w', tid)) canRonHouses.push('w');
|
if (tx.canRon('w', tid)) canRonHouses.push('w');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canKanHouse: House | null = null;
|
let canKanHouse: House | null = null;
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e': canKanHouse = this.canDaiminkan('s', tid) ? 's' : this.canDaiminkan('w', tid) ? 'w' : this.canDaiminkan('n', tid) ? 'n' : null; break;
|
case 'e': canKanHouse = tx.canDaiminkan('s', tid) ? 's' : tx.canDaiminkan('w', tid) ? 'w' : tx.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 's': canKanHouse = tx.canDaiminkan('e', tid) ? 'e' : tx.canDaiminkan('w', tid) ? 'w' : tx.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 'w': canKanHouse = tx.canDaiminkan('e', tid) ? 'e' : tx.canDaiminkan('s', tid) ? 's' : tx.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;
|
case 'n': canKanHouse = tx.canDaiminkan('e', tid) ? 'e' : tx.canDaiminkan('s', tid) ? 's' : tx.canDaiminkan('w', tid) ? 'w' : null; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canPonHouse: House | null = null;
|
let canPonHouse: House | null = null;
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e': canPonHouse = this.canPon('s', tid) ? 's' : this.canPon('w', tid) ? 'w' : this.canPon('n', tid) ? 'n' : null; break;
|
case 'e': canPonHouse = tx.canPon('s', tid) ? 's' : tx.canPon('w', tid) ? 'w' : tx.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 's': canPonHouse = tx.canPon('e', tid) ? 'e' : tx.canPon('w', tid) ? 'w' : tx.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 'w': canPonHouse = tx.canPon('e', tid) ? 'e' : tx.canPon('s', tid) ? 's' : tx.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;
|
case 'n': canPonHouse = tx.canPon('e', tid) ? 'e' : tx.canPon('s', tid) ? 's' : tx.canPon('w', tid) ? 'w' : null; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canCiiHouse: House | null = null;
|
let canCiiHouse: House | null = null;
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e': canCiiHouse = this.canCii('s', house, tid) ? 's' : this.canCii('w', house, tid) ? 'w' : this.canCii('n', house, tid) ? 'n' : null; break;
|
case 'e': canCiiHouse = tx.canCii('s', house, tid) ? 's' : tx.canCii('w', house, tid) ? 'w' : tx.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 's': canCiiHouse = tx.canCii('e', house, tid) ? 'e' : tx.canCii('w', house, tid) ? 'w' : tx.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 'w': canCiiHouse = tx.canCii('e', house, tid) ? 'e' : tx.canCii('s', house, tid) ? 's' : tx.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;
|
case 'n': canCiiHouse = tx.canCii('e', house, tid) ? 'e' : tx.canCii('s', house, tid) ? 's' : tx.canCii('w', house, tid) ? 'w' : null; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canRonHouses.length > 0 || canKanHouse != null || canPonHouse != null || canCiiHouse != null) {
|
if (canRonHouses.length > 0 || canKanHouse != null || canPonHouse != null || canCiiHouse != null) {
|
||||||
if (canRonHouses.length > 0) {
|
if (canRonHouses.length > 0) {
|
||||||
this.state.askings.ron = {
|
tx.state.askings.ron = {
|
||||||
callee: house,
|
callee: house,
|
||||||
callers: canRonHouses,
|
callers: canRonHouses,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (canKanHouse != null) {
|
if (canKanHouse != null) {
|
||||||
this.state.askings.kan = {
|
tx.state.askings.kan = {
|
||||||
callee: house,
|
callee: house,
|
||||||
caller: canKanHouse,
|
caller: canKanHouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (canPonHouse != null) {
|
if (canPonHouse != null) {
|
||||||
this.state.askings.pon = {
|
tx.state.askings.pon = {
|
||||||
callee: house,
|
callee: house,
|
||||||
caller: canPonHouse,
|
caller: canPonHouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (canCiiHouse != null) {
|
if (canCiiHouse != null) {
|
||||||
this.state.askings.cii = {
|
tx.state.askings.cii = {
|
||||||
callee: house,
|
callee: house,
|
||||||
caller: canCiiHouse,
|
caller: canCiiHouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.state.turn = null;
|
tx.state.turn = null;
|
||||||
this.state.nextTurnAfterAsking = Common.nextHouse(house);
|
tx.state.nextTurnAfterAsking = Common.nextHouse(house);
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asking: true as const,
|
asking: true as const,
|
||||||
canRonHouses: canRonHouses,
|
canRonHouses: canRonHouses,
|
||||||
|
@ -474,10 +511,9 @@ export class MasterGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流局
|
// 流局
|
||||||
if (this.state.tiles.length === 0) {
|
if (tx.state.tiles.length === 0) {
|
||||||
this.state.turn = null;
|
tx.state.turn = null;
|
||||||
|
tx.commit();
|
||||||
this.endKyoku();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asking: false as const,
|
asking: false as const,
|
||||||
|
@ -485,32 +521,38 @@ export class MasterGameEngine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.turn = Common.nextHouse(house);
|
tx.state.turn = Common.nextHouse(house);
|
||||||
|
|
||||||
const tsumoTile = this.tsumo();
|
const tsumoTile = tx.tsumo();
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asking: false as const,
|
asking: false as const,
|
||||||
tsumoTile: tsumoTile,
|
tsumoTile: tsumoTile,
|
||||||
next: this.state.turn,
|
next: tx.state.turn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public commit_kakan(house: House, tid: 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));
|
const tx = this.startTransaction();
|
||||||
|
|
||||||
|
const pon = tx.state.huros[house].find(h => h.type === 'pon' && $type(h.tiles[0]) === $type(tid));
|
||||||
if (pon == null) throw new Error('No such pon');
|
if (pon == null) throw new Error('No such pon');
|
||||||
this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(tid), 1);
|
tx.state.handTiles[house].splice(tx.state.handTiles[house].indexOf(tid), 1);
|
||||||
const tiles = [tid, ...pon.tiles];
|
const tiles = [tid, ...pon.tiles];
|
||||||
this.state.huros[house].push({ type: 'minkan', tiles: tiles, from: pon.from });
|
tx.state.huros[house].push({ type: 'minkan', tiles: tiles, from: pon.from });
|
||||||
|
|
||||||
this.state.ippatsus.e = false;
|
tx.state.ippatsus.e = false;
|
||||||
this.state.ippatsus.s = false;
|
tx.state.ippatsus.s = false;
|
||||||
this.state.ippatsus.w = false;
|
tx.state.ippatsus.w = false;
|
||||||
this.state.ippatsus.n = false;
|
tx.state.ippatsus.n = false;
|
||||||
|
|
||||||
this.state.activatedDorasCount++;
|
tx.state.activatedDorasCount++;
|
||||||
|
|
||||||
const rinsyan = this.tsumo();
|
const rinsyan = tx.tsumo();
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rinsyan,
|
rinsyan,
|
||||||
|
@ -520,29 +562,33 @@ export class MasterGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public commit_ankan(house: House, tid: TileId) {
|
public commit_ankan(house: House, tid: TileId) {
|
||||||
const t1 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(0);
|
const tx = this.startTransaction();
|
||||||
|
|
||||||
|
const t1 = tx.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(0);
|
||||||
if (t1 == null) throw new Error('No such tile');
|
if (t1 == null) throw new Error('No such tile');
|
||||||
const t2 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(1);
|
const t2 = tx.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(1);
|
||||||
if (t2 == null) throw new Error('No such tile');
|
if (t2 == null) throw new Error('No such tile');
|
||||||
const t3 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(2);
|
const t3 = tx.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(2);
|
||||||
if (t3 == null) throw new Error('No such tile');
|
if (t3 == null) throw new Error('No such tile');
|
||||||
const t4 = this.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(3);
|
const t4 = tx.state.handTiles[house].filter(t => $type(t) === $type(tid)).at(3);
|
||||||
if (t4 == null) throw new Error('No such tile');
|
if (t4 == null) throw new Error('No such tile');
|
||||||
this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t1), 1);
|
tx.state.handTiles[house].splice(tx.state.handTiles[house].indexOf(t1), 1);
|
||||||
this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t2), 1);
|
tx.state.handTiles[house].splice(tx.state.handTiles[house].indexOf(t2), 1);
|
||||||
this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t3), 1);
|
tx.state.handTiles[house].splice(tx.state.handTiles[house].indexOf(t3), 1);
|
||||||
this.state.handTiles[house].splice(this.state.handTiles[house].indexOf(t4), 1);
|
tx.state.handTiles[house].splice(tx.state.handTiles[house].indexOf(t4), 1);
|
||||||
const tiles = [t1, t2, t3, t4];
|
const tiles = [t1, t2, t3, t4];
|
||||||
this.state.huros[house].push({ type: 'ankan', tiles: tiles });
|
tx.state.huros[house].push({ type: 'ankan', tiles: tiles });
|
||||||
|
|
||||||
this.state.ippatsus.e = false;
|
tx.state.ippatsus.e = false;
|
||||||
this.state.ippatsus.s = false;
|
tx.state.ippatsus.s = false;
|
||||||
this.state.ippatsus.w = false;
|
tx.state.ippatsus.w = false;
|
||||||
this.state.ippatsus.n = false;
|
tx.state.ippatsus.n = false;
|
||||||
|
|
||||||
this.state.activatedDorasCount++;
|
tx.state.activatedDorasCount++;
|
||||||
|
|
||||||
const rinsyan = this.tsumo();
|
const rinsyan = tx.tsumo();
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rinsyan,
|
rinsyan,
|
||||||
|
@ -555,32 +601,35 @@ export class MasterGameEngine {
|
||||||
* @param house
|
* @param house
|
||||||
*/
|
*/
|
||||||
public commit_tsumoHora(house: House) {
|
public commit_tsumoHora(house: House) {
|
||||||
if (this.state.turn !== house) throw new Error('Not your turn');
|
const tx = this.startTransaction();
|
||||||
|
|
||||||
|
if (tx.state.turn !== house) throw new Error('Not your turn');
|
||||||
|
|
||||||
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
|
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
|
||||||
house: house,
|
house: house,
|
||||||
handTiles: this.handTileTypes[house],
|
handTiles: tx.handTileTypes[house],
|
||||||
huros: this.state.huros[house],
|
huros: tx.state.huros[house],
|
||||||
tsumoTile: this.handTileTypes[house].at(-1)!,
|
tsumoTile: tx.handTileTypes[house].at(-1)!,
|
||||||
ronTile: null,
|
ronTile: null,
|
||||||
riichi: this.state.riichis[house],
|
riichi: tx.state.riichis[house],
|
||||||
|
ippatsu: tx.state.ippatsus[house],
|
||||||
}));
|
}));
|
||||||
const doraCount =
|
const doraCount =
|
||||||
Common.calcOwnedDoraCount(this.handTileTypes[house], this.state.huros[house], this.doras) +
|
Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.state.huros[house], tx.doras) +
|
||||||
Common.calcRedDoraCount(this.state.handTiles[house], this.state.huros[house]);
|
Common.calcRedDoraCount(tx.state.handTiles[house], tx.state.huros[house]);
|
||||||
const fans = yakus.map(yaku => yaku.fan).reduce((a, b) => a + b, 0) + doraCount;
|
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, fans);
|
||||||
this.state.points.e += pointDeltas.e;
|
tx.state.points.e += pointDeltas.e;
|
||||||
this.state.points.s += pointDeltas.s;
|
tx.state.points.s += pointDeltas.s;
|
||||||
this.state.points.w += pointDeltas.w;
|
tx.state.points.w += pointDeltas.w;
|
||||||
this.state.points.n += pointDeltas.n;
|
tx.state.points.n += pointDeltas.n;
|
||||||
console.log('yakus', house, yakus);
|
console.log('yakus', house, yakus);
|
||||||
|
|
||||||
this.endKyoku();
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handTiles: this.state.handTiles[house],
|
handTiles: tx.state.handTiles[house],
|
||||||
tsumoTile: this.state.handTiles[house].at(-1)!,
|
tsumoTile: tx.state.handTiles[house].at(-1)!,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,20 +639,47 @@ export class MasterGameEngine {
|
||||||
kan: boolean;
|
kan: boolean;
|
||||||
ron: House[];
|
ron: House[];
|
||||||
}) {
|
}) {
|
||||||
if (this.state.askings.pon == null && this.state.askings.cii == null && this.state.askings.kan == null && this.state.askings.ron == null) throw new Error();
|
const tx = this.startTransaction();
|
||||||
|
|
||||||
const pon = this.state.askings.pon;
|
if (tx.state.askings.pon == null && tx.state.askings.cii == null && tx.state.askings.kan == null && tx.state.askings.ron == null) throw new Error();
|
||||||
const cii = this.state.askings.cii;
|
|
||||||
const kan = this.state.askings.kan;
|
|
||||||
const ron = this.state.askings.ron;
|
|
||||||
|
|
||||||
this.state.askings.pon = null;
|
const pon = tx.state.askings.pon;
|
||||||
this.state.askings.cii = null;
|
const cii = tx.state.askings.cii;
|
||||||
this.state.askings.kan = null;
|
const kan = tx.state.askings.kan;
|
||||||
this.state.askings.ron = null;
|
const ron = tx.state.askings.ron;
|
||||||
|
|
||||||
|
tx.state.askings.pon = null;
|
||||||
|
tx.state.askings.cii = null;
|
||||||
|
tx.state.askings.kan = null;
|
||||||
|
tx.state.askings.ron = null;
|
||||||
|
|
||||||
if (ron != null && answers.ron.length > 0) {
|
if (ron != null && answers.ron.length > 0) {
|
||||||
this.ronHora(answers.ron, ron.callee);
|
const callers = answers.ron;
|
||||||
|
const callee = ron.callee;
|
||||||
|
|
||||||
|
for (const house of callers) {
|
||||||
|
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
|
||||||
|
house: house,
|
||||||
|
handTiles: tx.handTileTypes[house],
|
||||||
|
huros: tx.state.huros[house],
|
||||||
|
tsumoTile: null,
|
||||||
|
ronTile: tx.hoTileTypes[callee].at(-1)!,
|
||||||
|
riichi: tx.state.riichis[house],
|
||||||
|
ippatsu: tx.state.ippatsus[house],
|
||||||
|
}));
|
||||||
|
const doraCount =
|
||||||
|
Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.state.huros[house], tx.doras) +
|
||||||
|
Common.calcRedDoraCount(tx.state.handTiles[house], tx.state.huros[house]);
|
||||||
|
const fans = yakus.map(yaku => yaku.fan).reduce((a, b) => a + b, 0) + doraCount;
|
||||||
|
const point = Common.fanToPoint(fans, house === 'e');
|
||||||
|
tx.state.points[callee] -= point;
|
||||||
|
tx.state.points[house] += point;
|
||||||
|
console.log('fans point', fans, point);
|
||||||
|
console.log('yakus', house, yakus);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ronned' as const,
|
type: 'ronned' as const,
|
||||||
callers: ron.callers,
|
callers: ron.callers,
|
||||||
|
@ -613,31 +689,33 @@ export class MasterGameEngine {
|
||||||
} else if (kan != null && answers.kan) {
|
} else if (kan != null && answers.kan) {
|
||||||
// 大明槓
|
// 大明槓
|
||||||
|
|
||||||
const tile = this.state.hoTiles[kan.callee].pop()!;
|
const tile = tx.state.hoTiles[kan.callee].pop()!;
|
||||||
const t1 = this.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(0);
|
const t1 = tx.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(0);
|
||||||
if (t1 == null) throw new Error('No such tile');
|
if (t1 == null) throw new Error('No such tile');
|
||||||
const t2 = this.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(1);
|
const t2 = tx.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(1);
|
||||||
if (t2 == null) throw new Error('No such tile');
|
if (t2 == null) throw new Error('No such tile');
|
||||||
const t3 = this.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(2);
|
const t3 = tx.state.handTiles[kan.caller].filter(t => $type(t) === $type(tile)).at(2);
|
||||||
if (t3 == null) throw new Error('No such tile');
|
if (t3 == null) throw new Error('No such tile');
|
||||||
|
|
||||||
this.state.handTiles[kan.caller].splice(this.state.handTiles[kan.caller].indexOf(t1), 1);
|
tx.state.handTiles[kan.caller].splice(tx.state.handTiles[kan.caller].indexOf(t1), 1);
|
||||||
this.state.handTiles[kan.caller].splice(this.state.handTiles[kan.caller].indexOf(t2), 1);
|
tx.state.handTiles[kan.caller].splice(tx.state.handTiles[kan.caller].indexOf(t2), 1);
|
||||||
this.state.handTiles[kan.caller].splice(this.state.handTiles[kan.caller].indexOf(t3), 1);
|
tx.state.handTiles[kan.caller].splice(tx.state.handTiles[kan.caller].indexOf(t3), 1);
|
||||||
|
|
||||||
const tiles = [tile, t1, t2, t3];
|
const tiles = [tile, t1, t2, t3];
|
||||||
this.state.huros[kan.caller].push({ type: 'minkan', tiles: tiles, from: kan.callee });
|
tx.state.huros[kan.caller].push({ type: 'minkan', tiles: tiles, from: kan.callee });
|
||||||
|
|
||||||
this.state.ippatsus.e = false;
|
tx.state.ippatsus.e = false;
|
||||||
this.state.ippatsus.s = false;
|
tx.state.ippatsus.s = false;
|
||||||
this.state.ippatsus.w = false;
|
tx.state.ippatsus.w = false;
|
||||||
this.state.ippatsus.n = false;
|
tx.state.ippatsus.n = false;
|
||||||
|
|
||||||
this.state.activatedDorasCount++;
|
tx.state.activatedDorasCount++;
|
||||||
|
|
||||||
const rinsyan = this.tsumo();
|
const rinsyan = tx.tsumo();
|
||||||
|
|
||||||
this.state.turn = kan.caller;
|
tx.state.turn = kan.caller;
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'kanned' as const,
|
type: 'kanned' as const,
|
||||||
|
@ -645,37 +723,39 @@ export class MasterGameEngine {
|
||||||
callee: kan.callee,
|
callee: kan.callee,
|
||||||
tiles: tiles,
|
tiles: tiles,
|
||||||
rinsyan,
|
rinsyan,
|
||||||
turn: this.state.turn,
|
turn: tx.state.turn,
|
||||||
};
|
};
|
||||||
} else if (pon != null && answers.pon) {
|
} else if (pon != null && answers.pon) {
|
||||||
const tile = this.state.hoTiles[pon.callee].pop()!;
|
const tile = tx.state.hoTiles[pon.callee].pop()!;
|
||||||
const t1 = this.state.handTiles[pon.caller].filter(t => $type(t) === $type(tile)).at(0);
|
const t1 = tx.state.handTiles[pon.caller].filter(t => $type(t) === $type(tile)).at(0);
|
||||||
if (t1 == null) throw new Error('No such tile');
|
if (t1 == null) throw new Error('No such tile');
|
||||||
const t2 = this.state.handTiles[pon.caller].filter(t => $type(t) === $type(tile)).at(1);
|
const t2 = tx.state.handTiles[pon.caller].filter(t => $type(t) === $type(tile)).at(1);
|
||||||
if (t2 == null) throw new Error('No such tile');
|
if (t2 == null) throw new Error('No such tile');
|
||||||
|
|
||||||
this.state.handTiles[pon.caller].splice(this.state.handTiles[pon.caller].indexOf(t1), 1);
|
tx.state.handTiles[pon.caller].splice(tx.state.handTiles[pon.caller].indexOf(t1), 1);
|
||||||
this.state.handTiles[pon.caller].splice(this.state.handTiles[pon.caller].indexOf(t2), 1);
|
tx.state.handTiles[pon.caller].splice(tx.state.handTiles[pon.caller].indexOf(t2), 1);
|
||||||
|
|
||||||
const tiles = [tile, t1, t2];
|
const tiles = [tile, t1, t2];
|
||||||
this.state.huros[pon.caller].push({ type: 'pon', tiles: tiles, from: pon.callee });
|
tx.state.huros[pon.caller].push({ type: 'pon', tiles: tiles, from: pon.callee });
|
||||||
|
|
||||||
this.state.ippatsus.e = false;
|
tx.state.ippatsus.e = false;
|
||||||
this.state.ippatsus.s = false;
|
tx.state.ippatsus.s = false;
|
||||||
this.state.ippatsus.w = false;
|
tx.state.ippatsus.w = false;
|
||||||
this.state.ippatsus.n = false;
|
tx.state.ippatsus.n = false;
|
||||||
|
|
||||||
this.state.turn = pon.caller;
|
tx.state.turn = pon.caller;
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ponned' as const,
|
type: 'ponned' as const,
|
||||||
caller: pon.caller,
|
caller: pon.caller,
|
||||||
callee: pon.callee,
|
callee: pon.callee,
|
||||||
tiles: tiles,
|
tiles: tiles,
|
||||||
turn: this.state.turn,
|
turn: tx.state.turn,
|
||||||
};
|
};
|
||||||
} else if (cii != null && answers.cii) {
|
} else if (cii != null && answers.cii) {
|
||||||
const tile = this.state.hoTiles[cii.callee].pop()!;
|
const tile = tx.state.hoTiles[cii.callee].pop()!;
|
||||||
let tiles: [TileId, TileId, TileId];
|
let tiles: [TileId, TileId, TileId];
|
||||||
|
|
||||||
switch (answers.cii) {
|
switch (answers.cii) {
|
||||||
|
@ -684,12 +764,12 @@ export class MasterGameEngine {
|
||||||
if (a == null) throw new Error();
|
if (a == null) throw new Error();
|
||||||
const b = Common.NEXT_TILE_FOR_SHUNTSU[a];
|
const b = Common.NEXT_TILE_FOR_SHUNTSU[a];
|
||||||
if (b == null) throw new Error();
|
if (b == null) throw new Error();
|
||||||
const aTile = this.state.handTiles[cii.caller].find(t => $type(t) === a);
|
const aTile = tx.state.handTiles[cii.caller].find(t => $type(t) === a);
|
||||||
if (aTile == null) throw new Error();
|
if (aTile == null) throw new Error();
|
||||||
const bTile = this.state.handTiles[cii.caller].find(t => $type(t) === b);
|
const bTile = tx.state.handTiles[cii.caller].find(t => $type(t) === b);
|
||||||
if (bTile == null) throw new Error();
|
if (bTile == null) throw new Error();
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(aTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(aTile), 1);
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(bTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(bTile), 1);
|
||||||
tiles = [tile, aTile, bTile];
|
tiles = [tile, aTile, bTile];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -698,12 +778,12 @@ export class MasterGameEngine {
|
||||||
if (a == null) throw new Error();
|
if (a == null) throw new Error();
|
||||||
const b = Common.NEXT_TILE_FOR_SHUNTSU[$type(tile)];
|
const b = Common.NEXT_TILE_FOR_SHUNTSU[$type(tile)];
|
||||||
if (b == null) throw new Error();
|
if (b == null) throw new Error();
|
||||||
const aTile = this.state.handTiles[cii.caller].find(t => $type(t) === a);
|
const aTile = tx.state.handTiles[cii.caller].find(t => $type(t) === a);
|
||||||
if (aTile == null) throw new Error();
|
if (aTile == null) throw new Error();
|
||||||
const bTile = this.state.handTiles[cii.caller].find(t => $type(t) === b);
|
const bTile = tx.state.handTiles[cii.caller].find(t => $type(t) === b);
|
||||||
if (bTile == null) throw new Error();
|
if (bTile == null) throw new Error();
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(aTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(aTile), 1);
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(bTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(bTile), 1);
|
||||||
tiles = [aTile, tile, bTile];
|
tiles = [aTile, tile, bTile];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -712,55 +792,59 @@ export class MasterGameEngine {
|
||||||
if (a == null) throw new Error();
|
if (a == null) throw new Error();
|
||||||
const b = Common.PREV_TILE_FOR_SHUNTSU[a];
|
const b = Common.PREV_TILE_FOR_SHUNTSU[a];
|
||||||
if (b == null) throw new Error();
|
if (b == null) throw new Error();
|
||||||
const aTile = this.state.handTiles[cii.caller].find(t => $type(t) === a);
|
const aTile = tx.state.handTiles[cii.caller].find(t => $type(t) === a);
|
||||||
if (aTile == null) throw new Error();
|
if (aTile == null) throw new Error();
|
||||||
const bTile = this.state.handTiles[cii.caller].find(t => $type(t) === b);
|
const bTile = tx.state.handTiles[cii.caller].find(t => $type(t) === b);
|
||||||
if (bTile == null) throw new Error();
|
if (bTile == null) throw new Error();
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(aTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(aTile), 1);
|
||||||
this.state.handTiles[cii.caller].splice(this.state.handTiles[cii.caller].indexOf(bTile), 1);
|
tx.state.handTiles[cii.caller].splice(tx.state.handTiles[cii.caller].indexOf(bTile), 1);
|
||||||
tiles = [bTile, aTile, tile];
|
tiles = [bTile, aTile, tile];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.huros[cii.caller].push({ type: 'cii', tiles: tiles, from: cii.callee });
|
tx.state.huros[cii.caller].push({ type: 'cii', tiles: tiles, from: cii.callee });
|
||||||
|
|
||||||
this.state.ippatsus.e = false;
|
tx.state.ippatsus.e = false;
|
||||||
this.state.ippatsus.s = false;
|
tx.state.ippatsus.s = false;
|
||||||
this.state.ippatsus.w = false;
|
tx.state.ippatsus.w = false;
|
||||||
this.state.ippatsus.n = false;
|
tx.state.ippatsus.n = false;
|
||||||
|
|
||||||
this.state.turn = cii.caller;
|
tx.state.turn = cii.caller;
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ciied' as const,
|
type: 'ciied' as const,
|
||||||
caller: cii.caller,
|
caller: cii.caller,
|
||||||
callee: cii.callee,
|
callee: cii.callee,
|
||||||
tiles: tiles,
|
tiles: tiles,
|
||||||
turn: this.state.turn,
|
turn: tx.state.turn,
|
||||||
};
|
};
|
||||||
} else if (this.state.tiles.length === 0) {
|
} else if (tx.state.tiles.length === 0) {
|
||||||
// 流局
|
// 流局
|
||||||
|
|
||||||
this.state.turn = null;
|
tx.state.turn = null;
|
||||||
this.state.nextTurnAfterAsking = null;
|
tx.state.nextTurnAfterAsking = null;
|
||||||
|
|
||||||
this.endKyoku();
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'ryuukyoku' as const,
|
type: 'ryuukyoku' as const,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.state.turn = this.state.nextTurnAfterAsking!;
|
tx.state.turn = tx.state.nextTurnAfterAsking!;
|
||||||
this.state.nextTurnAfterAsking = null;
|
tx.state.nextTurnAfterAsking = null;
|
||||||
|
|
||||||
const tile = this.tsumo();
|
const tile = tx.tsumo();
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'tsumo' as const,
|
type: 'tsumo' as const,
|
||||||
house: this.state.turn,
|
house: tx.state.turn,
|
||||||
tile,
|
tile,
|
||||||
turn: this.state.turn,
|
turn: tx.state.turn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -802,6 +886,12 @@ export class MasterGameEngine {
|
||||||
w: this.state.riichis.w,
|
w: this.state.riichis.w,
|
||||||
n: this.state.riichis.n,
|
n: this.state.riichis.n,
|
||||||
},
|
},
|
||||||
|
ippatsus: {
|
||||||
|
e: this.state.ippatsus.e,
|
||||||
|
s: this.state.ippatsus.s,
|
||||||
|
w: this.state.ippatsus.w,
|
||||||
|
n: this.state.ippatsus.n,
|
||||||
|
},
|
||||||
points: {
|
points: {
|
||||||
e: this.state.points.e,
|
e: this.state.points.e,
|
||||||
s: this.state.points.s,
|
s: this.state.points.s,
|
||||||
|
|
|
@ -58,6 +58,12 @@ export type PlayerState = {
|
||||||
w: boolean;
|
w: boolean;
|
||||||
n: boolean;
|
n: boolean;
|
||||||
};
|
};
|
||||||
|
ippatsus: {
|
||||||
|
e: boolean;
|
||||||
|
s: boolean;
|
||||||
|
w: boolean;
|
||||||
|
n: boolean;
|
||||||
|
};
|
||||||
points: {
|
points: {
|
||||||
e: number;
|
e: number;
|
||||||
s: number;
|
s: number;
|
||||||
|
@ -237,6 +243,7 @@ export class PlayerGameEngine {
|
||||||
tsumoTile: $type(tsumoTile),
|
tsumoTile: $type(tsumoTile),
|
||||||
ronTile: null,
|
ronTile: null,
|
||||||
riichi: this.state.riichis[house],
|
riichi: this.state.riichis[house],
|
||||||
|
ippatsu: this.state.ippatsus[house],
|
||||||
}));
|
}));
|
||||||
const doraCount =
|
const doraCount =
|
||||||
Common.calcOwnedDoraCount(handTiles.map(id => $type(id)), this.state.huros[house], this.doras) +
|
Common.calcOwnedDoraCount(handTiles.map(id => $type(id)), this.state.huros[house], this.doras) +
|
||||||
|
@ -289,6 +296,7 @@ export class PlayerGameEngine {
|
||||||
tsumoTile: null,
|
tsumoTile: null,
|
||||||
ronTile: $type(this.state.hoTiles[callee].at(-1)!),
|
ronTile: $type(this.state.hoTiles[callee].at(-1)!),
|
||||||
riichi: this.state.riichis[house],
|
riichi: this.state.riichis[house],
|
||||||
|
ippatsu: this.state.ippatsus[house],
|
||||||
}));
|
}));
|
||||||
const doraCount =
|
const doraCount =
|
||||||
Common.calcOwnedDoraCount(handTiles[house].map(id => $type(id)), this.state.huros[house], this.doras) +
|
Common.calcOwnedDoraCount(handTiles[house].map(id => $type(id)), this.state.huros[house], this.doras) +
|
||||||
|
|
Loading…
Reference in a new issue