トーマスプログラム

プログラミングやデザインのメモ

JavaScriptのmap関数の使い方

JavaScriptArray.prototype.mapは、配列の全ての要素に対して処理をしていき、その結果を新たな配列に返す関数。

var root = [1,4,9];

var array = root.map(Math.sqrt);

console.log(root);  // [1, 4, 9]
console.log(array); // [1, 2, 3]

また、自分でmap関数内の処理を書くこともできる。

var root = [1,4,9];

var array = root.map(function(row){ return row * 2;});

console.log(root);  // [1, 4, 9]
console.log(array);  // [2, 8, 18]

参考記事:Array.prototype.map() | MDN

オセロをMinMax法を導入する方法(JavaScript編)

JavaScriptでオセロを作っていて、そのオセロのCOMにMinMax法を導入したので方法を紹介。

ただし、コードは結構汚いです。

MinMax法とは?

MinMax法とは、オセロや将棋等のゲームで、コンピュータがどのように考えて手を指すかと言う思考方法(アルゴリズム)の1つ。

f:id:toumasuxp:20181213104307g:plain 画像:リバーシプログラムの作り方

上記の画像から分かるように、オセロには様々な手順の可能性が考えられるが、それぞれの局面に評価を付けていき、どの手順が最善かを調べるのがMinMax法となる。

具体的には、「自分(COM)の手を考えるときは最も評価が高い手を選び、相手の手を考えるときは最も評価が低い、つまりCOM側が指されて一番イヤな手を選ぶ」と言うのがMinMax法だと言える。

実際に書いたコード

実際に書いたコードは、以下の通り。

function minMaxCom(comColor, comBoard, putPos, thinkDepth, isPass, isComTurn, isRootFunc) {
  let comCurrentBoard = [];
      comCurrentBoard = comBoard.slice().map( function(row){ return row.slice(); });
    if(putPos) {
      putStone(-comColor, comCurrentBoard, putPos[0], putPos[1]);
    }

  let value,
      max      = -64,
      min      = 64,
      can_move = false,
      select_pos = [];

  if( thinkDepth === 0 ) {
    return countComStone(comCurrentBoard);
  }

  for(let x = 1; x <= BOARD_WIDTH; x++) {
    for(let y = 1; y <= BOARD_WIDTH; y++) {
      if( isValidPutStone(comColor, comCurrentBoard, x, y) ) {
        can_move = true;
        value = minMaxCom(-comColor, comCurrentBoard, [x, y], thinkDepth - 1, false, !isComTurn, false);
        if(isComTurn) {

          if( value > max ) {
            max = value;
            select_pos = [x, y];
          }
        } else {
          if( value < min ) {
            min = value;
            select_pos = [x, y];
          }
        }
      }
    }
  }
  if(!can_move) {
    if(isPass) {
      return countComStone(comCurrentBoard);
    } else {
      if(isComTurn) {
        max = minMaxCom(-comColor, comCurrentBoard, false, thinkDepth - 1, true, !isComTurn, false);
      }else {
        min = minMaxCom(-comColor, comCurrentBoard, false, thinkDepth - 1, true, !isComTurn, false);
      }
    }
  }

  if(isRootFunc) {
    return select_pos;
  }

  if(isComTurn) {
    return max;
  } else {
    return min;
  }
}

凄く汚いコードだが、要は「再帰関数を使ってどんどん手を深く読んでいき、最終的に石の多さを評価として返している」ようにしている。

ちなみに、リバーシプログラムの作り方のページでは、以下のコードがC言語で書かれていた。

static int Com_EndSearch(Com *self, int in_depth, int in_color, int in_opponent, int in_pass, int *out_move)
{
    int x, y;
    int value, max = -MAX_VALUE;
    int can_move = 0;
    int move;

    if (in_depth == 0) {
        self->Node++;
        return Board_CountDisks(self->Board, in_color) - Board_CountDisks(self->Board, in_opponent);
    }
    *out_move = NOMOVE;
    for (x = 0; x < BOARD_SIZE; x++) {
        for (y = 0; y < BOARD_SIZE; y++) {
            if (Board_Flip(self->Board, in_color, Board_Pos(x, y))) {
                if (!can_move) {
                    *out_move = Board_Pos(x, y);
                    can_move = 1;
                }
                value = -Com_MidSearch(self, in_depth - 1, in_opponent, in_color, 0, &move);
                Board_Unflip(self->Board);
                if (value > max) {
                    max = value;
                    *out_move = Board_Pos(x, y);
                }
            }
        }
    }
    if (!can_move) {
        if (in_pass) {
            *out_move = NOMOVE;
            self->Node++;
            max = Board_CountDisks(self->Board, in_color) - Board_CountDisks(self->Board, in_opponent);
        } else {
            *out_move = PASS;
            max = -Com_MidSearch(self, in_depth, in_opponent, in_color, 1, &move);
        }
    }
    return max;
}

