npm パッケージを公開しました!memory-speed-numbers

npmパッケージを公開したので、その内容について書きます。

この記事の流れ

  • パッケージの説明
  • 開発に用いた要素
  • 開発の背景
  • アイディア出し
  • 詰まった点
  • 工夫した点
  • 反省点
  • 最後に

パッケージの説明

ランダムに表示された5桁掛ける5行の数字を暗記して答えるというものです。
memory-speed-numbers

開発に用いた要素

  • enquirer
  • エスケープシーケンス
  • 非同期処理 Promise
  • クラス構文

開発の背景

現在、プログラミングスクール フィヨルドブートキャンプ(以下 FBC)でプログラミングを学んでおり、プラクティスの一環として作りました。

ラクティスの修了条件

作った npm パッケージの npm パッケージサイト( https://www.npmjs.com/ )上のURLを提出してOKをもらう。

注意

ここで作った npm は自分のポートフォリオの一つになります。すでにあるものや、同じような内容の npm では技術力を高める意味では作る意味はありますが、世にリリースをする意味を持ちません。なので、被らない内容のものを考えましょう。

アイディア出し

「アイディア、思いつかないなぁ」というところから始まりました。 アイディアを生むためには、日頃から頭の片隅に置いておき、考える必要があると思います。ですので、少し早めに考えることにしました。
まずやったことは、他の受講生さんの制作物を拝見し、インストールして使ってみました。 感想としては「え、こんなものが作れるの、すごい!」でした。「自分に作れるんだろうか・・・」 その中で以下のパッケージが印象に残り、自分も何か動くものを作ってみたいなという気持ちになりました。

  • flash-anzan フラッシュ暗算ができる。
    足し算を表示したり消したり、動いておもしろい!と思いました。

  • dummy_email_address_maker ユーザーテスト用のダミーメールアドレスを生成。
    ランダムな文字列でアドレスを生成している点が参考になりました。

上に挙げたパッケージと被る部分もありますが、、「数字を暗記して答える」ということを思い付きました。 調べるとスピードナンバー(英語名:Speed Numbers)という競技もあるようで、このアイディアで行こうと決めました。
ジャパンオープン記憶力選手権〜Japan Open Memory Championship〜

詰まったところ

ターミナルに表示したものを消すためにエスケープシーケンスを使っているのですが、画面の範囲外のものは消せないという仕様を知りませんでした。
エスケープシーケンスによる画面制御

当初は表示する数字の桁数と暗記時間の上限を決めていませんでした(そこが問題)。最初の入力値で500桁を入力すれば、ランダムな数字500桁を40桁ずつ改行して出力していました。 ギネス記録保持者は5分で500桁を記憶するらしく、桁数は多い方が良いと思っていました。
1分で数字100桁が記憶できる古代ギリシャ式4ステップ記憶術

利用者の入力桁数に合わせた出力行数を消すコードが完成し、「これでどんな桁数にも対応できる!やったぜ(ルンルン🎵)」と思ったのも束の間、100桁、200桁、...400桁と確認したところで、おかしいことに気づきました。

50
51
52
53
54
55
56 ← 桁数が多くなると、上にスクロールしたときに画面より上にあった部分が消えていないことがわかる。
MacBook-Pro: ~/bin/js_sample
$
簡略化したコード
function clearTerminalAfterDelay(numbers, milliseconds) {
  console.log(numbers); // 100行を出力する

  let deleteLine = 100; // 消したい行数
  setTimeout(() => {
    process.stdout.write(`\x1b[${deleteLine}A\x1b[0J`); // 出力した行数を消す
  }, milliseconds * 1000);
}

const digits = 100; // 桁
// 1から100までの配列を作り、改行文字で連結して文字列を作る。
// (実際には0から9までの数字で利用者が入力した桁数のランダムな数字の文字列を表示します。)
const numbers = Array.from({ length: digits }, (_, index) => index + 1).join(
  "\n"
);
const milliseconds = 3; // 3秒で消す
clearTerminalAfterDelay(numbers, milliseconds);


「NO !! これではアプリとして破綻しているではないか...」
3時間ほど絶望して考えた結果、出力桁数を25桁、暗記時間は選択式にして制限することにしました。

ここで得た教訓:壮大なものを作ろうとしない。

上限がないというのは良くないと思っていて、何かしらの制限が必要だとは思っていました。

工夫したところ

  1. 答え合わせの際、自分が間違えた箇所が確認できる
  2. 暗記した数字を入力する際、改行、スペースを使い放題
  3. 暗記した数字を入力する際、1文字以上25文字以内で、数字のみ入力可のバリデーションを設定

以下、順番に説明させていただきます。

答え合わせの際、自分が間違えた箇所が確認できる

FBCには Linux の cal コマンド(カレンダー)を実装しようというプラクティスがあります。そのプラクティスの歓迎要件で(必須条件ではない)、「今日の日付の部分の色が反転する(背景色と文字色が入れ替わる)」というのがあり、そのプラクティスでは取り組みませんでしたが、そういえばと思ってヒントを得ました。

Image from Gyazo

話が逸れますが、FBCでは Linux の cal、ls などのコマンドを Ruby で一から実装するプラクティスがあります。そしてその後のプラクィスでは、それをJavaScriptで再び作ったり、オブジェクト思考を学んで、それを適用して新しく作ったりします。同じ題材で別の技術や要素を利用して作るという点には一貫性があり、FBCのカリキュラムがよく考えられていて優れている点だと感じています。

暗記した数字を入力する際、改行、スペースを使い放題

実際のスピードナンバーという競技では専用の用紙が渡され、それに記入していくという形のようですが、それをCLIで実現するにはどうすれば良いのか考えました。 時間も限られており、あまり難しいこともできない、複雑になると見づらくなりそうだと思ったので、入力しやすくするために改行とスペースは使い放題可という結論に至りました。
入力値で複数行を受け付けるには enquirer で multiline: trueを書けば良く、大変便利だと思いました。

function inputUsersAnswerPrompt() {
  return new Input({
    multiline: true,
  });
}
暗記した数字を入力する際、1文字以上25文字以内で、数字のみ入力可のバリデーションを設定

以下サンプルコード(option-validate.js)を参考にしました。

'use strict';

const { Password } = require('enquirer');

const prompt = new Password({
  name: 'password',
  message: 'What is your password?',
  validate(value) {
    return (!value || value.length < 7) ? 'Password must be 7 or more chars' : true;
  }
});

prompt.run()
  .then(answer => console.log('Answer:', answer))
  .catch(console.error);

実際のコードはこちら。

import enquirer from "enquirer";
const { Input } = enquirer;

function inputUsersAnswerPrompt() {
  return new Input({
    message: "Input your answer",
    hint: " (Enter numbers.)",
    multiline: true,
    validate(value) {
      value = value.replace(/\s/g, "");
      return value.length === 0 || value.length > 25 || /[^\d]/.test(value)
        ? "Input must be between 1 and 25 characters and consist of numbers only."
        : true;
    }
  });
}

以下URLにサンプルコードがたくさんあります。
enquirer / examples /

反省点

反省する点としては、作る手順とリファクタリングです。見通しが立っていなかったというのもありますが、

  1. 全体として動くものを作る
  2. main.jsファイルに async / await を適用
  3. クラス構文を使ってオブジェクト思考っぽくクラス分けしてリファクタリング

の順番になりました。 全体を作って全体を作り変えていくというのは大変です。オブジェクト思考の部品化するという利点を活かすと、最初からオブジェクト指向でトライしていくべきだったのではないでしょうか。

最後に

実際に作ってみると、作るまで不都合なことに気づかなかったり、こういう機能を付けたらどうだろうというアイディアもいくつか浮かんできました。考えたことが実現できたときや、パッケージとして一つのものを作って公開できたときには達成感も感じました。新しいすごいことに挑戦するというよりは、カリキュラムでやってきた要素を少しずつ使うという形になりました。作ってみて学べることがたくさんあったと思います。
カリキュラムはまだまだ続く。
最後までお読みいただき、ありがとうございました。