プログラミング はじめの一歩 JavaScript + p5.js編
28:倉庫にしまった服を取り出す

この記事の詳しい内容には毎日新聞の「倉庫にしまった服」ページから有料記事に進むことで読めます。

概要

この倉庫にはTシャツを預けるラックとズボンを預けるラックがあり、全部ロボットが管理しています。

たとえば、を取り出したいときには、「Tシャツ[2] 取り出す」のようにロボットに伝えます。これは、Tシャツのラックの2番の箱に入っている服を取り出す、ということを意味します。

では問題です。を取り出したいときには、ロボットに何と伝えればよいでしょう?

論理を考える

「Tシャツ[2] 取り出す」という命令を分解すると、”Tシャツ”はラックの名前で、その後の”[2]”は箱の番号(左から0で始まる箱の位置)、”取り出す”はロボットのアクションだと考えられます。

は、ズボンのラックの2番の箱に入っているので、問題の答えの命令は「ズボン[2] 取り出す」 となります。

服を文字列で表すところからスタート

プログラミングのとっかかりを探しましょう。Tシャツが絵のでは抽象的で分かりづらいので、それぞれ文字で置き換えることにします。

‘スマイルマーク入りのTシャツ’, ‘ストライプ模様のTシャツ’, ‘グレーのTシャツ’, ‘ロボットの絵入りTシャツ’

これらの文字は1つ1つを個別の変数に値として割り当てることもできますが、ひとまとめにすることもできます。そのためにはJavaScriptの配列オブジェクトを使います。配列はリストのような構造を持っていて、内部にリストの項目を持つことができます。項目には順番がついています。

配列を作成するには、項目にしたいものを”,”で区切り、全体を”[“と”]”で囲みます。通常は変数に割り当てて使用します。下記は、TシャツのラックにあるTシャツの文字列を項目に持つ配列オブジェクトを作成し、tShirtStringsという名前の変数に代入しています。

// Tシャツのラック
let tShirtStrings = ['スマイルマーク入りのTシャツ', 'ストライプ模様のTシャツ', 'グレーのTシャツ', 'ロボットの絵入りTシャツ'];

ロボットが、「Tシャツ[2] 取り出す」という命令を実行するということは、配列tShirtStringsの位置2にある項目を取り出すということです。取り出すとそこにあったものはなくなります。また通常は、なくなった場所にはその右にあった項目がずれて移動し、空きが埋められます。

配列に含まれる項目は要素と呼ばれます。要素には配列内での番号が割り当てられます。番号は左(配列の先頭)から0で始まります。

配列要素の削除は配列オブジェクトのsplice()メソッドで行えます。splice()の第1引数には削除したい要素の番号である2を、第2引数には削除したい要素の個数である1を指定します。

// Tシャツのラック
let tShirtRack = ['スマイルマーク入りのTシャツ', 'ストライプ模様のTシャツ', 'グレーのTシャツ', 'ロボットの絵入りTシャツ'];
// 配列のsplice()メソッドを使ってインデックス位置2にある1つの要素を削除
let removedElement = tShirtRack.splice(2, 1);
print(removedElement); // 削除された要素
print(tShirtRack); // 削除後の配列

ラックからの服の取り出しはロボットが行うので、tShirtRack配列からの要素の削除もロボットが行います。

ロボットのクラス

では、配列要素が削除できるロボットを考えてみましょう。

ロボットを表すオブジェクトが配列要素を削除するアクションを実行するということは、ロボットのクラスを作成し、そこに配列要素を削除するメソッドを持たせればよいということです。

またこのロボットは2つのラックをすべて管理するということなので、Tシャツ用のラックとズボン用のラックを表すプロパティが必要です。さらに言えば、人間がロボットに服を預けることのできるメソッドもあった方が自然です(最初からロボットが服を全部持っているというのは不自然に思えます)。

そこでまずは、次のTakeoutRobotクラスとそのコンストラクタを考えます。

