npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tetris-fumen

v1.1.3

Published

Fumen parser for tetris

Downloads

84

Readme

tetris-fumen

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

Features

  • Tetris game state can be packed into a string and easily unpacked
  • Provide simple manipulation for game field
  • Decoder supports v115 and v110
  • Encoder supports v115 only

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:

  • 'T', 'I', 'O', 'L', 'J', 'S', 'Z'
  • Gray => 'X' or 'GRAY'
  • Empty => '_' or 'EMPTY'

The rotation states of mino is represented by below strings:

  • No rotate => 'spawn'
  • Clockwise => 'right'
  • Counter Clockwise => 'left'
  • 180 rotation => 'reverse'

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