Segmentation Faultぐ

Segmentation Fault

コアダンプの数だけ強くなれるよ。

さくらVPS + nginx + SSL証明書でHTTPS通信

さくらVPSにnginx + wordpressの環境を構築してちょこちょこ遊んでおります。近いうちにHTTPSで接続できるようにもしたいなぁと思ってたのですが、手つかずのまま大分時間が経過してしまいました。

www.segmentation-fault.xyz

今回はSSL証明書を導入してHTTPSで接続できるように設定していきます。

環境(前提)

  • CentOS 7 (さくらVPS)
  • Wordpressの環境を設定済み(httpでアクセス可能)


全体の流れ

作業的には下記3ステップとなります。

 1.サーバ上で秘密鍵とCSRの作成
 2.SSL証明書の購入
 3.サーバに証明書を設置


事前準備

まずは事前準備です。下記ソフトをインストールします。

$ sudo yum install openssl
$ sudo yum install mod_ssl


秘密鍵とCSRの作成

ココでは下記作業を行います。

  1. 秘密鍵作成のための擬似乱数ファイル(rand.dat)を生成
  2. 疑似乱数ファイルから秘密鍵を生成
  3. 秘密鍵からCSRを作成
# 疑似乱数ファイルを生成
$ cd /etc/pki/nginx
$ sudo openssl md5 /usr/bin/* > rand.dat
# 秘密鍵を生成
$ sudo openssl genrsa -rand rand.dat -des3 2048 > newkey.pem
61643 semi-random bytes loaded
Generating RSA private key, 2048 bit long modulus
.......................................+++
........................................................+++
e is 65537 (0x10001)
Enter pass phrase: <任意のパスワードを入力>
Verifying - Enter pass phrase: <再度同じパスワードを入力>
# CSRを生成 (入力内容は仮だよ)
$ sudo openssl req -new -key newkey.pem -out newcsr.pem
Enter pass phrase for newkey.pem: <秘密鍵生成時のパスワードを入力>
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:Arakawa
Organization Name (eg, company) [Default Company Ltd]:Sakura
Organizational Unit Name (eg, section) []:VPS
Common Name (eg, your name or your server's hostname) []:centos7.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$
$ cat newcsr.pem
-----BEGIN CERTIFICATE REQUEST-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA  秘密だよ  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE REQUEST-----


CSR生成時の設定項目は下記です。下記以外の項目については入力は任意です(Enter連打でOK)。

CSR生成時の入力項目 記載内容 設定例
Country Name (2 letter code) [XX]: 国別番号 JP
State or Province Name (full name) : 都道府県名 Tokyo
Locality Name (eg, city) [Default City]: 市区町村名 Arakawa
Organization Name (eg, company) [Default Company Ltd]: 組織名 Sakura
Organizational Unit Name (eg, section) : 部門名 VPS
Common Name (eg, your name or your server’s hostname) []: コモンネーム(FQDN) centos7.com


入力内容はご自身の環境に合わせて変更ください。作成した秘密鍵はなくさないように。
ここで生成したnewcsr.pemの中身がSSL証明書の購入時に必要になりますのでtextファイル等にコピペしましょう。

SSL証明書の購入

こちらからSSL証明書を購入します。今回はRapidSSLを選択します。

ssl.sakura.ad.jp

f:id:segmentation-fault:20170923232146p:plain

f:id:segmentation-fault:20170923232318p:plain

プランと決済方法を選択します。私は3年プランを選択しました。

f:id:segmentation-fault:20170923232457p:plain

続いて先ほど生成したCSRの内容を入力します。

f:id:segmentation-fault:20170923234858p:plain


後はさくらインターネットさんからメールが来るのを待ちます。

サーバー証明書の設置

サーバー認証


SSL証明書の購入手続きが完了すると下記のようなメールが届きます(抜粋)。会員ページより認証ファイルを取得して指定のパスに配置しましょう。

お申込みいただきましたSSLサーバ証明書の申請が完了いたしましたので、お知
らせいたします。

  ================================================================
  《 SSLサーバ証明書の情報 》

     サーバ証明書種別:SSL ラピッドSSL(3年)
     サービスコード  :xxxxxxxxxxxx
     コモンネーム    :xxxxxxxxxxxxxxxxx
  ================================================================

引き続き、認証局より申請いただいたドメインの使用権の確認がございますので、
以下の手順をご確認の上、認証ファイルのアップロードをお願いいたします。



    ※認証ファイルは以下のいずれかのURLに配置
      認証ファイル名は、fileauth.txtです

    例)
      http://<申請時に指定したFQDN(コモンネーム)>/.well-known/pki-validation/fileauth.txt

まず、認証ファイルを取りに行きます。

f:id:segmentation-fault:20170924101124p:plain

f:id:segmentation-fault:20170924100038p:plain

続いて認証ファイルを設置します。

# 環境により変わる

$ sudo mkdir -p /var/www/wordpress/.well-known/pki-validation/
$ sudo mv fileauth.txt /var/www/wordpress/.well-known/pki-validation/


設置後にブラウザからアクセスして中身が見えたらOKです。この状態でしばらく待つと認証が完了し再度メールが届きます。認証が終ったらサーバー上からファイルは削除しましょう。

証明書の設置


認証が完了すると下記のような、SSLサーバ証明書が発行された旨のメールが届きます。

お申込みいただきましたSSLサーバ証明書の発行が完了いたしましたので、以下
の通りお知らせいたします。

  ================================================================
  《 SSLサーバ証明書の情報 》

     サーバ証明書種別:SSL ラピッドSSL(3年)
     サービスコード  :xxxxxxxxxxxx
     コモンネーム  :xxxxxxxxxxxxxxxxxxx
     証明書有効期限 :2020年09月23日
  ================================================================

つきましては、以下に手順を記載いたしますので、お客様にてご利用サーバへ
の導入作業を行っていただきますよう、お願いいたします。
  • SSLサーバ証明書
  • 中間CA証明書

メールの記載に従って上記2点を取ってきます。会員ページよりSSLサーバ証明書、メールに記載されているリンクから中間CA証明書をそれぞれ取得します。



SSLサーバ証明書

f:id:segmentation-fault:20170924102936p:plain

f:id:segmentation-fault:20170924113101p:plain

認証が完了していると状態が変わり利用期間が表示されています。


中間CA証明書

f:id:segmentation-fault:20170924103442p:plain

取得した証明書をサーバー上で結合し配置します。

$ sudo cat server.crt ca.crt > serverca.crt
$ vim serverca.crt # 証明書の間に改行を入れる
$ cat serverca.crt
-----BEGIN CERTIFICATE-----

SSLサーバ証明書

-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----

中間CA証明書

-----END CERTIFICATE-----


これで必要なファイルはそろいました。

nginxの設定


秘密鍵とSSLサーバ証明書をnginxのコンフィグに合わせて配置します。

# 環境により異なる

$ sudo mkdir -p /etc/pki/nginx/private/
$ sudo cp serverca.crt /etc/pki/nginx/server.crt
$ sudo cp newkey.pem /etc/pki/nginx/private/server.key
$
$ cat /etc/nginx/conf.d/wordpress.conf
server {
    listen       80;
    server_name  .<あなたのサーバ>;
    rewrite     ^ https://<あなたのサーバ>$request_uri?;
}

server {
    listen       443 ssl http2;
    #listen       [::]:443 ssl http2;
    server_name  <あなたのサーバ>;
    root         /var/www/wordpress;

    ssl_certificate "/etc/pki/nginx/server.crt";
    ssl_certificate_key "/etc/pki/nginx/private/server.key";

    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_protocols TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

    index index.php;

    location ~* /wp-config.php {
        deny all;
    }


    location ~ \.php$ {
        fastcgi_pass    127.0.0.1:9000;
        fastcgi_index   index.php;
        fastcgi_param   SCRIPT_FILENAME /var/www/wordpress$fastcgi_script_name;
        fastcgi_param   PATH_INFO $fastcgi_script_name;
        include         fastcgi_params;
    }
}


このままnginxを再起動すると秘密鍵のパスワード入力が出来ずに失敗します。なので秘密鍵からパスフレーズを削除します。

$ sudo cd /etc/pki/nginx/private/
$ sudo cp server.key server.key.orig
$ sudo openssl rsa -in server.key -out server.key


nginxの設定が完了したらコンフィグ内容を確認して再起動します。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$
$ sudo systemctl restart nginx.service 



接続確認

nginxを再起動後にhttps付きでサイトにアクセスし、緑色の鍵マークが表示されればOKです。

f:id:segmentation-fault:20170924110532p:plain


鍵マークが表示されない(ブラウザに怒られる)、アクセスできない等が発生した場合はnginxのコンフィグ、ファイアウォールの設定、Wordpressの設定を見直してみてください。



coincheckでビットコインを自動で定期購入してみる

今回はcoincheckのAPIを使ってビットコインを購入するスクリプト(python)を作ってみます。 cronと組み合わせれば定期購入もできそうです。実行環境はLinux(CentOS 7)です。

ビットコイン取引高日本一の仮想通貨取引所 coincheck bitcoin

取引用のAPIキーを作成

APIを利用して取引を操作するにはAPIキーを作成する必要があるため先にやっておきます。coincheckにログイン後、設定からAPIキーを選択して下方の「新たにAPIキーを追加する」を選択しAPIキーを作成します。

f:id:segmentation-fault:20170922155233p:plain

許可する操作を選択できますので、必要なものにチェックを入れましょう。

f:id:segmentation-fault:20170922154946p:plain

ソースコード

取引用のAPIキーが用意できたので、下記のドキュメントを参考にスクリプトを作成していきます。 10000円分のビットコインを成り行きで購入するスクリプトです。

coincheck.com

# -*- coding: utf-8 -*-
import sys
import json
import requests
import time
import hmac
import hashlib

class ccapi:
    def __init__(self, key, key_secret, endpoint):
        self.key = key
        self.key_secret = key_secret
        self.endpoint = endpoint

    def get(self, path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.get(
                self.endpoint + path ,
                headers = self.__get_header(self.key, nonce, signature))

    def post(self, path, payload):
        payload = json.dumps(payload)
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path + payload

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.post(
                self.endpoint+path,
                data = payload,
                headers = self.__get_header(self.key, nonce, signature))

    def delete(self,path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.delete(
                self.endpoint+path,
                headers = self.__get_header(self.key, nonce, signature))

    def __get_header(self, key, nonce, signature):
        headers = {
            'ACCESS-KEY': key,
            'ACCESS-NONCE': nonce,
            'ACCESS-SIGNATURE': signature,
            'Content-Type': 'application/json'
        }

        return headers

if __name__ == '__main__':

    argv = sys.argv
    argc = len(argv)

    if (argc != 3):
        print('usage: python %s <key> <secret key>' % argv[0])
        quit()

    key = argv[1]
    key_secret = argv[2]
    endpoint = 'https://coincheck.com'

    cc = ccapi(key,key_secret,endpoint)

    # 販売レートの確認(特に意味は無い)
    salerate = '/api/rate/btc_jpy'
    rate = cc.get(salerate).json()
    print('BTC-JPY : \%s' % rate['rate'])

    # \10000分の成り行き買い注文
    order = '/api/exchange/orders'
    payload = {
       "pair": "btc_jpy",
       "order_type": "market_buy",
       "market_buy_amount": 10000,
    }
    #result = cc.post(order, payload).json()
    #print(result)

    # 注文キャンセル
    #order = '/api/exchange/orders/[id]'
    #result = cc.delete(order)
    #print(result)


実行結果

恐る恐る実行してみるとリクエストは成功した模様。早速、coincheckにログインして結果を確認します。

$ python ccauto.py 'アクセスキー' 'シークレットアクセスキー'
BTC-JPY : \414875.0
{'stop_loss_rate': None, 'id': xxxxxxxx, 'rate': None, 'amount': None, 'pair': 'btc_jpy', 'order_type': 'market_buy', 'market_buy_amount': '10000.0', 'success': True, 'created_at': '2017-09-22T05:53:30.127Z'}


スマホアプリで取引履歴と残高を確認。

f:id:segmentation-fault:20170922160459j:plain

f:id:segmentation-fault:20170922160509j:plain


キッチリ10000円分の購入が出来ていました。(一安心)

cronと組み合わせて定期購入

ビットコインが欲しいけど価格変動のリスクは抑えたい… そんな時に頼りになるのがドル・コスト平均での定期購入(積立)ですね。

cronと組み合わせれば簡単に実現できます。

まぁ自分で作らずとも他社の取引所には仮想通貨の積立サービスはあったりしますが趣味の世界なのでDon’t care。

zaif.jp

まとめ

実際にやってみると、驚くほど簡単にビットコインの購入が出来ることが分かります。(もちろん売却も) 入力する桁を間違えたりすると大変なことになってしまうのでチェック機能はキチンと作りたいですね。



canvasで少しずつ作るブロック崩し(5/5)

前回でひとまずブロック崩しと呼べるレベルになりました(たぶん)。今回はバーを台形の形にして角に当たると横方向に跳ね返る機能を追加します。また、ソースコードの整理、当たり判定の改善などを加えてまとめます。

canvasで作るブロック崩し

左クリック: バーを下げる
右クリック: ボール出現


当たり判定の見直し

ボールとブロックの当たり判定を整理します。

実装イメージは下記のようになります。

f:id:segmentation-fault:20170918182928p:plain


これをソースコードで下記のように表現しました。

function isHitBlock(ball) {
  
  hit = HIT.NO;
  
  for (var bi = 0; bi < BLOCK.length; bi++) {
    
    if (BLOCK[bi] == null) {
      continue;
    }
    
    if (BLOCK[bi].Alive == 0) {
      continue;
    }
    
    // ブロックとの接触判定
    var bx = BLOCK[bi].X + BLOCK[bi].WIDTH/2;
    var by = BLOCK[bi].Y + BLOCK[bi].HEIGHT/2;
    if ((Math.abs(ball.X - bx) < ball.RADIUS + BLOCK[bi].WIDTH/2) &&
        (Math.abs(ball.Y - by) < ball.RADIUS + BLOCK[bi].HEIGHT/2)) {
      
      BLOCK[bi].Alive = 0;
      ball.Vy *= (-1);
      
      hit = HIT.BLOCK;
    }
  }
  
  return hit;
}


バーを台形に変更

普通の長方形だと面白みがないので台形に変えていきます。一度長方形を描いてその上から背景と同じ色の直角二等辺三角系を両端に上塗りすることで描きます。

// バーの描画
function drawBar() {
  var delay = 1;
  
  BAR.X = (mouseX + delay * BAR.X) / (delay+1);
  
  // 色設定
  ctx.fillStyle = 'rgb(255,255,255)';
  
  // バー(長方形)の描画
  ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT);
  
  
  // 両端を黒塗りして台形にする
  ctx.beginPath();
  ctx.fillStyle = 'rgb(0, 0, 0)';
  ctx.moveTo(BAR.X, BAR.Y);
  ctx.lineTo(BAR.X+BAR.HEIGHT, BAR.Y);
  ctx.lineTo(BAR.X, BAR.Y+BAR.HEIGHT);
  ctx.fill();
  
  ctx.moveTo(BAR.X+BAR.WIDTH, BAR.Y);
  ctx.lineTo(BAR.X+BAR.WIDTH-BAR.HEIGHT, BAR.Y);
  ctx.lineTo(BAR.X+BAR.WIDTH, BAR.Y+BAR.HEIGHT);
  ctx.fill();
  ctx.closePath();
};


合わせてボールとバーの当たり判定に左右の両端とぶつかった場合の条件を加えます。

  // バー接触(両端)
  var cxl = BAR.X + BAR.HEIGHT/2;
  var cyl = BAR.Y + BAR.HEIGHT/2;
  var cxr = BAR.X + BAR.WIDTH - BAR.HEIGHT/2;
  var cyr = BAR.Y + BAR.HEIGHT/2;
  var hit = HIT.NO;
  
  if ((Math.abs(ball.X - cxl) < ball.RADIUS + BAR.HEIGHT/2) &&
      (Math.abs(ball.Y - cyl) < ball.RADIUS + BAR.HEIGHT/2)) {
      
    hit = HIT.BAR_LEFT;
    
  } else if ((Math.abs(ball.X - cxr) < ball.RADIUS + BAR.HEIGHT/2) &&
      (Math.abs(ball.Y - cyr) < ball.RADIUS + BAR.HEIGHT/2)) {
              
    hit = HIT.BAR_RIGHT;
    
  } else if ((BAR.X <= ball.X && ball.X <= BAR.X + BAR.WIDTH) &&
             (Math.abs(ball.Y - BAR.Y) <= BAR.HEIGHT)) {
    
    hit = HIT.BAR_CENTER;
    
  } else {
    
    hit = HIT.NO;
  }


また、バーの両端に当たった場合はボールをななめ45°上方に跳ね返すようにします。

    // バー接触後の速度計算
    ball.Vy = (ball.M * ball.Vy - BAR.M * BAR.Vy) / ball.M;
    
    // Vyが初速度より減速した場合は、初速度に戻す
    ball.Vy = Math.abs(ball.Vy) > ball.Vy0 ? (ball.Vy * BAR.E * (-1)) : (ball.Vy0 * (-1));
    
    switch(hit) {
      case HIT.BAR_LEFT:
        ball.Vx = ball.Vy * Math.cos(Math.PI * (1/4));
        ball.Vy = ball.Vy * Math.sin(Math.PI * (1/4));
        break;
      case HIT.BAR_RIGHT:
        ball.Vx = ball.Vy * Math.cos(Math.PI * (3/4));
        ball.Vy = ball.Vy * Math.sin(Math.PI * (3/4));
        break;
      case HIT.BAR_CENTOER:
      default:
        break;
    }


ソースコード

<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript">

var canvas;
var ctx;
var mouseX;
var mouseY;
var timerID;

// フィールドの情報
var FIELD = {
  'HEIGHT'  : 0,
  'WIDTH'   : 0,
  'GRAVITY' : 588, // 重力加速度(0.9 * FPS)
  'FPS'     : 60,  // frame per second
  'E'       : 0.7, 
  'E0'      : 10,
};

// バーの情報
var BAR = {
  'HEIGHT' : 10,
  'WIDTH'  : 65,
  'UNDER'  : 25,
  'X'      : 0,
  'Y'      : 0,
  'PUSH'   : 10,
  'Vx'     : 0,
  'Vx0'    : 0,
  'Vy'     : 0,
  'Vy0'    : 100,
  'M'      : 5,
  'E'      : 0.7,
  'E0'     : 10,
};

// ボールの情報
var BALL = [];
var Ball = function() {
  this.Alive  = 0;
  this.X      = 0;
  this.Y      = 0;
  this.Vy     = 0;
  this.Vx     = 0;
  this.Vx0    = 50;
  this.Vy0    = 200;
  this.RADIUS = 5;
  this.M      = 1;
  this.HUE    = 0.5;
};

// ブロックの情報
var BLOCK = [];
var Block = function() {
  this.Alive  = 0;
  this.WIDTH  = 30;
  this.HEIGHT = 10;
  this.X      = 0;
  this.Y      = 0;
  this.Red    = 255;
  this.Green  = 255;
  this.Blue   = 255;
};

// 衝突種別
var HIT = {
  WALL_LEFT  : 1,
  WALL_RIGHT : 2,
  CEILING    : 3,
  FLOOR      : 4,
  BAR_LEFT   : 5,
  BAR_RIGHT  : 6,
  BAR_CENTER : 7,
  BLOCK      : 8,
  NO         : 9
};

// 初期化処理
function initialize() {
  canvas = document.getElementById('canvas');
  if(!canvas && !canvas.getContext) {
    return false;
  }
  
  // キャンバス作成
  ctx = canvas.getContext('2d');
  FIELD.WIDTH = ctx.canvas.width ;
  FIELD.HEIGHT = ctx.canvas.height;
  
  // バーの設定
  mouseX = FIELD.WIDTH/2;  // バーの初期位置は中心
  BAR.X = mouseX;
  BAR.Y = FIELD.HEIGHT-BAR.UNDER;
  
  // ブロックの生成
  createBlocks();
  
  // 各種イベント設定
  canvas.addEventListener('mousemove', getMouseCoordinate, false);
  canvas.addEventListener('mousedown', pushBar, false);
  canvas.addEventListener('mouseup', popBar, false);
  canvas.addEventListener('contextmenu', putBall, false);
  
  // 描画開始
  timerID = setInterval(drawField, 1000/FIELD.FPS);
};


// マウス座標の更新
function getMouseCoordinate(e) {
  var rect = e.target.getBoundingClientRect();
  mouseX = Math.floor(e.clientX - rect.left);
  mouseY = Math.floor(e.clientY - rect.top);
};

// ボールの生成
function putBall(e) {
  e.preventDefault();
  
  // ボールの初期位置は中心
  var tail = BALL.length;
  BALL[tail] = new Ball();
  BALL[tail].Alive = 1;
  BALL[tail].X = BAR.X;
  BALL[tail].Y = FIELD.HEIGHT- (BAR.UNDER + BAR.HEIGHT);
  BALL[tail].Vx = BALL[tail].Vx0;
  BALL[tail].Vy = BALL[tail].Vy0 * (-1);
  
};

// バーの収縮
function pushBar(e) {
  if (!e.pageX) {
    e = event.touches[0];
  }
  
  if (e.button == 0) {
    setTimeout(pushBarEvent, 1000/FIELD.FPS);
  }
};

// バーの反発
function popBar(e) {
  if (!e.pageX) {
    e = event.touches[0];
  }
  
  if (e.button == 0) {
    setTimeout(popBarEvent, 1000/FIELD.FPS);
  }
};

// バーの収縮処理
function pushBarEvent() {
  if (BAR.Y < FIELD.HEIGHT - BAR.UNDER + BAR.PUSH) {
    BAR.Vy = BAR.Vy0;
    BAR.Y += BAR.Vy * (FIELD.FPS/1000);
    setTimeout(pushBarEvent, 1000/FIELD.FPS);
  } else {
    BAR.Vy = 0;
    BAR.Y = FIELD.HEIGHT - BAR.UNDER + BAR.PUSH;
  }
};

var BarTimer;
// バーの反発処理
function popBarEvent() {
  if (BAR.Y > FIELD.HEIGHT-BAR.UNDER) {
    BAR.Vy = BAR.Vy0 * (-1);
    BAR.Y  += BAR.Vy * (FIELD.FPS/1000);
    setTimeout(popBarEvent, 1000/FIELD.FPS);
  } else {
    BAR.Y = FIELD.HEIGHT-BAR.UNDER;
    setTimeout(resetBarSpeed, 100);
  }
};

function resetBarSpeed() {
  BAR.Vy = 0;
};

// ブロックの生成
function createBlocks() {
  var bxmax = 13;
  var bymax = 6;
  var btop  = 20;
  var bleft = 20;
  var bidx = 0;
  var bint = 5;
  
  for (var y = 0; y < bymax; y++) {
    var green = Math.floor(Math.random() * 256);
    for (var x = 0; x < bxmax; x++) {
      BLOCK[bidx] = new Block();
      BLOCK[bidx].X = x * BLOCK[bidx].WIDTH + btop + (x * bint);
      BLOCK[bidx].Y = y * BLOCK[bidx].HEIGHT + bleft + (y * bint);
      BLOCK[bidx].Alive = 1;
      BLOCK[bidx].Red = 255;
      BLOCK[bidx].Green = green;
      BLOCK[bidx].Blue = 128;
      bidx++;
    }
  }
  
};


// 画面の描画
function drawField() {
  
  calcBallP();
  
  drawBack();
  drawBall();
  drawBar();
  drawBlock();
  
};


// ボール位置計算
function calcBallP() {
  
  for (var i = 0; i < BALL.length; i++) {
    
    if (BALL[i] == null) {
      continue;
    }
    
    // 生存しているボールのみ計算
    if (BALL[i].Alive == 1) {
      
      var hit = HIT.NO;
      
      // 壁・天井・床 当たり判定
      if (hit == HIT.NO) {
        hit = isHitFrame(BALL[i]);
      }
      
      // バー当たり判定
      if (hit == HIT.NO) {
        hit = isHitBar(BALL[i]);
      }
      
      // ブロック当たり判定
      if (hit == HIT.NO) {
        hit = isHitBlock(BALL[i]);
      }
      
      // ボールの速度更新
      // Y成分
      BALL[i].Vy += FIELD.GRAVITY * (1/FIELD.FPS);
      BALL[i].Y  += BALL[i].Vy * (1/FIELD.FPS);
      
      // X成分
      BALL[i].X += BALL[i].Vx * (1/FIELD.FPS);
    }
  }
  
  deleteAllDeadBall();
};

function isHitFrame(ball) {
  
  var hit = HIT.NO;
  
  // 床・天井接触
  if (ball.Y <= 0 || FIELD.HEIGHT <= ball.Y) {
    if (ball.Y <= 0) {
      
      // 天井
      ball.Y = FIELD.E0;
      ball.Vy = ball.Vy * FIELD.E * (-1)
      
      hit =  HIT.CEILING;
    } else {
      // 床
      // 床に接触したボールは死亡
      ball.Alive = 0;
      ball.Vx = 0;
      ball.Vy = 0;
      
      hit =  HIT.FLOOR;
    }
  }
  
  if (hit == HIT.NO) {
    // 壁接触
    if (ball.X <= 0 || FIELD.WIDTH <= ball.X) {
      if (ball.X <= 0) {
        ball.X = FIELD.E0;
        hit = HIT.WALL_LEFT;
      } else {
        ball.X = FIELD.WIDTH - FIELD.E0;
        hit = HIT.WALL_RIGHT;
      }
      
      ball.Vx = ball.Vx * (-1);
    }
  }
  
  return hit;
}


function isHitBar(ball) {

  // バー接触(両端)
  var cxl = BAR.X + BAR.HEIGHT/2;
  var cyl = BAR.Y + BAR.HEIGHT/2;
  var cxr = BAR.X + BAR.WIDTH - BAR.HEIGHT/2;
  var cyr = BAR.Y + BAR.HEIGHT/2;
  var hit = HIT.NO;
  
  if ((Math.abs(ball.X - cxl) < ball.RADIUS + BAR.HEIGHT/2) &&
      (Math.abs(ball.Y - cyl) < ball.RADIUS + BAR.HEIGHT/2)) {
      
    hit = HIT.BAR_LEFT;
    
  } else if ((Math.abs(ball.X - cxr) < ball.RADIUS + BAR.HEIGHT/2) &&
      (Math.abs(ball.Y - cyr) < ball.RADIUS + BAR.HEIGHT/2)) {
              
    hit = HIT.BAR_RIGHT;
    
  } else if ((BAR.X <= ball.X && ball.X <= BAR.X + BAR.WIDTH) &&
             (Math.abs(ball.Y - BAR.Y) <= BAR.HEIGHT)) {
    
    hit = HIT.BAR_CENTER;
    
  } else {
    
    hit = HIT.NO;
  }
  
  if (hit != HIT.NO) {
    // バーとボールの境界でバタつきを防ぐための処置
    ball.Y = BAR.Y - BAR.E0;
    
    // バー接触後の速度計算
    ball.Vy = (ball.M * ball.Vy - BAR.M * BAR.Vy) / ball.M;
    
    // Vyが初速度より減速した場合は、初速度に戻す
    ball.Vy = Math.abs(ball.Vy) > ball.Vy0 ? (ball.Vy * BAR.E * (-1)) : (ball.Vy0 * (-1));
    
    switch(hit) {
      case HIT.BAR_LEFT:
        ball.Vx = ball.Vy * Math.cos(Math.PI * (1/3));
        ball.Vy = ball.Vy * Math.sin(Math.PI * (1/3));
        break;
      case HIT.BAR_RIGHT:
        ball.Vx = ball.Vy * Math.cos(Math.PI * (2/3));
        ball.Vy = ball.Vy * Math.sin(Math.PI * (2/3));
        break;
      case HIT.BAR_CENTOER:
      default:
        break;
    }
  }
  
  return hit;
}

function isHitBlock(ball) {
  
  hit = HIT.NO;
  
  for (var bi = 0; bi < BLOCK.length; bi++) {
    
    if (BLOCK[bi] == null) {
      continue;
    }
    
    if (BLOCK[bi].Alive == 0) {
      continue;
    }
    
    // ブロックとの接触判定
    var bx = BLOCK[bi].X + BLOCK[bi].WIDTH/2;
    var by = BLOCK[bi].Y + BLOCK[bi].HEIGHT/2;
    if ((Math.abs(ball.X - bx) < ball.RADIUS + BLOCK[bi].WIDTH/2) &&
        (Math.abs(ball.Y - by) < ball.RADIUS + BLOCK[bi].HEIGHT/2)) {
      
      BLOCK[bi].Alive = 0;
      ball.Vy *= (-1);
      
      hit = HIT.BLOCK;
    }
  }
  
  return hit;
}

// 死亡したボールを削除
function deleteAllDeadBall() {
  var isDeadBall = 1;
  while(isDeadBall != 0) {
    isDeadBall = 0;
    for (var i = 0; i < BALL.length; i++) {
      if (BALL[i] == null) {
        continue;
      }
      
      if(BALL[i].Alive == 0) {
        delete BALL[i];
        BALL.splice(i,1);
        isDeadBall = 1;
        break;
      }
    }
  }
}


function drawBack() {
  ctx.fillStyle = 'rgb(0, 0, 0)';
  ctx.fillRect(0, 0, FIELD.WIDTH, FIELD.HEIGHT);
};


// ボールの描画
function drawBall() {
  
  ctx.save();
  
  // 生存しているボールの数だけ描画
  for(var i = 0; i < BALL.length; i++) {
    
    if (BALL[i] == null) {
      continue;
    }
    
    if (BALL[i].Alive) {
      // 円の描画設定
      ctx.beginPath();
      ctx.arc(BALL[i].X, BALL[i].Y, BALL[i].RADIUS, 0, 2*Math.PI, true);
      ctx.closePath();
      
      // 色設定
      BALL[i].HUE += 0.5;
      ctx.strokeStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
      ctx.fillStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
      ctx.shadowColor = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
    }
    
    // 描画実行
    ctx.stroke();
    ctx.fill();
  
  }
  
  ctx.restore();
};

// バーの描画
function drawBar() {
  var delay = 1;
  
  BAR.X = (mouseX + delay * BAR.X) / (delay+1);
  
  // 色設定
  ctx.fillStyle = 'rgb(255,255,255)';
  
  // バー(長方形)の描画
  ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT);
  
  
  // 両端を黒塗りして台形にする
  ctx.beginPath();
  ctx.fillStyle = 'rgb(0, 0, 0)';
  ctx.moveTo(BAR.X, BAR.Y);
  ctx.lineTo(BAR.X+BAR.HEIGHT, BAR.Y);
  ctx.lineTo(BAR.X, BAR.Y+BAR.HEIGHT);
  ctx.fill();
  
  ctx.moveTo(BAR.X+BAR.WIDTH, BAR.Y);
  ctx.lineTo(BAR.X+BAR.WIDTH-BAR.HEIGHT, BAR.Y);
  ctx.lineTo(BAR.X+BAR.WIDTH, BAR.Y+BAR.HEIGHT);
  ctx.fill();
  ctx.closePath();
};

// ブロックの描画
function drawBlock() {
  
  for (var i = 0; i < BLOCK.length; i++) {
    if (BLOCK[i] == null) {
      continue;
    }
    
    if (BLOCK[i].Alive) {
      ctx.fillStyle = 'rgb('+ BLOCK[i].Red +','+ BLOCK[i].Green +','+ BLOCK[i].Blue +')';
      ctx.fillRect(BLOCK[i].X, BLOCK[i].Y, BLOCK[i].WIDTH, BLOCK[i].HEIGHT);
    }
  }
}

function reset_game() {
  var isBallRemain = 1;
  while(BALL.length != 0) {
    delete BALL[0];
    BALL.splice(0,1);
  }
  
  clearInterval(timerID);
  
  initialize();
}

// 初期化イベント
window.addEventListener('load', initialize, false);

</script>
</head>
<body>
<canvas id='canvas' width=500 height=300></canvas>
<form><input type="button" name="reset" value="Reset" onClick="reset_game()"></form>
<p>
左クリック: バーを下げる <br />
右クリック: ボール出現
</p>
</body>
</html>


まとめ

この程度の機能ですが、スキル不足もあり実装にだいぶ時間がかかってしまい…(^-^;) けれど実際に作ってみることで得る物はたくさんありました。

また、いずれJavascript/HTML/CSSの理解が深まった時に見返してみて改善を加えたいと思います。 今度作るときは残機やスコア表示、複数ステージ、ランキング機能、サウンドエフェクトなんかをつけてみたいですね。


関連ページ

canvasで少しずつ作るブロック崩し(1/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(2/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(3/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(その4) - Segmentation Fault




WebSocketを使ってcoincheckの板情報(BTC/JPY)をリアルタイムで表示する

はじめに

以前、coincheckのAPIをPythonから利用して仮想通貨の取引レートを取得をやってみました。本当はJavascriptで取得したかったのですが、同一参照元がうんぬんの制約(あまり理解できてない)によりサーバー側でアクセス拒否されてしまうことが分かり断念しました。

www.segmentation-fault.xyz

調べていく内にどうやらWebSocketを使って取得する方法もあることを知り、coincheckはβ版ですがWebSocketに対応していたので勉強もかねてcoincheckで公開されているAPIを使ってビットコインの取引情報をリアルタイムで取得してみました。

coincheck.com


WebSocketで取得した板情報(BTC/JPY)

bids
注文のレート, 注文量

asks
注文のレート, 注文量


ソースコード

あまり綺麗ではありませんが、下記のソースコードで実現しています。

<html>
<meta charset="UTF-8">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
$(function() {
  
  var webSocket = null;
  var bids_cnt = 0;
  var asks_cnt = 0;
  
  function open() {
    if (webSocket == null) {
      webSocket = new WebSocket("wss://ws-api.coincheck.com/");
      
      // イベントハンドラの設定
      webSocket.onopen = onOpen;
      webSocket.onmessage = onMessage;
      webSocket.onclose = onClose;
      webSocket.onerror = onError;
    }
    
  }
  
  // 接続イベント
  function onOpen(event) {
    // 板情報の購読開始
    webSocket.send(JSON.stringify({type: "subscribe", channel: "btc_jpy-orderbook"}));
  }
  
  // メッセージ受信イベント
  function onMessage(event) {
    
    if (event && event.data) {
      
      // JSONデータに変換
      var json = $.parseJSON(event.data);
      
      for (var i = 0; i < json[1].bids.length; i++) {
        $('#bids').append(json[1].bids[i][0] + ",  " + json[1].bids[i][1] + "<br/>");
        bids_cnt++;
      }
      
      for (var i = 0; i < json[1].asks.length; i++) {
        $('#asks').append(json[1].asks[i][0] + ",  " + json[1].asks[i][1] + "<br/>");
        asks_cnt++;
      }
      
      // 10行溜まったら一旦消去
      if (bids_cnt > 10) {
         $('#bids').empty();
         bids_cnt = 0;
      }
      
      if (asks_cnt > 10) {
         $('#asks').empty();
         asks_cnt = 0;
      }
    }
  }

  // エラーイベント
  function onError(event) {
    console.log("エラーが発生しました。");
  }

  // 切断イベント
  function onClose(event) {
    console.log("コネクションが切断しました。");
  }
  
  window.addEventListener('load', open);
  
});
</script>
</head>
<body>
<div class="orderbook">
<p style="font-size: 80%;">bids<br /> 注文のレート, 注文量</p>
<div id="bids" style="font-size: 80%;height: 250px;width:50%">
</div>
<p style="font-size: 80%;">asks<br /> 注文のレート, 注文量</p>
<div id="asks" style="font-size: 80%;height: 250px;width:50%">
</div>
</div>
</body>
</html>


このままだと表示がダサいのでデザインを改善して取引所風の表示にしたいですね。



canvasで少しずつ作るブロック崩し(4/5)


前回は右クリックでボールを出現、左クリックでバーを上下させボールを打ち返す機能を加えました。今回はいよいよブロックを作ってボールが当たると消える機能をつけてみます。


完成品

左クリック: バーを下げる
右クリック: ボール出現

ソースコード

<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript">
(function() {

  var canvas;
  var ctx;
  var mouseX;
  var mouseY;
  
  // バーの情報
  var BAR = {
    'HEIGHT' : 10,
    'WIDTH'  : 50,
    'UNDER'  : 25,
    'X'      : 0,
    'Y'      : 0,
    'PUSH'   : 10,
    'Vx'     : 0,
    'Vx0'    : 0,
    'Vy'     : 0,
    'Vy0'    : 150,
    'M'      : 5,
    'E'      : 0.7,
    'E0'     : 10,
  };
  
  // フィールドの情報
  var FIELD = {
    'HEIGHT'  : 0,
    'WIDTH'   : 0,
    'GRAVITY' : 588, // 重力加速度(0.9 * FPS)
    'FPS'     : 60,  // frame per second
    'E'       : 0.7, 
    'E0'      : 10,
  };
  
  // ボールの情報
  var BALL = [];
  var Ball = function() {
    this.Alive  = 0;
    this.X      = 0;
    this.Y      = 0;
    this.Vy     = 0;
    this.Vx     = 100;
    this.Vx0    = 0;
    this.Vy0    = 200;
    this.RADIUS = 5;
    this.M      = 1;
    this.HUE    = 0.5;
  };
  
  // ブロックの情報
  var BLOCK = [];
  var Block = function() {
    this.Alive  = 0;
    this.WIDTH  = 30;
    this.HEIGHT = 10;
    this.X      = 0;
    this.Y      = 0;
  };
  
  // 初期化処理
  function initialize() {
    canvas = document.getElementById('canvas');
    if(!canvas && !canvas.getContext) {
      return false;
    }
    
    // キャンバス作成
    ctx = canvas.getContext('2d');
    FIELD.WIDTH = ctx.canvas.width ;
    FIELD.HEIGHT = ctx.canvas.height;
    
    // バーの設定
    mouseX = FIELD.WIDTH/2;  // バーの初期位置は中心
    BAR.X = mouseX;
    BAR.Y = FIELD.HEIGHT-BAR.UNDER;
    
    // ブロックの生成
    createBlocks();
    
    // 各種イベント設定
    canvas.addEventListener('mousemove', getMouseCoordinate, false);
    canvas.addEventListener('mousedown', pushBar, false);
    canvas.addEventListener('mouseup', popBar, false);
    canvas.addEventListener('contextmenu', putBall, false);
    
    setInterval(drawField, 1000/FIELD.FPS);
  };
  
  
  // マウス座標の更新
  function getMouseCoordinate(e) {
    var rect = e.target.getBoundingClientRect();
    mouseX = Math.floor(e.clientX - rect.left);
    mouseY = Math.floor(e.clientY - rect.top);
  };
  
  // ボールの生成
  function putBall(e) {
    e.preventDefault();
    
    // ボールの初期位置は中心
    var tail = BALL.length;
    BALL[tail] = new Ball();
    BALL[tail].Alive = 1;
    BALL[tail].X = BAR.X;
    BALL[tail].Y = FIELD.HEIGHT-BAR.UNDER;
    BALL[tail].Vy = BALL[tail].Vy0 * (-1);
  };
  
  // バーの収縮
  function pushBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      setTimeout(pushBarEvent, 1000/FIELD.FPS);
    }
  };
  
  // バーの反発
  function popBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      setTimeout(popBarEvent, 1000/FIELD.FPS);
    }
  };
  
  // バーの収縮処理
  function pushBarEvent() {
    if (BAR.Y < FIELD.HEIGHT - BAR.UNDER + BAR.PUSH) {
      BAR.Vy = BAR.Vy0;
      BAR.Y += BAR.Vy * (FIELD.FPS/1000);
      setTimeout(pushBarEvent, 1000/FIELD.FPS);
    } else {
      BAR.Vy = 0;
      BAR.Y = FIELD.HEIGHT - BAR.UNDER + BAR.PUSH;
    }
  };
  
  var BarTimer;
  // バーの反発処理
  function popBarEvent() {
    if (BAR.Y > FIELD.HEIGHT-BAR.UNDER) {
      BAR.Vy = BAR.Vy0 * (-1);
      BAR.Y  += BAR.Vy * (FIELD.FPS/1000);
      setTimeout(popBarEvent, 1000/FIELD.FPS);
    } else {
      BAR.Y = FIELD.HEIGHT-BAR.UNDER;
      setTimeout(resetBarSpeed, 100);
    }
  };
  
  function resetBarSpeed() {
    BAR.Vy = 0;
  };
  
  // ブロックの生成
  function createBlocks() {
    var bxmax = 13;
    var bymax = 6;
    var btop  = 20;
    var bleft = 20;
    var bidx = 0;
    var bint = 5;
    
    for (var x = 0; x < bxmax; x++) {
      for (var y = 0; y < bymax; y++) {
        BLOCK[bidx] = new Block();
        BLOCK[bidx].X = x * BLOCK[bidx].WIDTH + btop + (x * bint);
        BLOCK[bidx].Y = y * BLOCK[bidx].HEIGHT + bleft + (y * bint);
        BLOCK[bidx].Alive = 1;
        
        bidx++;
      }
    }
    
  };
  
  
  // 画面の描画
  function drawField() {
    
    calcBallP();
    drawBack();
    drawBall();
    drawBar();
    drawBlock();
  };
  
  // ボール位置計算
  function calcBallP() {
    
    for (var i = 0; i < BALL.length; i++) {
      
      if (BALL[i] == null) {
        continue;
      }
      
      // 生存しているボールのみ計算
      if (BALL[i].Alive == 1) {
        // 床・天井接触
        if (BALL[i].Y <= 0 || FIELD.HEIGHT <= BALL[i].Y) {
          if (BALL[i].Y <= 0) {
            
            // 天井
            BALL[i].Y = FIELD.E0;
            BALL[i].Vy = BALL[i].Vy * FIELD.E * (-1)
          } else {
            // 床
            // 床に接触したボールは死亡
            BALL[i].Alive = 0;
            BALL[i].Vx = 0;
            BALL[i].Vy = 0;
          }
        }
        
        // 壁接触
        if (BALL[i].X <= 0 || FIELD.WIDTH <= BALL[i].X) {
          if (BALL[i].X <= 0) {
            BALL[i].X = FIELD.E0;
          } else {
            BALL[i].X = FIELD.WIDTH - FIELD.E0;
          }
          
          BALL[i].Vx = BALL[i].Vx * (-1);
        }
        
        // バー接触
        if (BAR.X <= BALL[i].X && BALL[i].X <= BAR.X + BAR.WIDTH) {
          if (Math.abs(BALL[i].Y - BAR.Y) <= BAR.HEIGHT) {
            
            // バーとボールの境界でバタつきを防ぐための処置
            BALL[i].Y = BAR.Y - BAR.E0;
            
            // バー接触後の速度計算
            BALL[i].Vy = (BALL[i].M * BALL[i].Vy - BAR.M * BAR.Vy) / BALL[i].M;
            
            // Vyが初速度より減速した場合は、初速度に戻す
            BALL[i].Vy = Math.abs(BALL[i].Vy) > BALL[i].Vy0 ? (BALL[i].Vy * BAR.E * (-1)) : (BALL[i].Vy0 * (-1));
          }
        }
        
        // ブロック接触
        for (var bi = 0; bi < BLOCK.length; bi++) {
          var xtouch = 0;
          var ytouch = 0;
          
          if (BLOCK[bi] == null) {
            continue;
          }
          
          if (BLOCK[bi].Alive == 0) {
            continue;
          }
          
          if ((Math.abs(BALL[i].X - BLOCK[bi].X) < BALL[i].RADIUS/2 + BLOCK[bi].WIDTH/2) &&
              (Math.abs(BALL[i].Y - BLOCK[bi].Y) < BALL[i].RADIUS/2 + BLOCK[bi].HEIGHT/2)) {
            
            BLOCK[bi].Alive = 0;
            BALL[i].Vy *= (-1);
          }
        }
        
        // 縦計算
        BALL[i].Vy += FIELD.GRAVITY * (1/FIELD.FPS);
        BALL[i].Y  += BALL[i].Vy * (1/FIELD.FPS);
        
        // 横計算
        BALL[i].X += BALL[i].Vx * (1/FIELD.FPS);
      }
    }
    
    deleteAllDeadBall();
  };
  
  // 死亡したボールを削除
  function deleteAllDeadBall() {
    var isDeadBall = 1;
    while(isDeadBall != 0) {
      isDeadBall = 0;
      for (var i = 0; i < BALL.length; i++) {
        if (BALL[i] == null) {
          continue;
        }
        
        if(BALL[i].Alive == 0) {
          delete BALL[i];
          BALL.splice(i,1);
          isDeadBall = 1;
          break;
        }
      }
    }
  }
  
  
  function drawBack() {
    ctx.fillStyle = 'rgb(0, 0, 0)';
    ctx.fillRect(0, 0, FIELD.WIDTH, FIELD.HEIGHT);
  };
  
  
  // ボールの描画
  function drawBall() {
    
    ctx.save();
    
    // 生存しているボールの数だけ描画
    for(var i = 0; i < BALL.length; i++) {
      
      if (BALL[i] == null) {
        continue;
      }
      
      if (BALL[i].Alive) {
        // 円の描画設定
        ctx.beginPath();
        ctx.arc(BALL[i].X, BALL[i].Y, BALL[i].RADIUS, 0, 2*Math.PI, true);
        ctx.closePath();
        
        // 色設定
        BALL[i].HUE += 0.5;
        ctx.strokeStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
        ctx.fillStyle = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
        ctx.shadowColor = 'hsl(' + BALL[i].HUE + ', 50%, 50%)';
      }
      
      // 描画実行
      ctx.stroke();
      ctx.fill();
    
    }
    
    ctx.restore();
  };
  
  // バーの描画
  function drawBar() {
    var delay = 1;
    
    BAR.X = (mouseX + delay * BAR.X) / (delay+1);
    
    // 色設定
    ctx.fillStyle = 'rgb(255,255,255)';
    
    // バーの描画設定
    ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT);
    
  };
  
  // ブロックの描画
  function drawBlock() {
    // 色設定
    ctx.fillStyle = 'rgb(0,255,0)';
    
    for (var i = 0; i < BLOCK.length; i++) {
      if (BLOCK[i] == null) {
        continue;
      }
      
      if (BLOCK[i].Alive) {
        ctx.fillRect(BLOCK[i].X, BLOCK[i].Y, BLOCK[i].WIDTH, BLOCK[i].HEIGHT);
      }
    }
  }
  
  // 初期化イベント
  window.addEventListener('load', initialize, false);
  
} ) ();
</script>
</head>
<body>
<canvas id='canvas' width=500 height=300></canvas>
<p>
左クリック: バーを下げる <br />
右クリック: ボール出現
</p>
</body>
</html>



ボールとブロックの当たり判定がイマイチなので変な動きが多いです。
他にも色々ツッコミどころはありますが、改善点含めて次回でまとめて完成としたいです。

関連ページ

canvasで少しずつ作るブロック崩し(1/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(2/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(3/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(5/5) - Segmentation Fault



Mackerel(マカレル)を使ってワクワクしてみる

以前から気になっていたサーバー監視サービスのMackerel(マカレル)を使ってCentOS7(さくらVPS)を監視してみました。

はじめに

Mackerelは"株式会社はてな"さんが提供するクラウド型のサーバー監視サービスです。

mackerel.io

エンジニアをワクワクさせる「直感的サーバー監視サービス」

興味をそそられるキャッチコピーだったので気になっておりました。

2週間は無料トライアルで使用することができ、その後に有料プランor無料プランで利用できるようです。料金も月額 \1800×ホスト数なので使い勝手の検証は個人でも気軽に出来ますね。ちなみにMackerelは日本語で鯖(サバ)です。洒落がきいてますね。


登録~監視まで

サインアップ

まずは利用するためのアカウントを作って行きます。メールアドレスの登録か連携可能な外部のクラウドサービスでサインアップします。

f:id:segmentation-fault:20170904231609p:plain f:id:segmentation-fault:20170904231802p:plain


オーガニゼーションの作成

サインアップ後、オーガニゼーションなるものを作成するよう求められます。既に他ユーザが使用している名前にすることはできないみたいです。 特に思いつかなかったのでYellowtail(ブリ)にしました。

f:id:segmentation-fault:20170904232131p:plain


案内に従って無料トライアルではじめていきます。

f:id:segmentation-fault:20170904232844p:plain


これでサービスを利用できるようになりました。


エージェントのインストール

Mackerelによる監視には対象のサーバーにエージェントが必要になるので手順に従って導入します。

f:id:segmentation-fault:20170904233157p:plain


手順にあるコマンドを対象サーバーで実行してエージェントが起動すればOK。

$ sudo [コマンドのコピペ]

...(途中略)...

*************************************

     Done! Welcome to Mackerel!

*************************************
$


$ sudo journalctl -u mackerel-agent.service
-- Logs begin at Mon 2017-08-14 00:20:01 JST, end at Mon 2017-09-04 20:35:37 JST. --
Sep 04 20:33:39 <ホスト名> systemd[1]: Starting mackerel.io agent...
Sep 04 20:33:39 <ホスト名> systemd[1]: Started mackerel.io agent.
Sep 04 20:33:39 <ホスト名> mackerel-agent[27889]: 2017/09/04 20:33:39 INFO <main> Starting mackerel-agent version
Sep 04 20:33:40 <ホスト名> mackerel-agent[27889]: 2017/09/04 20:33:40 INFO <command> Start: apibase = https://
$


サービス、ロールの作成

エージェントを導入すると監視が始まりますが、サービスとロールというものを定義するとサービスを構成する複数ホストをまとめて管理したり、サービス内のホストをロール毎に分類して管理することができるようです。

mackerel.io

グループ分けするほどサーバー数を持ってませんが、適当にサービスをBonito(カツオ)、ロールをSoda(ソーダガツオ)、Striped(ハガツオ)で作って監視してみます。

f:id:segmentation-fault:20170904234320p:plain

f:id:segmentation-fault:20170904234637p:plain

グラフの表示

監視状況のグラフはこんな感じです。

f:id:segmentation-fault:20170904234745p:plain


まとめ

ここまでは基本的な設定ですが、登録から監視開始まで10分程度の作業でとても簡単にできました。後はアラート設定なんかも色々といじったりして遊んでみようと思います(ワクワク)。



canvasで少しずつ作るブロック崩し(3/5)

前回はボールを1個だけ出現させてバーで跳ね返せる機能を作りました。複数のボールを出現する機能とクリックでバーを上下させボールを打ち返せる機能をつけてみます。

機能追加版

左クリック: バーを下げる
右クリック: ボール出現


ソースコード

<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript">
(function() {

  var canvas;
  var ctx;
  var width;
  var height;
  var mouseX;
  var mouseY;
  var barTimerID;
  var pushTimerID;
  var popTimerID;
  var ball_id = 0;
  var ball = [];
  
  var BAR = {
    'HEIGHT' : 10,
    'WIDTH'  : 50,
    'UNDER'  : 25,
    'X'      : 0,
    'Y'      : 0,
    'PUSH'   : 10,
    'Vx'     : 0,
    'Vx0'    : 0,
    'Vy'     : 0,
    'Vy0'    : 100,
    'M'      : 10,
    'E'      : 0.7,
    'E0'     : 10,
  };
  
  var BALL = {
    'ALIVE'  : 0,
    'X'      : 0,   
    'Y'      : 0,   
    'Vx'     : 0,   // ボール速度(x成分)
    'Vx0'    : 100, // ボールの初速
    'Vy'     : 0,   // ボール速度(y成分)
    'Vy0'    : 600, // ボールの初速(y成分の最低速度)
    'RADIUS' : 5,
    'M'      : 1,
  };
  
  var FIELD = {
    'GRAVITY' : 588, // 重力加速度(0.9 * FPS)
    'FPS'     :  60, // frame per second
    'E'       : 0.7, 
    'E0'      : 10,
  };
  
  
  var Ball = function(id) {
    this.id = id;
    this.Alive = 0;
    this.X = 0;
    this.Y = 0;
    this.Vy = 0;
    this.Vx = 100;
    this.Vx0 = 0;
    this.Vy0 = 200;
    this.RADIUS = 5;
    this.M = 1;
    this.timerID;
  };
  
  // 初期化処理
  function initialize() {
    canvas = document.getElementById('canvas');
    if(!canvas && !canvas.getContext) {
      return false;
    }
    
    ctx = canvas.getContext('2d');
    width = ctx.canvas.width ;
    height = ctx.canvas.height;
    
    // バーの初期位置は中心
    mouseX = width/2;
    BAR.X = mouseX;
    BAR.Y = height-BAR.UNDER;
    
    
    canvas.addEventListener('mousemove', getMouseCoordinate, false);
    canvas.addEventListener('mousedown', pushBar, false);
    canvas.addEventListener('mouseup', popBar, false);
    canvas.addEventListener('contextmenu', putBall, false);
    
    setInterval(calcBallP, 1000/FIELD.FPS);
    setInterval(drawField, 1000/FIELD.FPS);
  };
  
  
  // マウス座標の更新
  function getMouseCoordinate(e) {
    var rect = e.target.getBoundingClientRect();
    mouseX = Math.floor(e.clientX - rect.left);
    mouseY = Math.floor(e.clientY - rect.top);
  };
  
  // ボールの生成
  function putBall(e) {
    e.preventDefault();
    
    // ボールの初期位置は中心
    ball[ball_id] = new Ball(ball_id);
    ball[ball_id].Alive = 1;
    ball[ball_id].X = BAR.X;
    ball[ball_id].Y = height-BAR.UNDER;
    ball[ball_id].Vy = ball[ball_id].Vy0 * (-1);
    
    ball_id++;
    
    console.log(ball.length);
  };
  
  // バーの収縮
  function pushBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      //BAR.Y += BAR.PUSH;
      clearInterval(popTimerID);
      clearInterval(pushTimerID);
      pushTimerID = setInterval(pushBarEvent, 1000/FIELD.FPS);
    }
  };
  
  // バーの反発
  function popBar(e) {
    if (!e.pageX) {
      e = event.touches[0];
    }
    
    if (e.button == 0) {
      //BAR.Y = height-BAR.UNDER;
      clearInterval(popTimerID);
      clearInterval(pushTimerID);
      pushTimerID = setInterval(popBarEvent, 1);
    }
  };
  
  
  // バーの収縮処理
  function pushBarEvent() {
    if (BAR.Y < height - BAR.UNDER + BAR.PUSH) {
      BAR.Vy = BAR.Vy0;
      BAR.Y += BAR.Vy * (FIELD.FPS/1000);
    } else {
      BAR.Vy = 0;
      BAR.Y = height - BAR.UNDER + BAR.PUSH;
    }
  };
  
  // バーの反発処理
  function popBarEvent() {
    if (BAR.Y > height-BAR.UNDER) {
      BAR.Vy = BAR.Vy0 * (-1);
      BAR.Y  += BAR.Vy * (FIELD.FPS/1000);
    } else {
      BAR.Vy = 0;
      BAR.Y = height-BAR.UNDER;
    }
  };
  
  // ボール位置計算
  function calcBallP() {
    
    for (var i = 0; i < ball.length; i++) {
      
      // 生存しているボールのみ計算
      if (ball[i].Alive) {
        // 床・天井接触
        if (ball[i].Y <= 0 || height <= ball[i].Y) {
          if (ball[i].Y <= 0) {
            
            // 天井
            ball[i].Y = FIELD.E0;
            ball[i].Vy = ball[i].Vy * FIELD.E * (-1)
          } else {
            // 床
            // 床に接触したボールは死亡
            ball[i].Alive = 0;
            ball[i].Vx = 0;
            ball[i].Vy = 0;
          }
        }
        
        // 壁接触
        if (ball[i].X <= 0 || width <= ball[i].X) {
          if (ball[i].X <= 0) {
            ball[i].X = FIELD.E0;
          } else {
            ball[i].X = width - FIELD.E0;
          }
          
          ball[i].Vx = ball[i].Vx * (-1);
        }
        
        // バー接触
        if (BAR.X <= ball[i].X && ball[i].X <= BAR.X + BAR.WIDTH) {
          if (Math.abs(ball[i].Y - BAR.Y) <= BAR.HEIGHT) {
            
            // バーとボールの境界でバタつきを防ぐための処置
            ball[i].Y = BAR.Y - BAR.E0;
            
            // バー接触後の速度計算
            ball[i].Vy = (ball[i].M * ball[i].Vy - BAR.M * BAR.Vy) / ball[i].M;
            
            // Vyが初速度より減速した場合は、初速度に戻す
            ball[i].Vy = Math.abs(ball[i].Vy) > ball[i].Vy0 ? (ball[i].Vy * BAR.E * (-1)) : (ball[i].Vy0 * (-1));
          }
        }
        
        // 縦計算
        ball[i].Vy += FIELD.GRAVITY * (1/FIELD.FPS);
        ball[i].Y  += ball[i].Vy * (1/FIELD.FPS);
        
        // 横計算
        ball[i].X += ball[i].Vx * (1/FIELD.FPS);
      }
    } 
  };
  
  // 画面の描画
  function drawField() {
    
    drawBack();
    drawBall();
    drawBar();
    
  };
  
  function drawBack() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
    ctx.fillRect(0, 0, width, height);
  };
  
  var hue = 0.5;
  
  function drawBall() {
    
    ctx.save();
    
    // 生存しているボールの数だけ描画
    for(var i = 0; i < ball.length; i++) {
      if (ball[i].Alive) {
        // 円の描画設定
        ctx.beginPath();
        ctx.arc(ball[i].X, ball[i].Y, ball[i].RADIUS, 0, 2*Math.PI, true);
        ctx.closePath();
        
        // 色設定
        hue += 0.5;
        ctx.strokeStyle = 'hsl(' + hue + ', 50%, 50%)';
        ctx.fillStyle = 'hsl(' + hue + ', 50%, 50%)';
        ctx.shadowColor = 'hsl(' + hue + ', 50%, 50%)';
        ctx.shadowBlur = 10;
      }
      
      // 描画実行
      ctx.stroke();
      ctx.fill();
    
    }
    
    ctx.restore();
  };
  
  function drawBar() {
    var delay = 1;
    
    BAR.X = (mouseX + delay * BAR.X) / (delay+1);
    
    // 色設定
    ctx.fillStyle = 'rgb(255,255,255)';
    
    // 円の描画設定
    ctx.fillRect(BAR.X, BAR.Y, BAR.WIDTH, BAR.HEIGHT);
    
  };
  
  // 初期化イベント
  window.addEventListener('load', initialize, false);
  
} ) ();
</script>
</head>
<body>
<canvas id='canvas' width=500 height=300></canvas>
</body>
</html>



次回はブロックを作ってゲームっぽくしていきます。 あと、バーで球を打つ時の判定は改善しないとダメそう。

関連ページ

canvasで少しずつ作るブロック崩し(1/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(2/5) - Segmentation Fault
canvasで少しずつ作るブロック崩し(その4) - Segmentation Fault
canvasで少しずつ作るブロック崩し(5/5) - Segmentation Fault