class TakeoutRobot {
    constructor() {
        // ロボットが管理するラック2つ
        this.rack0 = [];
        this.rack1 = [];
        // rack0とrack1にracksからアクセスできるようにする
        this.racks = [this.rack0, this.rack1];
    }

rack0プロパティはTシャツ用のラックを表す配列に、rack1プロパティはズボン用のラックを表す配列にします。これは人間の側の約束事です。これらにそれぞれTシャツやズボンを追加することで、ロボットに服を預けることが表現できます。this.racks = [this.rack0, this.rack1]というのは、rack0とrack1配列にアクセスしやすくするための工夫で、特に両方の配列にアクセスしたいとき役立ちます。

rack0とrack1配列への服の追加は次のputClothes()メソッドで行えます。呼び出すときにはラックの番号(0がTシャツの、1がズボンのラックとします)と、預けたい服を指定します。getRack()は確認用のメソッドです。

// ラック番号と服(文字列)を指定して、ロボットに服をあずける
    putClothes(rackNumber, clothes) {
        if (rackNumber === 0) {
            this.rack0.push(clothes);
        }
        else if (rackNumber === 1) {
            this.rack1.push(clothes);
        }
    }
    // 2つのラックの中身を出力して確認する
    getRack() {
    	print(this.racks);
    }

ロボットがラックから服を取り出すアクションは次のメソッドで定義できます。このtakeout()メソッドには、ラック番号(0か1)と、そのラック内での服の位置を渡します。

    // ラックから服を取り出す
    // 服がどのラックの何番めの箱に入っているかが分かっている
    takeout(rackIndex, index) {
        // ラックの特定
        const rack = this.racks[rackIndex];
        // 服の特定
        const clothes = rack[index];
        // そこになければfalseを返してメソッドを終える
        if (clothes === undefined) return false;
        // 要求された服を取り出すので、当該ラックからその服を削除する
        // => rack0かrack1から、指定インデックス位置にある要素を1つ削除する
        rack.splice(index, 1);
        // 服を返す
        return clothes;
    }
}

どのラックかはパラメータのrackIndexで分かります。0ならrack0、1ならrack1配列です。これを変数rackに割り当て、[index]でrack0かrack1の文字列要素を調べます。それがこのメソッドが返すべき服です。

ただしそこに要素がない可能性があるので、その場合にはreturn falseを実行して、ここでメソッドを終えます。

服の要素がある場合には、現在対象としている配列rackのsplice()メソッドを使って、返すべき服をその配列から削除します。これでラックからその服を取り出すことになります。

ロボットに服を預け、取り出す

TakeoutRobotクラスを使用するには、まずnew演算子でクラスのインスタンスを作成します。そしてputClothes()メソッドを通して、指定ラック(Tシャツなら0)に文字列のTシャツを預けます。

let robot;

robot = new TakeoutRobot();
// Tシャツを預ける
robot.putClothes(0, 'スマイルマーク入りのTシャツ');
robot.putClothes(0, 'ストライプ模様のTシャツ');
robot.putClothes(0, 'グレーのTシャツ');
robot.putClothes(0, 'ロボットの絵入りTシャツ');

同じことは、文字列の服の配列を用意しておき、forループで一括してputClothes()を呼び出すことでも行えます。

let pantsRack = ['黄色の短パン', 'オレンジ色の短パン', '青色の短パン'];

// forループで預ける
for (let i = 0; i < pantsRack.length; i++) {
    robot.putClothes(1, pantsRack[i]);
}
robot.getRack();

この時点でロボットのgetRack()を実行すると、ロボットのrack0とrack1配列の中身が下図のように確認できます。

ロボットにTシャツとズボンを預けたので、取り出すことができます。Tシャツのラックにある2番めの箱に入っている服を取り出すよう、ロボットに伝えるには、robot.takeout(0, 2)を呼び出します。ロボットは、求められた服がそこにあった場合にはその服を、なかった場合にはfalseを返します。

