wip
This commit is contained in:
7
packages/misskey-mahjong/.eslintignore
Normal file
7
packages/misskey-mahjong/.eslintignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
/built
|
||||
/coverage
|
||||
/.eslintrc.js
|
||||
/jest.config.ts
|
||||
/test
|
||||
/test-d
|
||||
10
packages/misskey-mahjong/.eslintrc.cjs
Normal file
10
packages/misskey-mahjong/.eslintrc.cjs
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
};
|
||||
31
packages/misskey-mahjong/build.js
Normal file
31
packages/misskey-mahjong/build.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { build } from "esbuild";
|
||||
import { globSync } from "glob";
|
||||
|
||||
const entryPoints = globSync("./src/**/**.{ts,tsx}");
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints,
|
||||
minify: true,
|
||||
outdir: "./built/esm",
|
||||
target: "es2022",
|
||||
platform: "browser",
|
||||
format: "esm",
|
||||
};
|
||||
|
||||
if (process.env.WATCH === "true") {
|
||||
options.watch = {
|
||||
onRebuild(error, result) {
|
||||
if (error) {
|
||||
console.error("watch build failed:", error);
|
||||
} else {
|
||||
console.log("watch build succeeded:", result);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
build(options).catch((err) => {
|
||||
process.stderr.write(err.stderr);
|
||||
process.exit(1);
|
||||
});
|
||||
43
packages/misskey-mahjong/package.json
Normal file
43
packages/misskey-mahjong/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-mahjong",
|
||||
"version": "0.0.1",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./built/esm/index.js",
|
||||
"types": "./built/dts/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./built/esm/*",
|
||||
"types": "./built/dts/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./build.js",
|
||||
"build:tsc": "npm run tsc",
|
||||
"tsc": "npm run tsc-esm && npm run tsc-dts",
|
||||
"tsc-esm": "tsc --outDir built/esm",
|
||||
"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
|
||||
"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
|
||||
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||
"@types/node": "20.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.18.1",
|
||||
"@typescript-eslint/parser": "6.18.1",
|
||||
"eslint": "8.56.0",
|
||||
"nodemon": "3.0.2",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
],
|
||||
"dependencies": {
|
||||
"crc-32": "1.2.2",
|
||||
"esbuild": "0.19.11",
|
||||
"glob": "10.3.10"
|
||||
}
|
||||
}
|
||||
523
packages/misskey-mahjong/src/engine.ts
Normal file
523
packages/misskey-mahjong/src/engine.ts
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import CRC32 from 'crc-32';
|
||||
|
||||
export const TILE_TYPES = [
|
||||
'bamboo1',
|
||||
'bamboo2',
|
||||
'bamboo3',
|
||||
'bamboo4',
|
||||
'bamboo5',
|
||||
'bamboo6',
|
||||
'bamboo7',
|
||||
'bamboo8',
|
||||
'bamboo9',
|
||||
'character1',
|
||||
'character2',
|
||||
'character3',
|
||||
'character4',
|
||||
'character5',
|
||||
'character6',
|
||||
'character7',
|
||||
'character8',
|
||||
'character9',
|
||||
'circle1',
|
||||
'circle2',
|
||||
'circle3',
|
||||
'circle4',
|
||||
'circle5',
|
||||
'circle6',
|
||||
'circle7',
|
||||
'circle8',
|
||||
'circle9',
|
||||
'wind-east',
|
||||
'wind-south',
|
||||
'wind-west',
|
||||
'wind-north',
|
||||
'dragon-red',
|
||||
'dragon-green',
|
||||
'dragon-white',
|
||||
] as const;
|
||||
|
||||
export type Tile = typeof TILE_TYPES[number];
|
||||
|
||||
export function isTile(tile: string): tile is Tile {
|
||||
return TILE_TYPES.includes(tile as Tile);
|
||||
}
|
||||
|
||||
export type House = 'e' | 's' | 'w' | 'n';
|
||||
|
||||
export type MasterState = {
|
||||
user1House: House;
|
||||
user2House: House;
|
||||
user3House: House;
|
||||
user4House: House;
|
||||
tiles: Tile[];
|
||||
eHandTiles: Tile[];
|
||||
sHandTiles: Tile[];
|
||||
wHandTiles: Tile[];
|
||||
nHandTiles: Tile[];
|
||||
eHoTiles: Tile[];
|
||||
sHoTiles: Tile[];
|
||||
wHoTiles: Tile[];
|
||||
nHoTiles: Tile[];
|
||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
eRiichi: boolean;
|
||||
sRiichi: boolean;
|
||||
wRiichi: boolean;
|
||||
nRiichi: boolean;
|
||||
ePoints: number;
|
||||
sPoints: number;
|
||||
wPoints: number;
|
||||
nPoints: number;
|
||||
turn: House | null;
|
||||
ponAsking: {
|
||||
source: House;
|
||||
target: House;
|
||||
} | null;
|
||||
ciiAsking: {
|
||||
source: House;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export class Common {
|
||||
public static nextHouse(house: House): House {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
return 's';
|
||||
case 's':
|
||||
return 'w';
|
||||
case 'w':
|
||||
return 'n';
|
||||
case 'n':
|
||||
return 'e';
|
||||
}
|
||||
}
|
||||
|
||||
public static checkYaku(tiles: Tile[]) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class MasterGameEngine {
|
||||
public state: MasterState;
|
||||
|
||||
constructor(state: MasterState) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public static createInitialState(): MasterState {
|
||||
const tiles = TILE_TYPES.slice();
|
||||
tiles.sort(() => Math.random() - 0.5);
|
||||
|
||||
const eHandTiles = tiles.splice(0, 14);
|
||||
const sHandTiles = tiles.splice(0, 13);
|
||||
const wHandTiles = tiles.splice(0, 13);
|
||||
const nHandTiles = tiles.splice(0, 13);
|
||||
|
||||
return {
|
||||
user1House: 'e',
|
||||
user2House: 's',
|
||||
user3House: 'w',
|
||||
user4House: 'n',
|
||||
tiles,
|
||||
eHandTiles,
|
||||
sHandTiles,
|
||||
wHandTiles,
|
||||
nHandTiles,
|
||||
eHoTiles: [],
|
||||
sHoTiles: [],
|
||||
wHoTiles: [],
|
||||
nHoTiles: [],
|
||||
ePonnedTiles: [],
|
||||
sPonnedTiles: [],
|
||||
wPonnedTiles: [],
|
||||
nPonnedTiles: [],
|
||||
eCiiedTiles: [],
|
||||
sCiiedTiles: [],
|
||||
wCiiedTiles: [],
|
||||
nCiiedTiles: [],
|
||||
eRiichi: false,
|
||||
sRiichi: false,
|
||||
wRiichi: false,
|
||||
nRiichi: false,
|
||||
ePoints: 25000,
|
||||
sPoints: 25000,
|
||||
wPoints: 25000,
|
||||
nPoints: 25000,
|
||||
turn: 'e',
|
||||
ponAsking: null,
|
||||
ciiAsking: null,
|
||||
};
|
||||
}
|
||||
|
||||
private ツモ(): Tile {
|
||||
const tile = this.state.tiles.pop();
|
||||
switch (this.state.turn) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
public op_dahai(house: House, tile: Tile) {
|
||||
if (this.state.turn !== house) throw new Error('Not your turn');
|
||||
|
||||
switch (house) {
|
||||
case 'e':
|
||||
if (!this.state.eHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.eHandTiles.splice(this.state.eHandTiles.indexOf(tile), 1);
|
||||
this.state.eHoTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
if (!this.state.sHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.sHandTiles.splice(this.state.sHandTiles.indexOf(tile), 1);
|
||||
this.state.sHoTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
if (!this.state.wHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.wHandTiles.splice(this.state.wHandTiles.indexOf(tile), 1);
|
||||
this.state.wHoTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
if (!this.state.nHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.nHandTiles.splice(this.state.nHandTiles.indexOf(tile), 1);
|
||||
this.state.nHoTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
|
||||
let canPonHouse: House | null = null;
|
||||
if (house === 'e') {
|
||||
canPonHouse = this.canPon('s', tile) ? 's' : this.canPon('w', tile) ? 'w' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 's') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('w', tile) ? 'w' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 'w') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('s', tile) ? 's' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 'n') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('s', tile) ? 's' : this.canPon('w', tile) ? 'w' : null;
|
||||
}
|
||||
|
||||
// TODO
|
||||
//let canCii: boolean = false;
|
||||
//if (house === 'e') {
|
||||
// canCii = this.state.sHandTiles...
|
||||
//} else if (house === 's') {
|
||||
// canCii = this.state.wHandTiles...
|
||||
//} else if (house === 'w') {
|
||||
// canCii = this.state.nHandTiles...
|
||||
//} else if (house === 'n') {
|
||||
// canCii = this.state.eHandTiles...
|
||||
//}
|
||||
|
||||
if (canPonHouse) {
|
||||
this.state.ponAsking = {
|
||||
source: house,
|
||||
target: canPonHouse,
|
||||
};
|
||||
return {
|
||||
canPonHouse: canPonHouse,
|
||||
};
|
||||
}
|
||||
|
||||
this.state.turn = Common.nextHouse(house);
|
||||
|
||||
const tsumoTile = this.ツモ();
|
||||
|
||||
return {
|
||||
tsumo: tsumoTile,
|
||||
};
|
||||
}
|
||||
|
||||
public op_pon(house: House) {
|
||||
if (this.state.ponAsking == null) throw new Error('No one is asking for pon');
|
||||
if (this.state.ponAsking.target !== house) throw new Error('Not you');
|
||||
|
||||
const source = this.state.ponAsking.source;
|
||||
const target = this.state.ponAsking.target;
|
||||
this.state.ponAsking = null;
|
||||
|
||||
let tile: Tile;
|
||||
|
||||
switch (source) {
|
||||
case 'e':
|
||||
tile = this.state.eHoTiles.pop();
|
||||
break;
|
||||
case 's':
|
||||
tile = this.state.sHoTiles.pop();
|
||||
break;
|
||||
case 'w':
|
||||
tile = this.state.wHoTiles.pop();
|
||||
break;
|
||||
case 'n':
|
||||
tile = this.state.nHoTiles.pop();
|
||||
break;
|
||||
default: throw new Error('Invalid source');
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case 'e':
|
||||
this.state.ePonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 's':
|
||||
this.state.sPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
}
|
||||
|
||||
this.state.turn = target;
|
||||
}
|
||||
|
||||
public op_noOnePon() {
|
||||
if (this.state.ponAsking == null) throw new Error('No one is asking for pon');
|
||||
|
||||
this.state.ponAsking = null;
|
||||
this.state.turn = Common.nextHouse(this.state.turn);
|
||||
|
||||
const tile = this.ツモ();
|
||||
|
||||
return {
|
||||
house: this.state.turn,
|
||||
tile,
|
||||
};
|
||||
}
|
||||
|
||||
private canPon(house: House, tile: Tile): boolean {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
return this.state.eHandTiles.filter(t => t === tile).length === 2;
|
||||
case 's':
|
||||
return this.state.sHandTiles.filter(t => t === tile).length === 2;
|
||||
case 'w':
|
||||
return this.state.wHandTiles.filter(t => t === tile).length === 2;
|
||||
case 'n':
|
||||
return this.state.nHandTiles.filter(t => t === tile).length === 2;
|
||||
}
|
||||
}
|
||||
|
||||
public calcCrc32ForUser1(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser2(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser3(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser4(): number {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
export type PlayerState = {
|
||||
user1House: House;
|
||||
user2House: House;
|
||||
user3House: House;
|
||||
user4House: House;
|
||||
tilesCount: number;
|
||||
eHandTiles: Tile[] | null[];
|
||||
sHandTiles: Tile[] | null[];
|
||||
wHandTiles: Tile[] | null[];
|
||||
nHandTiles: Tile[] | null[];
|
||||
eHoTiles: Tile[];
|
||||
sHoTiles: Tile[];
|
||||
wHoTiles: Tile[];
|
||||
nHoTiles: Tile[];
|
||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
eRiichi: boolean;
|
||||
sRiichi: boolean;
|
||||
wRiichi: boolean;
|
||||
nRiichi: boolean;
|
||||
ePoints: number;
|
||||
sPoints: number;
|
||||
wPoints: number;
|
||||
nPoints: number;
|
||||
latestDahaiedTile: Tile | null;
|
||||
turn: House | null;
|
||||
};
|
||||
|
||||
export class PlayerGameEngine {
|
||||
/**
|
||||
* このエラーが発生したときはdesyncが疑われる
|
||||
*/
|
||||
public static InvalidOperationError = class extends Error {};
|
||||
|
||||
private myUserNumber: 1 | 2 | 3 | 4;
|
||||
public state: PlayerState;
|
||||
|
||||
constructor(myUserNumber: PlayerGameEngine['myUserNumber'], state: PlayerState) {
|
||||
this.myUserNumber = myUserNumber;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public get myHouse(): House {
|
||||
switch (this.myUserNumber) {
|
||||
case 1: return this.state.user1House;
|
||||
case 2: return this.state.user2House;
|
||||
case 3: return this.state.user3House;
|
||||
case 4: return this.state.user4House;
|
||||
}
|
||||
}
|
||||
|
||||
public get myHandTiles(): Tile[] {
|
||||
switch (this.myHouse) {
|
||||
case 'e': return this.state.eHandTiles as Tile[];
|
||||
case 's': return this.state.sHandTiles as Tile[];
|
||||
case 'w': return this.state.wHandTiles as Tile[];
|
||||
case 'n': return this.state.nHandTiles as Tile[];
|
||||
}
|
||||
}
|
||||
|
||||
public get myHoTiles(): Tile[] {
|
||||
switch (this.myHouse) {
|
||||
case 'e': return this.state.eHoTiles;
|
||||
case 's': return this.state.sHoTiles;
|
||||
case 'w': return this.state.wHoTiles;
|
||||
case 'n': return this.state.nHoTiles;
|
||||
}
|
||||
}
|
||||
|
||||
public op_tsumo(house: House, tile: Tile) {
|
||||
if (house === this.myHouse) {
|
||||
this.myHandTiles.push(tile);
|
||||
} else {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.push(null);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.push(null);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.push(null);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.push(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public op_dahai(house: House, tile: Tile) {
|
||||
if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError();
|
||||
|
||||
if (house === this.myHouse) {
|
||||
this.myHandTiles.splice(this.myHandTiles.indexOf(tile), 1);
|
||||
this.myHoTiles.push(tile);
|
||||
} else {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.pop();
|
||||
this.state.eHoTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.pop();
|
||||
this.state.sHoTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.pop();
|
||||
this.state.wHoTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.pop();
|
||||
this.state.nHoTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (house === this.myHouse) {
|
||||
this.state.turn = null;
|
||||
} else {
|
||||
const canPon = this.myHandTiles.filter(t => t === tile).length === 2;
|
||||
|
||||
// TODO: canCii
|
||||
|
||||
return {
|
||||
canPon,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public op_pon(source: House, target: House) {
|
||||
let tile: Tile;
|
||||
|
||||
switch (source) {
|
||||
case 'e': {
|
||||
const lastTile = this.state.eHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
const lastTile = this.state.sHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 'w': {
|
||||
const lastTile = this.state.wHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
const lastTile = this.state.nHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Invalid source');
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case 'e':
|
||||
this.state.ePonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 's':
|
||||
this.state.sPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
}
|
||||
|
||||
this.state.turn = target;
|
||||
}
|
||||
}
|
||||
7
packages/misskey-mahjong/src/index.ts
Normal file
7
packages/misskey-mahjong/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export * as Engine from './engine.js';
|
||||
export * as Serializer from './serializer.js';
|
||||
114
packages/misskey-mahjong/src/serializer.ts
Normal file
114
packages/misskey-mahjong/src/serializer.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Tile } from './engine.js';
|
||||
|
||||
export type Log = {
|
||||
time: number;
|
||||
player: 1 | 2 | 3 | 4;
|
||||
operation: 'dahai';
|
||||
tile: string;
|
||||
};
|
||||
|
||||
export type SerializedLog = number[];
|
||||
|
||||
export const TILE_MAP: Record<Tile, number> = {
|
||||
'bamboo1': 1,
|
||||
'bamboo2': 2,
|
||||
'bamboo3': 3,
|
||||
'bamboo4': 4,
|
||||
'bamboo5': 5,
|
||||
'bamboo6': 6,
|
||||
'bamboo7': 7,
|
||||
'bamboo8': 8,
|
||||
'bamboo9': 9,
|
||||
'character1': 10,
|
||||
'character2': 11,
|
||||
'character3': 12,
|
||||
'character4': 13,
|
||||
'character5': 14,
|
||||
'character6': 15,
|
||||
'character7': 16,
|
||||
'character8': 17,
|
||||
'character9': 18,
|
||||
'circle1': 19,
|
||||
'circle2': 20,
|
||||
'circle3': 21,
|
||||
'circle4': 22,
|
||||
'circle5': 23,
|
||||
'circle6': 24,
|
||||
'circle7': 25,
|
||||
'circle8': 26,
|
||||
'circle9': 27,
|
||||
'wind-east': 28,
|
||||
'wind-south': 29,
|
||||
'wind-west': 30,
|
||||
'wind-north': 31,
|
||||
'dragon-red': 32,
|
||||
'dragon-green': 33,
|
||||
'dragon-white': 34,
|
||||
};
|
||||
|
||||
export function serializeTile(tile: Tile): number {
|
||||
return TILE_MAP[tile];
|
||||
}
|
||||
|
||||
export function deserializeTile(tile: number): Tile {
|
||||
return Object.keys(TILE_MAP).find(key => TILE_MAP[key as Tile] === tile) as Tile;
|
||||
}
|
||||
|
||||
export function serializeLogs(logs: Log[]) {
|
||||
const _logs: number[][] = [];
|
||||
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
const log = logs[i];
|
||||
const timeDelta = i === 0 ? log.time : log.time - logs[i - 1].time;
|
||||
|
||||
switch (log.operation) {
|
||||
case 'dahai':
|
||||
_logs.push([timeDelta, log.player, 1, serializeTile(log.tile)]);
|
||||
break;
|
||||
//case 'surrender':
|
||||
// _logs.push([timeDelta, log.player, 1]);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
return _logs;
|
||||
}
|
||||
|
||||
export function deserializeLogs(logs: SerializedLog[]) {
|
||||
const _logs: Log[] = [];
|
||||
|
||||
let time = 0;
|
||||
|
||||
for (const log of logs) {
|
||||
const timeDelta = log[0];
|
||||
time += timeDelta;
|
||||
|
||||
const player = log[1];
|
||||
const operation = log[2];
|
||||
|
||||
switch (operation) {
|
||||
case 1:
|
||||
_logs.push({
|
||||
time,
|
||||
player: player,
|
||||
operation: 'dahai',
|
||||
tile: log[3],
|
||||
});
|
||||
break;
|
||||
//case 1:
|
||||
// _logs.push({
|
||||
// time,
|
||||
// player: player === 1,
|
||||
// operation: 'surrender',
|
||||
// });
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
return _logs;
|
||||
}
|
||||
33
packages/misskey-mahjong/tsconfig.json
Normal file
33
packages/misskey-mahjong/tsconfig.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./built/",
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitReturns": true,
|
||||
"esModuleInterop": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user