上記のコードは厳密にはNegaMax法と言うMinMax法の兄弟のような方法で書いているが、JavaScriptで書く場合もこれぐらいシンプルに持っていきたい。

JavaScriptのsetTimeOut関数には戻り値があるよって言う話

色々調べてみると、JavaScriptsetTimeOut関数には戻り値があることが分かったのでメモをしておきます。

setTimeOutの戻り値とは

WindowOrWorkerGlobalScope.setTimeout() | MDN」の記事によると、setTimeOut()の戻り値はタイマーを識別する正の整数と分かります。

戻り値 timeoutID は、setTimeout() を呼び出して作成したタイマーを識別する正の整数値です。この値は、タイムアウトをキャンセルするために clearTimeout() へ渡すことができます。

上記の引用文の様に、戻り値timeoutIDをタイムアウトをキャンセルするclearTimeOut()関数に渡すことでタイマーをキャンセルできる。

具体的な使い道

つまり、setTimeOut関数を使うことで、単に「何秒後に関数を呼び出す」と言う使い方以外に「何秒間何もしなければ関数を呼び出す」と言う使い方も可能と言うわけだ。

具体的には以下のコードが挙げられる。

let timeOutId;

function CheckTime() {
  if ( timeOutId != null) {
    clearTimeOut(timeOutId);
  }
  
  timeOutId = setTimeOut(  fooFunc, 1000);
}

hogeDom.addEventListener('click', CheckTime, false);

上記のコードではhogeDomをクリックして、もし1秒間(1000ms)再度クリックしなければfooFunc関数を呼び出すという動きをしてくれる。

GitHubで新しいプロジェクトを追加する方法

f:id:toumasuxp:20181212130846j:plain

Newボタンを押す。

f:id:toumasuxp:20181212130918j:plain

コピーを押す。

git init
git  remote add  origin gitのurl

すでにREADME.mdLICENSEファイルを作成している場合は以下のコマンドをする。

 git pull origin master
git add .
git commit -m 'first'

`` git push origin master

JavaScriptの配列で参照渡しではなく値渡しをする方法と注意点

Javascriptの配列において参照渡しではなく値渡しをする時に苦労したので、メモを書いておきます。

JavaScriptの配列で値渡しにする方法

JavaScriptの配列で値渡しにする方法は、主に以下の2つがある。

sliceメソッドを使う

まずは、以下の様にsliceメソッドを使う事が挙げられる。

array1 = [1,3,5,4];

array2 = array1.slice();

concatメソッドを使う

次に、concatメソッドを使うことも考えられる。

concatは2つの配列を合体させて新しい配列を作るメソッドだが、空の配列に対してコピーしたい配列をconcatさせると。値渡しのように配列をコピーできる。

array1 = [1,3,5,4];

array2 = [].concat(array1);

1次元配列の値渡しであれば、上記の2つの内どちらかのメソッドを使えば良い。

2次元配列以上の値渡しをしたい時の注意点

sliceやconcatは便利だが、あくまでも1次元配列の時に使えるもので、2次元以上の場合は2次元目からは参照渡しになってしまい思いも寄らない動きをしてしまう。

MDNのサイトにも以下の様に記されている。

slice は元の配列を変更しませんが、元の配列から取り出された要素のコピーを含むシャローコピー( 1 段階の深さのコピー)を返します。元の配列の要素は以下のようにして新しい配列にコピーされます。

参考記事:Array.prototype.slice() - JavaScript | MDN

concat は this や引数として与えられた配列を変更しませんが、その代わりに元の配列から結合させた同じ要素のコピーを含むシャローコピー (1 次元の配列要素までの浅いコピー) を返します。

参考記事:Array.prototype.concat() - JavaScript | MDN

では、2次元配列でも値渡しをする場合はどうすれば良いかと言うと、以下の様にするとできる。

array1 = [[1,2],[34,6],[5,44]];

array2 = array1.slice().map( function(row){ return row.slice(); });

上記では、mapメソッドを使って2次元目の配列にもsliceメソッドを使うことで、2次元目の配列も値渡しになるようにしている。

javaScriptにおけるfromCharCode()とcharCodeAt()の使い方

JavaScriptで文字列を扱う時にfromCharCode()charCodeAt()が便利だったので、まとめておきます。

fromCharCode()とcharCodeAt()の使い方

fromCharCode()は、ASCiiコードの番号を指定することで文字列を表示できるString.prototypeの関数で、charCodeAt()は文字のASCiiコードの番号を表示できる関数となる。

以下は、具体例を挙げてみる。

fromCharCode()の具体例

var char = String.fromCharCode( 48 );

console.log(char);  // "0"と表示

var str = String.fromCharCode(97, 98, 99);

console.log(str);  // "abc"と表示

上記の様にUnicode値にあった文字を返してくれるのが、fromCharCode()関数だ。引数を複数指定することで文字列を生成することができる。

fromCharCode()では、16bit、つまり2byteまでの文字に対応しており、絵文字などを表示させることはできない。もし、絵文字などもUnicode値で表示したい場合はfromCodePoint()関数を使えば良い。

ただ、fromCharCodeの使い道は「文字列の0 ~ 9を順番に表示させたい」とか「アルファベットを順番に表示させたい」と言う使い方が多いと思うので、fromCharCode()で十分だと思う。

参考記事:String.fromCharCode() | MDN

参考記事:String.fromCodePoint() | MDN

参考記事:ASCIIコード表

charCodeAtの具体的な使い方

charCodeAt()の具体的な使い方は以下の通りだ。

var code = 'a'.charCodeAt();

console.log(code);  // 97

var code2 = 'abc'.charCodeAt(2);

console.log(code2); // 99

上記の様にcharCodeAt()は指定した文字列の中の1文字を選び、その1文字のUnicode値を返してくれる関数だ。 引数を指定していなければ、引数には0が入る。

似たような関数にcharAt()があるが、これは文字列の内1文字を抜き出して表示する関数なので、役割が違う。

参考:String.prototype.charCodeAt() | MDN

参考:String.prototype.charAt() | MDN

fromCharCodeとcharCodeAtはいつ役に立つのか?

この両者の関数は、主に「アルファベットのa~zを順番に表示させたい」などの時に役立つだろう。

例えば、以下のコードがある。

for(let start = 'a'.charCodeAt(),  end = 'z'.charCodeAt(); start <= end; start++) {
  let char = String.fromCharCode(start);
  console.log(char);
}

上記のコードを実行すると、以下の結果となる。

"a"
"b"
"c"
  .
  .
"y"
"z"

ちなみに、C言語のように以下の様にできないのかと試したが、結果はaしか表示されなかった。

for(let start = 'a',  end = 'z'; start <= end; start++) {
  console.log(start);
}

// 'a'しか表示されない

bundle install時に「An error occurred while installing sqlite3 (1.3.13), and Bundler cannot continue.」と言うエラーが出たときの対策法

railsアプリを作成するためにbundle installをする際に

An error occurred while installing sqlite3 (1.3.13), and Bundler cannot continue.
Make sure that `gem install sqlite3 -v '1.3.13' --source 'https://rubygems.org/'` succeeds before bundling.

と言うエラーが出たので、その解決策をメモしておきます。

上記のエラーはどんな意味?

An error occurred while installing sqlite3 (1.3.13), and Bundler cannot continue.
Make sure that `gem install sqlite3 -v '1.3.13' --source 'https://rubygems.org/'` succeeds before bundling.

先ほどのエラーは「sqlite3 (version 1.3.13 )をインストールしている時にエラーが起こったよ。だからbundle installを続けられないよ。だから、再度bundle installをする前にgem install sqlite3 -v '1.3.13' --source 'https://rubygems.org/' となっているか確かめて」と言う意味です。

このエラーよりも上の方に出ているエラー文を確認してみると、以下の様に表示されているはずです。

current directory: /usr/local/src/bundles/tutorial/ruby/2.4.0/gems/sqlite3-1.3.12/ext/sqlite3
/home/username/.rbenv/versions/2.4.2/bin/ruby -r ./siteconf20181211-17487-1eyzf34.rb extconf.rb
checking for sqlite3.h... no
sqlite3.h is missing. Try 'brew install sqlite3',
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
and check your shared library search path (the
location where your sqlite3 shared library is located).
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

特に大事なのが4行目以降で

 Try 'brew install sqlite3',
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
and check your shared library search path (the
location where your sqlite3 shared library is located).

と書かれていることから、CentOSの環境ではyum install sqlite-develのコマンドを行って、sqlite3の依存関係を解決するパッケージをインストールすればいけそうです。

(Ubuntu等の他のOSであれば、それぞれのコマンドを使ってsqlite3の依存関係を解決するパッケージをインストールしてください)

早速、以下のコマンドを実行。

sudo yum install sqlite-devel

その後にbundle installを実行すると、今度は上手くいきました。