// Tシャツのラックにある2番めの箱に入っている服を取り出す
const myTshirt = robot.takeout(0, 2);
if (myTshirt) {
    print(myTshirt);
}
robot.getRack();

下図はこの結果です。Tシャツのラックにある2番めの箱、つまりrobotのrack0プロパティのインデックス位置2の要素は文字列の’グレーのTシャツ’なので、これが返されます。そしてその後getRack()を呼び出しているので、最新のラック状況が分かります。期待通り、rack0配列から要素’グレーのTシャツ’がなくなっているのが分かります。

再度取り出すとどうなるか?

ここで問題です。再度robot.takeout(0, 2)を呼び出すと結果はどうなると思いますか? ’グレーのTシャツ’は取り出してもうないので、falseが返されるでしょうか? 

答えは’ロボットの絵入りTシャツ’が返される、です。robot.takeout(0, 2)は’グレーのTシャツ’を取り出せという命令ではなく、robotのrack0プロパティのインデックス位置2にある要素を返して、その要素を配列から削除しろという命令です。したがって、1回めのrobot.takeout(0, 2)によって要素が1つ減ったrack0のインデックス位置2にある’ロボットの絵入りTシャツ’が返されます。

‘青色の短パン’を取り出す全コード

以下はここまでを網羅した全コードです。ズボンのラックの2番の位置にある’青色の短パン’を取り出します。

// ズボンのラック
let pantsRack = ['黄色の短パン', 'オレンジ色の短パン', '青色の短パン'];

let robot;

function setup() {
    noCanvas();
    robot = new TakeoutRobot();
    // Tシャツを預ける
    robot.putClothes(0, 'スマイルマーク入りのTシャツ');
    robot.putClothes(0, 'ストライプ模様のTシャツ');
    robot.putClothes(0, 'グレーのTシャツ');
    robot.putClothes(0, 'ロボットの絵入りTシャツ');
    // robot.getRack();  // 確認
    // forループで預ける
    for (let i = 0; i < pantsRack.length; i++) {
        robot.putClothes(1, pantsRack[i]);
    }
    //robot.getRack();
    // Tシャツのラックにある2番めの箱に入っている服を取り出す
    /*
    const myTshirt = robot.takeout(0, 2);
    if (myTshirt) {
      print(myTshirt);
    }
    robot.getRack();
    */
    // 問題の答え
    // ズボンのラックにある2番めの箱に入っている服を取り出す
    const myPants = robot.takeout(1, 2);
    if (myPants) {
        print(myPants);
    }
    robot.getRack();
}

class TakeoutRobot {
    constructor() {
            // ロボットが管理するラック2つ
            this.rack0 = [];
            this.rack1 = [];
            // rack0とrack1にracksからアクセスできるようにする
            this.racks = [this.rack0, this.rack1];

        }
        // ラック番号と服(文字列)を指定して、ロボットに服をあずける
    putClothes(rackNumber, clothes) {
            if (rackNumber === 0) {
                this.rack0.push(clothes);
            }
            else if (rackNumber === 1) {
                this.rack1.push(clothes);
            }
        }
        // 2つのラックの中身を出力して確認する
    getRack() {
        print(this.racks);
    }

    // ラックから服を取り出す
    // 服がどのラックの何番めの箱に入っているかが分かっている
    takeout(rackIndex, index) {
        // ラックの特定
        const rack = this.racks[rackIndex];
        // 服の特定
        const clothes = rack[index];
        // そこになければfalseを返してメソッドを終える
        if (clothes === undefined) return false;
        // 要求された服を取り出すので、当該ラックからその服を削除する
        // => rack0かrack1から、指定インデックス位置にある要素を1つ削除する
        rack.splice(index, 1);
        // 服を返す
        return clothes;
    }
}

以上でお題を解決する論理は完成です。

服を探してくれ!

電池切れやデータの損傷がない限り、ロボットはまず与えられた仕事を完全に実行しますが、人間はロボットに預けたときの服の名前やラック番号を忘れます。人間はそれでも何とかしろとロボットに命令します。以下では人間のこの困った願いをかなえる方法を探っていきます。

最終手段はおそらく、今ロボットが管理している服を全部見せ、そこから人間が選ぶ方法でしょう。人間が預けた服について何も覚えていない場合には、これよりほかにありません。ただし人間は嘘をつき、なりすますので、それを見破る能力がロボットに要ります。

ラックの番号は覚えているが、服の名前は忘れた、特徴は覚えている

人間は服を預けるとき、robot.putClothes(0, ‘スマイルマーク入りのTシャツ’) のように、ラックの番号と服の名前を指定するのですが、このときの正式な名前を忘れた場合です。ただし’スマイルマーク’などと、ほかと異なる、その服だけの特徴は覚えています。

これは服の特徴を表す文字列であるSrtingオブジェクトのindexOf()メソッドで解決できます。indexOf()はp5.jsのメソッドではなく、JavaScript本体のメソッドです。

String.indexOf()
indexOf() メソッドは、呼び出す String オブジェクト中で、 fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。
str.indexOf(searchValue [, fromIndex])

Stringオブジェクトはそれを構成する文字が連なったもので、文字の連なりの順番を持っています。その文字の連なりを調べることで、指定された文字がそこに含まれているどうかが分かります。

たとえば次のindexOf()は5を返します。

let res = 'ロボットの絵入りTシャツ'.indexOf('絵入り');
print(res); // 5

‘ロボットの絵入りTシャツ’は文字を”で囲んだものなので、JavaScriptのStringオブジェクトであり、indexOf()が使用できます。indexOf()に’絵入り’を与えると、indexOf()は自分を呼び出したStringオブジェクトの先頭から’絵入り’という文字列を探し、それを最初に見つけた位置を返します。’絵入り’は下図に示すように位置5から始まるので、変数resには5が代入されます。

今の場合、知りたいのはインデックス位置ではなく、特徴を示す文字が含まれているかどうかなので、これには、含まれていない場合に返される-1が利用できます。つまり、返されたインデックス値が-1より大きければ、そこに含まれていると分かります。

以上を踏まえたTakeoutRobotクラスのメソッドは次のように記述できます。

// ラックは分かっているが、預けたときの名前は覚えていない。特徴は覚えている
takeoutWithRack(rackIndex, hint) {
    // rack0かrack1かを特定
    const rack = this.racks[rackIndex];
    // 与えられたラックを走査する
    for (let i = 0; i < rack.length; i++) {
        /// 配列内の文字列を特定
        const str = rack[i];
        // その文字列にhintが含まれていれば
        const index = str.indexOf(hint);
        if (index > -1) {
            print('見つかりました!');
            // その要素(文字列)を特定
            const elm = rack[i];
            // その要素をrackから削除
            rack.splice(i, 1);
            // 要素を返す
            return elm;
        }
        else {
            print('見つかりません。。。');
        }
    }
    return false; // 見つからなかった
}

このtakeoutWithRack()メソッドは、引数としてラックの番号(0か1)と、ヒントとなる特徴の文字列を取ります。指定されたラック(rack0かrack1)にヒントの文字列を含む要素が見つかった場合にはその要素を返し、見つからなかった場合にはfalseを返します。

* 見つかった場合、 return elm で要素が返され、メソッドの実行はそこで終わるので、最終行のreturn false は実行されません。

次の例では、takeoutWithRack()に、Tシャツのラック番号0とヒントの文字列’ストライプ’を渡しています。

const clothe = robot.takeoutWithRack(0, 'ストライプ');
if (clothe) {
	print(clothe);	// ストライプ模様のTシャツ
}
すっかり忘れてしまったが、特徴だけは覚えている

ラックの番号を覚えていず(これは、預けたのがTシャツかズボンかも忘れたということです)、ほかと区別できる特徴だけ覚えている場合です。これもindexOf()メソッドで解決できます。

// 特徴だけ覚えている
takeoutWithHint(hint) {
    // racksの長さ分(=2)だけ繰り返す
    for (let i = 0; i < this.racks.length; i++) {
        // ラックの特定 rack0かrack1
        const rack = this.racks[i];
        // rack0かrack1の長さ分だけ繰り返す
        for (let j = 0; j < rack.length; j++) {
            // 配列内の文字列を特定
            const str = rack[j];
            // その文字列にhintが含まれていれば
            if (str.indexOf(hint) > -1) {
                print('見つかりました!');
                // その要素(文字列)を特定
                const elm = rack[j];
                // その要素をrackから削除
                rack.splice(j, 1);
                return elm;
            }
            else {
                print('見つかりません。。。');
            }
        }
    }
    return false; // 見つからなかった
}

ラックが分からないので、rack0とrack1両方を調べています。

このtakeoutWithHint()メソッドには特徴のヒントだけ渡します。

// 特徴だけ覚えている
const clothe = robot.takeoutWithHint('黄色');
if (clothe) {
    print(clothe);	// 黄色の短パン
}

では、特徴とは言えない文字列を渡したときはどうなるでしょう?

const clothe = robot.takeoutWithHint('短パン');
if (clothe) {
    print(clothe);
}
robot.getRack();

indexOf()メソッドは自分を呼び出したStringオブジェクトの先頭から指定された文字列を探し、それを最初に見つけた位置を返すので、’黄色の短パン’を返し、それをラックから削除します。

渡されたヒントが、取り出したい服をほかを区別する特徴でないので、ロボットとしてはこれ以上致し方ありません。

視覚化と3つのお願い

最後に視覚化しましょう。またボタンを3つ作って、それぞれ、

  • Tシャツラックの1番め
  • ズボンのラックにある青
  • ラック不明、”ロボット”という特徴だけ分かっている

服を取り出すよう、ロボットに伝えます。取り出した服は、ロボットの前に描画します。

以下がその全コードです。

let robot;
// イメージ関連の変数
let tShirtImages, pantsImages, robotImage;
let tShirtRackImage, pantsRackImage;
// ロボットが取り出した服を入れる配列
let takeouts = [];

function preload() {
    const smileT = loadImage('images/smileT.png');
    const stripeT = loadImage('images/stripeT.png');
    const grayT = loadImage('images/grayT.png');
    const robotT = loadImage('images/robotT.png');
    tShirtImages = [smileT, stripeT, grayT, robotT];

    const yellowP = loadImage('images/yellowP.png');
    const orangeP = loadImage('images/orangeP.png');
    const blueP = loadImage('images/blueP.png');
    pantsImages = [yellowP, orangeP, blueP];

    robotImage = loadImage('images/robot.png');
    tShirtRackImage = loadImage('images/tShirtRack.png');
    pantsRackImage = loadImage('images/pantsRack.png');
}

function setup() {
    createCanvas(400, 300);

    // Tシャツのラック
    let tShirtStrings = ['スマイルマーク入りのTシャツ', 'ストライプ模様のTシャツ', 'グレーのTシャツ', 'ロボットの絵入りTシャツ'];
    // ズボンのラック
    let pantsRackStrings = ['黄色の短パン', 'オレンジ色の短パン', '青色の短パン'];
    // 文字列とイメージをMapオブジェクトで関連付け、Tシャツの配列にまとめる
    // Tシャツを表すMapオブジェクトの配列
    let tShirtRack = clothesMapping(tShirtStrings, tShirtImages);
    // 文字列とイメージをMapオブジェクトで関連付け、ズボンの配列にまとめる
    // ズボンを表すMapオブジェクトの配列
    let pantsRack = clothesMapping(pantsRackStrings, pantsImages);

    robot = new TakeoutRobot(240, 170, robotImage);

    // Tシャツを預ける
    for (let i = 0; i < tShirtRack.length; i++) {
        robot.putClothes(0, tShirtRack[i]);
    }
    // ズボンを預ける
    for (let i = 0; i < pantsRack.length; i++) {
        robot.putClothes(1, pantsRack[i]);
    }
    // 確認
    //robot.getRack();
    // [Tシャツラックの1番めのTシャツ]ボタン
    const takeoutButton1 = setButton('Tシャツラックの1番めのTシャツ', {
        x: 20,
        y: 320
    });
    // ボタンのクリックで
    takeoutButton1.mousePressed(() => {
        // ロボットにTシャツのラックにある1番めのシャツを取り出すように命令する
        // ロボットはそのTシャツのMapオブジェクトを返す
        const clothe = robot.takeout(0, 1);
        // 取り出す服がなかった場合はtakeouts配列に追加しない
        if (!clothe) return;
        // 表示用配列に追加 => ロボットの下に描画される
        takeouts.push(clothe);
    });

    // [ズボンのラックにあるオレンジ]ボタン
    const takeoutButton2 = setButton('ズボンのラックにある青', {
        x: 20,
        y: 360
    });
    // ボタンのクリックで
    takeoutButton2.mousePressed(() => {
        // ロボットにTシャツのラックにある1番めのシャツを取り出すように命令する
        // ロボットはそのTシャツのMapオブジェクトを返す
        const clothe = robot.takeoutWithRack(1, '青');
        // 取り出す服がなかった場合はtakeouts配列に追加しない
        if (!clothe) return;
        // 表示用配列に追加 => ロボットの下に描画される
        takeouts.push(clothe);
    });
    // [ラック不明、特徴だけ分かっている]ボタン
    const takeoutButton3 = setButton('ラック不明、"ロボット"という特徴だけ分かっている', {
        x: 20,
        y: 400
    });
    // ボタンのクリックで
    takeoutButton3.mousePressed(() => {
        // ロボットにTシャツのラックにある1番めのシャツを取り出すように命令する
        // ロボットはそのTシャツのMapオブジェクトを返す
        const clothe = robot.takeoutWithHint('ロボット');
        // 取り出す服がなかった場合はtakeouts配列に追加しない
        if (!clothe) return;
        // 表示用配列に追加 => ロボットの下に描画される
        takeouts.push(clothe);
    });
}

function draw() {
    background(231, 237, 191);  // 背景
    robot.display(); // ロボット
    image(tShirtRackImage, 30, 50); // Tシャツのラック
    image(pantsRackImage, 30, 150); // ズボンのラック
    robot.displayRack(0); // rack0にあるMapのイメージとインデックス番号を描画
    robot.displayRack(1); // rack1にあるMapのイメージとインデックス番号を描画

    // ロボットが取り出した服を描画
    for (let i = 0; i < takeouts.length; i++) {
        image(takeouts[i].get('img'), 220 + i * 50, 250)
    }
}

// 文字列の配列とイメージの配列を受け取り、それらを関連づけたMapオブジェクトを作成し、
// Mapオブジェクトを入れた配列を返す
function clothesMapping(strArray, imageArray) {
    let arr = [];
    for (let i = 0; i < strArray.length; i++) {
        const map = new Map();
        map.set('str', strArray[i]); // strキー
        map.set('img', imageArray[i]); // imgキー
        arr.push(map);
    }
    return arr;
}

function setButton(label, pos) {
    const button = createButton(label);
    button.size(340, 30);
    button.position(pos.x, pos.y);
    return button;
}

class TakeoutRobot {
    constructor(x, y, img) {
        // 描画に必要な(x,y)位置とイメージ
        this.x = x;
        this.y = y;
        this.image = img;
        this.rack0 = [];
        this.rack1 = [];
        this.racks = [this.rack0, this.rack1];
    }

    // 描画
    display() {
        image(this.image, this.x, this.y);
    }

    // Tシャツとズボンのラック内容(イメージとインデックス番号)を
    // ラックイメージの手前に描画
    displayRack(rackIndex) {
        // ラックを特定
        const rack = this.racks[rackIndex];
        // rackIndex(0か1)と数値を使ってy位置を決める
        const y = (rackIndex + 1) * 60 + (rackIndex * 40);
        // ラック内のMapオブジェクトの数だけ繰り返し
        for (let i = 0; i < rack.length; i++) {
            // Mapの特定
            const map = rack[i];
            // そのMapが持っているイメージを取得
            const img = map.get('img');
            // 服を右にずらしつつ描画
            image(img, 90 + (img.width + 1) * i, y);
            // 配列内のインデックス番号も描画
            text(i, 105 + 44 * i, y + 55);
        }
    }

    // 特徴だけ覚えている
    takeoutWithHint(hint) {
        // racksの長さ分(=2)だけ繰り返す
        for (let i = 0; i < this.racks.length; i++) {
            // ラックの特定 rack0かrack1
            const rack = this.racks[i];
            // rack0かrack1の長さ分だけ繰り返す
            for (let j = 0; j < rack.length; j++) {
                // 配列内の文字列を特定
                const str = rack[j].get('str');
                // その文字列にhintが含まれていれば
                if (str.indexOf(hint) > -1) {
                    print('見つかりました!');
                    // その要素(文字列)を特定
                    const elm = rack[j];
                    // その要素をrackから削除
                    rack.splice(j, 1);
                    return elm;
                }
                else {
                    print('見つかりません。。。');
                }
            }
        }
        return false; // 見つからなかった
    }

    // 棚は分かっているが、預けたときの名前は覚えていない。特徴は覚えている
    takeoutWithRack(rackIndex, hint) {
        // rack0かrack1かを特定
        const rack = this.racks[rackIndex];
        // 与えられたラックを走査する
        for (let i = 0; i < rack.length; i++) {
            /// 配列内の文字列を特定
            const str = rack[i].get('str');
            // その文字列にhintが含まれていれば
            const index = str.indexOf(hint);
            if (index > -1) {
                print('見つかりました!');
                // その要素(文字列)を特定
                const elm = rack[i];
                // その要素をrackから削除
                rack.splice(i, 1);
                // 要素を返す
                return elm;
            }
            else {
                print('見つかりません。。。');
            }
        }
        return false; // 見つからなかった
    }


    // ラック番号と服(Map)を指定して、ロボットに服をあずける
    putClothes(rackNumber, clothes) {
            if (rackNumber === 0) {
                this.rack0.push(clothes);
            }
            else if (rackNumber === 1) {
                this.rack1.push(clothes);
            }
        }
        // 2つのラックの中身を出力して確認する
    getRack() {
        print(this.racks);
    }

    // ラックから服を取り出す
    // 服がどのラックの何番めの箱に入っているかが分かっている
    takeout(rackIndex, index) {
        // ラックの特定
        const rack = this.racks[rackIndex];
        // 服の特定
        const clothes = rack[index];
        // そこになければfalseを返してメソッドを終える
        if (clothes === undefined) return false;
        // 要求された服を取り出すので、当該ラックからその服を削除する
        // => rack0かrack1から、指定インデックス位置にある要素を1つ削除する
        rack.splice(index, 1);
        // 服を返す
        return clothes;
    }
}

下図は3つのボタンを上から順にクリックした後の画面です。

視覚化に当たっては、次のclothesMapping()関数を使って、服の文字列とそれに対応するイメージを、Mapオブジェクトを使って対応付けしています。これにより、StringオブジェクトのindexOf()メソッドとimage()関数が、同じMapオブジェクトに対して適用できるようになります。

// 文字列の配列とイメージの配列を受け取り、それらを関連づけたMapオブジェクトを作成し、
// Mapオブジェクトを入れた配列を返す
function clothesMapping(strArray, imageArray) {
    let arr = [];
    for (let i = 0; i < strArray.length; i++) {
        const map = new Map();
        map.set('str', strArray[i]); // strキー
        map.set('img', imageArray[i]); // imgキー
        arr.push(map);
    }
    return arr;
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA