Commit 2c6d6fc6 authored by nanahira's avatar nanahira

add ydke and update doc

parent 439c6df1
...@@ -15,6 +15,14 @@ const anotherDeck = YGOProDeck.fromEncodedString(code); // decode it back ...@@ -15,6 +15,14 @@ const anotherDeck = YGOProDeck.fromEncodedString(code); // decode it back
const ydk = deck.toYdkString(); // YDK format const ydk = deck.toYdkString(); // YDK format
const yetAnotherDeck = YGOProDeck.fromYdkString(ydk); // decode it back const yetAnotherDeck = YGOProDeck.fromYdkString(ydk); // decode it back
const ydke = deck.toYdkeURL();
const yetAnotherDeck2 = YGOProDeck.fromYdkeURL(ydke); // decode it back
const ygomobileDeckUrl = deck.toYGOMobileDeckURL(); // YGOMobile Deck URL
const yetAnotherDeck3 = YGOProDeck.fromYGOMobileDeckURL(ygomobileDeckUrl); // decode it back
const updateDeckPayload = deck.toUpdateDeckPayload(); // payload for MSG_UPDATE_DECK
``` ```
......
import { fromBase64Url, toBase64Url } from './src/base64'; import { fromBase64Url, toBase64Url } from './src/base64';
import { BufferWriter, countItems } from './src/utils'; import { BufferWriter, countItems } from './src/utils';
import { fromYGOMobileDeckUri, toYGOMobileDeckUri } from './src/ygom'; import { fromYdkeURL, toYdkeURL } from './src/ydke';
import { fromYGOMobileDeckURL, toYGOMobileDeckURL } from './src/ygom';
export default class YGOProDeck { export default class YGOProDeck {
main: number[] = []; main: number[] = [];
...@@ -113,8 +114,8 @@ export default class YGOProDeck { ...@@ -113,8 +114,8 @@ export default class YGOProDeck {
return writer.buffer; return writer.buffer;
} }
fromYGOMobileDeckUri(uri: string): YGOProDeck { fromYGOMobileDeckURL(uri: string): YGOProDeck {
const parsed = fromYGOMobileDeckUri(uri); const parsed = fromYGOMobileDeckURL(uri);
this.main = parsed.main; this.main = parsed.main;
this.extra = parsed.extra; this.extra = parsed.extra;
this.side = parsed.side; this.side = parsed.side;
...@@ -122,13 +123,33 @@ export default class YGOProDeck { ...@@ -122,13 +123,33 @@ export default class YGOProDeck {
return this; return this;
} }
static fromYGOMobileDeckUri(uri: string): YGOProDeck { static fromYGOMobileDeckURL(uri: string): YGOProDeck {
return new YGOProDeck().fromYGOMobileDeckUri(uri); return new YGOProDeck().fromYGOMobileDeckURL(uri);
} }
toYGOMobileDeckUri(): string { toYGOMobileDeckURL(): string {
return toYGOMobileDeckUri(this.main, this.extra, this.side, this.name && { return toYGOMobileDeckURL(this.main, this.extra, this.side, this.name && {
name: this.name, name: this.name,
}); });
} }
fromYdkeURL(uri: string): YGOProDeck {
const parsed = fromYdkeURL(uri);
this.main = parsed.main;
this.extra = parsed.extra;
this.side = parsed.side;
return this;
}
static fromYdkeURL(uri: string): YGOProDeck {
return new YGOProDeck().fromYdkeURL(uri);
}
toYdkeURL(): string {
return toYdkeURL({
main: this.main,
extra: this.extra,
side: this.side,
});
}
} }
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
"prettier": "^2.8.4", "prettier": "^2.8.4",
"rimraf": "^4.1.2", "rimraf": "^4.1.2",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"ydke": "^1.1.0"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
...@@ -5168,6 +5169,13 @@ ...@@ -5168,6 +5169,13 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/ydke": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ydke/-/ydke-1.1.0.tgz",
"integrity": "sha512-q6EbAMtup1stTOER9/PNLH/uDQ1guYmiTU/+jn1BSePmkfvm9Rky49byQDNqYQjwrSXoLaoygkQMGIwNQRr+cg==",
"dev": true,
"license": "LGPL-3.0+"
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
......
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
"prettier": "^2.8.4", "prettier": "^2.8.4",
"rimraf": "^4.1.2", "rimraf": "^4.1.2",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"ydke": "^1.1.0"
} }
} }
// ydke.ts
export interface TypedDeck {
main: number[];
extra: number[];
side: number[];
}
function base64ToUint32Array(base64: string): number[] {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
const view = new DataView(bytes.buffer);
const result: number[] = [];
for (let i = 0; i < bytes.length; i += 4) {
result.push(view.getUint32(i, true)); // little-endian
}
return result;
}
function uint32ArrayToBase64(data: number[]): string {
const buffer = new ArrayBuffer(data.length * 4);
const view = new DataView(buffer);
data.forEach((val, i) => view.setUint32(i * 4, val, true));
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
export function toYdkeURL(deck: TypedDeck): string {
return (
'ydke://' +
uint32ArrayToBase64(deck.main) +
'!' +
uint32ArrayToBase64(deck.extra) +
'!' +
uint32ArrayToBase64(deck.side) +
'!'
);
}
export function fromYdkeURL(ydke: string): TypedDeck {
if (!ydke.startsWith('ydke://')) {
throw new Error('Invalid ydke:// URI');
}
const [mainStr, extraStr, sideStr] = ydke.slice(7).split('!');
if (mainStr === undefined || extraStr === undefined || sideStr === undefined) {
throw new Error('Incomplete ydke:// URI');
}
return {
main: base64ToUint32Array(mainStr),
extra: base64ToUint32Array(extraStr),
side: base64ToUint32Array(sideStr),
};
}
...@@ -47,7 +47,7 @@ function countUnique(cards: number[]): number { ...@@ -47,7 +47,7 @@ function countUnique(cards: number[]): number {
// === 主函数 === // === 主函数 ===
export function toYGOMobileDeckUri( export function toYGOMobileDeckURL(
main: number[], main: number[],
extra: number[], extra: number[],
side: number[], side: number[],
...@@ -80,7 +80,7 @@ export function toYGOMobileDeckUri( ...@@ -80,7 +80,7 @@ export function toYGOMobileDeckUri(
} }
export function fromYGOMobileDeckUri(uri: string): { export function fromYGOMobileDeckURL(uri: string): {
main: number[]; main: number[];
extra: number[]; extra: number[];
side: number[]; side: number[];
......
...@@ -54,15 +54,36 @@ describe('Sample test.', () => { ...@@ -54,15 +54,36 @@ describe('Sample test.', () => {
it('should encode and decode ygomobile', () => { it('should encode and decode ygomobile', () => {
const uri = 'http://deck.ourygo.top?name=%E7%99%BD%E9%93%B6%E5%9F%8E%E7%A0%81&ygotype=deck&v=1&d=J-xK4Mka02AuEAMf2dV6mj7aRemuJNQJK8BwrcEYh0MqOJqBJSgYqUwxPEVhOG8R18WWVKmzkT-xEdwxbmGVkkOdrVIpufaYI3Hs8oOrcya8Bi40h9G79iFW80rq-o6P-AHsusPY5nmvHLol0DIqEykESVlf6VSbxVJp-j7XZtTE0XvmJW80rqH28R4rgyRovOusVJzbutenYFBA_cyK6d3UWcQkJQlLjaroWavH-INFA56k5DQNWOQ1gpvxrKVBLgEk1olpolKmSgriramLlgtBK1EQ6C6oi94ZyHe7N6T7mqE6peds7mahrORP6A'; const uri = 'http://deck.ourygo.top?name=%E7%99%BD%E9%93%B6%E5%9F%8E%E7%A0%81&ygotype=deck&v=1&d=J-xK4Mka02AuEAMf2dV6mj7aRemuJNQJK8BwrcEYh0MqOJqBJSgYqUwxPEVhOG8R18WWVKmzkT-xEdwxbmGVkkOdrVIpufaYI3Hs8oOrcya8Bi40h9G79iFW80rq-o6P-AHsusPY5nmvHLol0DIqEykESVlf6VSbxVJp-j7XZtTE0XvmJW80rqH28R4rgyRovOusVJzbutenYFBA_cyK6d3UWcQkJQlLjaroWavH-INFA56k5DQNWOQ1gpvxrKVBLgEk1olpolKmSgriramLlgtBK1EQ6C6oi94ZyHe7N6T7mqE6peds7mahrORP6A';
const deck = new YGOProDeck().fromYGOMobileDeckUri(uri); const deck = new YGOProDeck().fromYGOMobileDeckURL(uri);
expect(deck.main).toHaveLength(58); expect(deck.main).toHaveLength(58);
expect(deck.extra).toHaveLength(15); expect(deck.extra).toHaveLength(15);
expect(deck.side).toHaveLength(15); expect(deck.side).toHaveLength(15);
expect(deck.main[0]).toBe(22812963); expect(deck.main[0]).toBe(22812963);
expect(deck.extra[0]).toBe(12381100); expect(deck.extra[0]).toBe(12381100);
expect(deck.side[0]).toBe(20292186); expect(deck.side[0]).toBe(20292186);
const uri2 = deck.toYGOMobileDeckUri(); const uri2 = deck.toYGOMobileDeckURL();
expect(uri2).toBe(uri); expect(uri2).toBe(uri);
}) })
it('should encode and decode ydke URL', () => {
const deck = new YGOProDeck();
deck.main.push(111111);
deck.main.push(111111);
deck.main.push(111111);
deck.main.push(222222);
deck.main.push(333333);
deck.extra.push(444444);
deck.extra.push(555555);
deck.side.push(666666);
const uri = deck.toYdkeURL();
expect(uri.startsWith('ydke://')).toBe(true);
const decoded = new YGOProDeck().fromYdkeURL(uri);
expect(decoded.main).toStrictEqual(deck.main);
expect(decoded.extra).toStrictEqual(deck.extra);
expect(decoded.side).toStrictEqual(deck.side);
});
}); });
import YGOProDeck from '..';
import * as ydkejs from 'ydke';
describe('YDKE interoperability test', () => {
const main = [1000, 1000, 1000, 2000, 2000, 3000];
const extra = [4000, 5000];
const side = [6000];
it('should encode with ygopro-deck-encode and decode with ydke.js', () => {
const deck = new YGOProDeck();
deck.main = [...main];
deck.extra = [...extra];
deck.side = [...side];
const uri = deck.toYdkeURL();
const decoded = ydkejs.parseURL(uri);
expect(Array.from(decoded.main)).toStrictEqual(main);
expect(Array.from(decoded.extra)).toStrictEqual(extra);
expect(Array.from(decoded.side)).toStrictEqual(side);
});
it('should encode with ydke.js and decode with ygopro-deck-encode', () => {
const typedDeck: ydkejs.TypedDeck = {
main: new Uint32Array(main),
extra: new Uint32Array(extra),
side: new Uint32Array(side),
};
const uri = ydkejs.toURL(typedDeck);
const decoded = new YGOProDeck().fromYdkeURL(uri);
expect(decoded.main).toStrictEqual(main);
expect(decoded.extra).toStrictEqual(extra);
expect(decoded.side).toStrictEqual(side);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment