knewjade / tetris-fumen

A NPM package to Encode/Decode fumen for tetris
MIT License
14 stars 2 forks source link

tetris-fumen

tetris-fumen is a parser library to encode/decode tetris game state. Data compatible with fumen, 連続テト譜エディタ can be created and manipulated.

Features

Installation

npm install tetris-fumen

Specifications

Field

Field have 10x23 play area and 1 garbage line.

    |__________|          22 // Top
    |__________|          21
    |__________|          20
    |__________|          19
   ~~~~~~~~~~~~~~~         .
    |__________|           .
    |___JJJ____|           .
    |L__ZZJS___|           2
    |L___ZZSSOO|           1
    |LL IIIISOO|  <-  y =  0
    ============
    |XXXXXXXXX_|  <-  y = -1 // Garbage Line
     ^        ^
 x = 0 ...... 9

Mino

The type of mino is represented by below strings:

The rotation states of mino is represented by below strings:

The position of mino is represented by (x, y); See harddrop's article SRS/How Guideline SRS Really Works for details

Getting Started

1. Decode

Data used in the code below

Javascript

const { decoder } = require('tetris-fumen');

const data = "v115@vhGRQYHAvItJEJmhCAUGJKJJvMJTNJGBJFKYPAUEzP?EJG98AQmqhECDdCA";
// const data = "https://harddrop.com/fumen/?v115@vhGRQYHAvItJEJmhCAUGJKJJvMJTNJGBJFKYPAUEzP?EJG98AQmqhECDdCA";  // Allow URL format
const pages = decoder.decode(data);

console.log(pages.length);  // 7

console.log(pages[0].comment);  // 'Opening'
console.log(pages[0].operation);  // { type: 'I', rotation: 'spawn', x: 4, y: 0 }

Typescript

import { decoder, Page } from 'tetris-fumen';

const data = 'v115@vhGRQYHAvItJEJmhCAUGJKJJvMJTNJGBJFKYPAUEzP?EJG98AQmqhECDdCA';
// const data = "https://harddrop.com/fumen/?v115@vhGRQYHAvItJEJmhCAUGJKJJvMJTNJGBJFKYPAUEzP?EJG98AQmqhECDdCA";  // Allow URL format
const pages: Page[] = decoder.decode(data);

console.log(pages.length);  // 7

console.log(pages[0].comment);  // 'Opening'
console.log(pages[0].operation);  // { type: 'I', rotation: 'spawn', x: 4, y: 0 }

2.1. Update decoded fumen

Loading data in the code below Generated data by the code below

Javascript

const { decoder, encoder } = require('tetris-fumen');

const pages = decoder.decode('v115@9gI8AeI8AeI8AeI8KeAgH');
pages[0].comment = '4 Lines';
pages[0].operation = { type: 'I', rotation: 'left', x: 9, y: 1 };

console.log(encoder.encode(pages));  // v115@9gI8AeI8AeI8AeI8Ke5IYJA0no2AMOprDTBAAA

2.2. Create from scratch

Data generated by the code below

Javascript

const { encoder, Field } = require('tetris-fumen');

const pages = [];
pages.push({
    field: Field.create(
        'LLL_______' +
        'LOO_______' +
        'JOO_______' +
        'JJJ_______',
        'XXXXXXXXX_',
    ),
    comment: 'Perfect Clear Opener',
});

pages.push({
    operation: {
        type: 'T', rotation: 'left', x: 9, y: 1,
    },
});

pages.push({
    operation: {
        type: 'Z', rotation: 'spawn', x: 7, y: 0,
    },
});

pages.push({
    operation: {
        type: 'S', rotation: 'spawn', x: 8, y: 2,
    },
});

pages.push({
    comment: 'Success: 61.19 %',
    flags: {
        mirror: true,
    },
});

pages.push({
    comment: '(Mirror)',
});

console.log(encoder.encode(pages));  // v115@9gilGeglRpGeg...../~/.....ciNEyoAVB

Typescript

import { encoder, EncodePage, Field } from 'tetris-fumen';

const pages: EncodePage[] = [];
pages.push({
    field: Field.create(
        'LLL_______' +
        'LOO_______' +
        'JOO_______' +
        'JJJ_______',
        'XXXXXXXXX_',
    ),
    comment: 'Perfect Clear Opener',
});

pages.push({
    operation: {
        type: 'T', rotation: 'left', x: 9, y: 1,
    },
});

pages.push({
    operation: {
        type: 'Z', rotation: 'spawn', x: 7, y: 0,
    },
});

pages.push({
    operation: {
        type: 'S', rotation: 'spawn', x: 8, y: 2,
    },
});

pages.push({
    comment: 'Success: 61.19 %',
    flags: {
        mirror: true,
    },
});

pages.push({
    comment: '(Mirror)',
});

console.log(encoder.encode(pages));  // v115@9gilGeglRpGeg...../~/.....ciNEyoAVB

3. Page details

Javascript

const { decoder } = require('tetris-fumen');
const pages = decoder.decode('v115@vhGRQYHAvItJEJmhCAUGJKJJvMJTNJGBJFKYPAUEzP?EJG98AQmqhECDdCA');
const page = pages[0];

/* index: Page number, begin from 0 */
console.log(page.index);  // 0

/* comment: Comment on the page */
console.log(page.comment);  // 'Opening'

/* operation: Placed mino states */
console.log(page.operation);  // { type: 'I', rotation: 'spawn', x: 4, y: 0 }

/* flags: Fumen options on the page
     - colorize: If true, apply guideline color to block
     - lock: If true, lock mino after placement
     - mirror: If true, flip field horizontally after placement
     - quiz: If true, comment is in quiz format
     - rise: If true, rise garbage after placement */  
console.log(page.flags);// { colorize: true, lock: true, mirror: false, quiz: false, rise: false }

/* field: Field object on the page before applying operation and flags */
const field = page.field;
console.log(field.at(4, 0));  // '_'  // EMPTY

field.put(page.operation);  // field object is mutable
console.log(field.at(4, 0));  // 'I'

/* mino: Same as `operation`, but have a more flexible operators */
const mino = page.mino();
console.log(mino.type, mino.rotation, mino.x, mino.y);  // I spawn 4 0
console.log(mino.operation());  // == page.operation
console.log(mino.positions());  // [{ x: 3, y: 0 }, { x: 4, y: 0 }, { x: 5, y: 0 }, { x: 6, y: 0 }]
                                // the position of the blocks
console.log(mino.isValid());  // true  // Whether it's correct as mino
mino.x = -1;
console.log(mino.isValid());  // false

4. Field details

Javascript

/* Create field */
const field = Field.create(
    'LLL_______' +
    'LOO_______' +
    'JOO_______' +
    'JJJ_______',  // field
    'XXXXXXXXX_',  // garbage (optional)
);

/* Get block type */
field.at(9, 0);  // '_'

/* Set block */
field.set(9, 0, 'O');
field.set(9, 1, 'GRAY');
field.set(9, 2, 'EMPTY');

field.set(0, -1, 'X');  // same as 'GRAY'
field.set(9, -1, '_');  // same as 'EMPTY'

/** Current:
      LLL_______
      LOO_______
      JOO______X
      JJJ______O
      XXXXXXXXX_
 */

/* Check if can fill piece */
field.canFill({ type: 'I', rotation: 'left', x: 9, y: 3 });  // true

/* Fill piece even if not on the ground, and return the mino as the placed */
field.fill({ type: 'I', rotation: 'left', x: 9, y: 3 });

/* Check if can fill and lock piece on the ground */
field.canLock({ type: 'O', rotation: 'spawn', x: 4, y: 0 });  // true
field.canLock({ type: 'O', rotation: 'spawn', x: 4, y: 1 });  // false

/* Harddrop and fill piece to the ground, and return the mino as the placed */
field.put({ type: 'O', rotation: 'spawn', x: 4, y: 10 });  // return `{ type: 'O', rotation: 'spawn', x: 4, y: 0 }`

/* Convert to string 
                        default
     @param `reduced`   true    If true, EMPTY line is not parsed
     @param `separator` '\n'    Specify characters between lines 
     @param `garbage`   true    If true, garbage is parsed */
console.log(field.str());

/** Current:
      _________I
      _________I
      LLL______I
      LOO______I
      JOO_OO___X
      JJJ_OO___O
      XXXXXXXXX_
 */

// Copy field
const copied = field.copy();
console.log(copied.str() === field.str());  // true
copied.set(0, 0, 'T');
console.log(copied.str() === field.str());  // false