
proxyって便利に使えると初めて知った、ユゲタです。
仕事で、とあるクローリングシステムを作っているんですが、クローリング先のいくつかのサイトで、頻度高くアクセスすると、すぐにバンされてしまいます。
これを回避するためにproxyを使うんですが、使い捨てのように使えるproxyは本当に助かりますね。
でも、良い子はこれを真似しちゃダメですよ。
さて、本日のプログラミングは、ブロック崩し目前の壁打ちテニスの、ソースコード見直しを行い、リファクタリングにトライしてみたいと思います。
ポイントは、コードの無駄を無くし、をスッキリさせる事と、若干の処理速度の向上を目指したいと思います。
あと、ボールとラケットとのコリジョン(当たり判定)に角に当たった時に、ボールの進行を左右反転する処理を入れてみます。
まとめのポイントは記載しますが、詳細が気になる人は、前回のソースコードと比較してみてください。
本日のIT謎掛け
「proxy」と、かけまして・・・
「ラジオリスナーの投稿」と、ときます。
そのココロは・・・
匿名性が高いでしょう。
ソースコード
(function(){
  var __options = {
    canvas : "#mycanvas",
    width  : 400,
    height : 600,
    wall   : {
      w : 400 , h : 600 , 
      color_stroke : "transparent" , 
      color_fill : "#382B8C" , 
      size : 20
    },
    ball   : {
      x :  50 , y :  50 , r : 10,
      color_stroke : "transparent",
      color_fill : "#F2B5A7" , 
      moveX : 4 , 
      moveY : 4
    },
    bar    : { 
      w :  60 , h :  10 , 
      color_stroke : "transparent" , 
      color_fill : "#958ABF" , 
      moveX : 12 
    },
    dialog : {
      color_fill   : "white",
      color_stroke : "#382B8C",
      width_stroke : 2,
      text_color   : "#382B8C",
      text_size    : 16,
      text_width   : 0.9,
      text_font    : "sans-serif",
      radius       : 4,
      w            : 0.7,
      h            : 0.3
    }
  };
  var MAIN = function(){
    if(!this.check()){
      alert("htmlに指定のcanvasがありません。");
      return;
    }
    this.init();
    this.draw(true);
    this.animation_set();
    this.event_set();
    this.game_start();
  };
  MAIN.prototype.init = function(){
    this.data_reset();
    // 画面サイズ調整
    var canvas = this.canvas;
    canvas.setAttribute("width"  , this.options.wall.w);
    canvas.setAttribute("height" , this.options.wall.h);
  };
  MAIN.prototype.dialog_window = function(o){
    var w = o.wall.w * o.dialog.w,
        h = o.wall.h * o.dialog.h,
        x = o.wall.w / 2 * (1 - o.dialog.w),
        y = o.wall.w / 2 * (1 - o.dialog.h),
        r = o.dialog.radius;
    h = h > 180 ? h : 180;
    this.ctx.fillStyle   = o.dialog.color_fill;
    this.ctx.strokeStyle = o.dialog.color_stroke;
    this.ctx.lineWidth   = o.dialog.width_stroke * 2;
    this.ctx.beginPath();
    this.ctx.moveTo(x,y + r);
    this.ctx.arc(x+r , y+h-r , r , Math.PI , Math.PI*0.5 , true);
    this.ctx.arc(x+w-r , y+h-r , r , Math.PI*0.5,0 , 1);
    this.ctx.arc(x+w-r , y+r , r , 0 , Math.PI*1.5 , 1);
    this.ctx.arc(x+r , y+r , r , Math.PI*1.5 , Math.PI , 1);   
    this.ctx.closePath();
    this.ctx.stroke();
    this.ctx.fill();
  }
  MAIN.prototype.dialog_text = function(o , texts){
    var w = (o.wall.w * o.dialog.w) * o.dialog.text_width;
    
    
    var x = o.wall.w / 2;
    var y = o.wall.w / 2 * (1 - o.dialog.h) + 30;
    var text_y = y;
    for(var i=0; i<texts.length; i++){
      this.ctx.fillStyle    = texts[i].color    || o.dialog.text_color;
      this.ctx.textAlign    = texts[i].align    || "center";
      this.ctx.textBaseline = texts[i].baseline || "top";
      var font_size         = texts[i].size || o.dialog.text_size;
      var font_weight       = texts[i].weight || "";
      this.ctx.font         = font_weight +" "+ font_size+"px '"+ o.dialog.text_font +"' ";
      this.ctx.fillText(texts[i].text , x, text_y , w);
      text_y += font_size + (texts[i].margin || 10);
    }
  };
  MAIN.prototype.game_start = function(){
    this.dialog_window(this.options);
    this.dialog_text(this.options , [
      {text:"壁打ちブロック" , size:30 , margin:30 , weight:"bold"},
      {text:"画面をクリックすると"},
      {text:"ゲームが開始します"}
    ]);
  };
  MAIN.prototype.game_over = function(){
    this.flg_gamestart = false;
    this.data_reset();
    this.dialog_window(this.options);
    this.dialog_text(this.options , [
      {text:"Game over", size:30 , margin:30 , color:"red" , weight:"bold"},
      {text:"画面をクリックすると"},
      {text:"ゲームが開始します"}
    ]);
  };
  MAIN.prototype.data_reset = function(){
    this.options = JSON.parse(JSON.stringify(__options));
    this.options.wall.w = window.innerWidth  < this.options.wall.w ? window.innerWidth  : this.options.wall.w;
    this.options.wall.h = window.innerHeight < this.options.wall.h ? window.innerHeight : this.options.wall.h;
  };
  MAIN.prototype.getCanvas = function(){
    if(typeof this.canvas === "undefined"){
      this.canvas = document.querySelector(__options.canvas);
    }
    return this.canvas;
  };
  MAIN.prototype.getContext = function(){
    if(typeof this.ctx === "undefined"){
      var canvas = this.getCanvas();
      if(!canvas){return null;}
      this.ctx = canvas.getContext("2d");
    }
    return this.ctx;
  };
  MAIN.prototype.check = function(){
    var ctx = this.getContext();
    if(ctx){
      return true;
    }
    else{
      return false;
    }
  };
  MAIN.prototype.ctx_clear = function(){
    this.ctx.clearRect(0, 0,  this.canvas.width,  this.canvas.height);
  };
  MAIN.prototype.draw = function(flg){
    if(!flg && this.flg_gamestart !== true){return;}
    this.ctx_clear();
    this.draw_wall(this.options.wall);
    this.draw_bar( this.options.wall , this.options.bar);
    this.draw_ball(this.options.wall , this.options.ball);
  };
  MAIN.prototype.draw_wall = function(ow){
    var ctx = this.ctx;
    ctx.strokeStyle = ow.color_stroke;
    ctx.strokeWidth = ow.color_stroke === "transparent" ? 0 : 1;
    ctx.fillStyle   = ow.color_fill;
    ctx.beginPath();
    ctx.moveTo(0,0);
    ctx.lineTo(ow.w , 0);
    ctx.lineTo(ow.w , ow.h);
    ctx.lineTo(ow.w - ow.size , ow.h);
    ctx.lineTo(ow.w - ow.size , ow.size);
    ctx.lineTo(ow.size , ow.size);
    ctx.lineTo(ow.size , ow.h);
    ctx.lineTo(0 , ow.h);
    ctx.lineTo(0 , 0);
    ctx.stroke();
    ctx.fill();
  };
  MAIN.prototype.draw_bar = function(ow,ob){
    this.ctx.strokeStyle = ob.color_stroke;
    this.ctx.strokeWidth = ob.color_stroke === "transparent" ? 0 : 1;
    this.ctx.fillStyle   = ob.color_fill;
    ob.x = ob.x ? ob.x : (ow.w - ob.w) / 2;
    ob.y = ow.h - ob.h - 50;
    this.ctx.fillRect(ob.x , ob.y , ob.w , ob.h);
  };
  MAIN.prototype.draw_ball = function(ow,ob){
    this.ctx.strokeStyle = ob.color_stroke;
    this.ctx.strokeWidth = ob.color_stroke === "transparent" ? 0 : 1;
    this.ctx.fillStyle   = ob.color_fill;
    this.ctx.beginPath();
    ob.x = ob.x || ow.w / 2;
    ob.y = ob.y || ow.h / 2;
    this.ctx.arc( ob.x , ob.y , ob.r, 0, Math.PI * 2 );
    this.ctx.fill();
  };
  MAIN.prototype.animation_set = function(){
    if(this.flg_gamestart !== true){return;}
    new LIB().anim((function(e){this.animation(e)}).bind(this));
  };
  MAIN.prototype.animation = function(timestamp){
    if(this.flg_gamestart !== true){return;}
    if (!this.time_start){this.time_start = timestamp;}
    // debug用(play上限秒数)
    // if(timestamp - this.time_start > 30000){return;}
    // keydown-bar-move
    switch(this.keydown_flg){
      case "right":
        this.bar_move(this.options.bar.x + this.options.bar.moveX);
        break;
      case "left":
        this.bar_move(this.options.bar.x - this.options.bar.moveX);
        break;
    }
    this.ball_move();
    this.draw();
    this.animation_set();
  };
  MAIN.prototype.ball_move = function(){
    if(this.flg_gamestart !== true){return;}
    
    this.options.ball.x += this.options.ball.moveX;
    this.options.ball.y += this.options.ball.moveY;
    this.collision_wall(this.options.wall , this.options.ball);
    this.collision_bar(this.options.bar , this.options.ball);
  };
  // 当たり判定(壁)
  MAIN.prototype.collision_wall = function(ow , ob){
    // <- : left
    if(ob.x - ob.r < ow.size){
      ob.x     = ow.size + ob.r;
      ob.moveX = ob.moveX * -1;
    }
    // ^ : top
    if(ob.y - ob.r < ow.size){
      ob.y     = ow.size + ob.r;
      ob.moveY = ob.moveY * -1;
    }
    // -> : right
    if(ob.x + ob.r > ow.w - ow.size){
      ob.x     = ow.w - ow.size - ob.r;
      ob.moveX = ob.moveX * -1;
    }
    // v : bottom (game-over)
    if(ob.y + ob.r > ow.h){
      this.game_over();
    }
  }
  // 当たり判定(ラケット)
  MAIN.prototype.collision_bar = function(bar , ball){
    // ボールが上移動の場合は処理対象外
    if(ball.moveY < 0){return;}
    // ball-direct-under (正反射)
    if(ball.y + ball.r > bar.y
    && ball.x > bar.x
    && ball.x < bar.x + bar.w){
      ball.moveY = ball.moveY * -1;
    }
    // 左角判定(ボールと角との距離がボール半径以下の判定)
    else if(ball.moveX > 0
    && Math.sqrt(Math.pow(bar.x - ball.x, 2) + Math.pow(bar.y - ball.y, 2)) <= ball.r){
      this.calc_angle(bar , ball);
    }
    // 右角判定(ボールと角との距離がボール半径以下の判定)
    else if(ball.moveX < 0
    && Math.sqrt(Math.pow((bar.x + bar.w) - ball.x, 2) + Math.pow(bar.y - ball.y, 2)) <= ball.r){
      this.calc_angle(bar , ball);
    }
  };
  MAIN.prototype.calc_angle = function(bar,ball){
    // 反転
    ball.moveX = -ball.moveX;
    ball.moveY = -ball.moveY;
  };
  MAIN.prototype.event_set = function(){
    new LIB().event(window , "click"      , (function(e){this.click(e)}).bind(this));
    new LIB().event(window , "keydown"    , (function(e){this.keydown(e)}).bind(this));
    new LIB().event(window , "keyup"      , (function(e){this.keyup(e)}).bind(this));
    new LIB().event(window , "mousemove"  , (function(e){this.mousemove(e)}).bind(this));
    new LIB().event(window , "touchmove"  , (function(e){this.touchmove(e)}).bind(this));
    new LIB().event(window , "touchend"   , (function(e){this.touchend(e)}).bind(this));
  };
  MAIN.prototype.click = function(e){
    if(this.flg_gamestart === true){return}
    this.flg_gamestart = true;
    this.animation_set();
  };
  MAIN.prototype.bar_move = function(bar_x){
    if(bar_x < this.options.wall.size){
      bar_x = this.options.wall.size;
    }
    if(bar_x + this.options.bar.w > this.options.wall.w - this.options.wall.size){
      bar_x = this.options.wall.w - this.options.wall.size - this.options.bar.w;
    }
    this.options.bar.x = bar_x;
  };
  MAIN.prototype.keydown = function(e){
    switch(e.keyCode){
      case 37:  // <-
      case 'ArrowLeft':
      this.keydown_flg = "left";
      break;
      case 39:  // ->
      case 'ArrowRight':
      this.keydown_flg = "right";
      break;
    }
  };
  MAIN.prototype.keyup = function(e){
    this.keydown_flg = false;
  };
  MAIN.prototype.mousemove = function(e){
    if(this.flg_gamestart !== true){return;}
    this.mousePos = this.mousePos || e.clientX;
    this.bar_move(this.options.bar.x + (e.clientX - this.mousePos));
    this.mousePos = e.clientX;
    this.draw();
  };
  MAIN.prototype.touchmove = function(e){
    if(this.flg_gamestart !== true){return;}
    if(!e || !e.touches || e.touches.length > 1){
      this.mousePos = null;
      return;
    }
    this.mousePos = typeof this.mousePos === "number" ? this.mousePos : e.touches[0].clientX;
    this.bar_move(this.options.bar.x + (e.touches[0].clientX - this.mousePos));
    this.mousePos = e.touches[0].clientX;
    this.draw();
  };
  MAIN.prototype.touchend = function(e){
    this.mousePos = null;
  };
  var LIB  = function(){};
  LIB.prototype.event = function(target, mode, func , flg){
    flg = (flg) ? flg : false;
    if (target.addEventListener){target.addEventListener(mode, func, flg)}
    else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})}
  };
  LIB.prototype.anim = function(func , time){
    if(window.requestAnimationFrame
    ||  window.webkitRequestAnimationFrame
    ||  window.mozRequestAnimationFrame
    ||  window.oRequestAnimationFrame
    ||  window.msRequestAnimationFrame){
      window.requestAnimationFrame(func);
    }
    else{
      time = time || 0;
      anim_flg = setTimeout(func , time);
    }
  };
  LIB.prototype.construct = function(){
    switch(document.readyState){
      case "complete"    : new MAIN();break;
      case "interactive" : this.event(window , "DOMContentLoaded" , (function(){new MAIN()}).bind(this));break;
      default            : this.event(window , "load"             , (function(){new MAIN()}).bind(this));break;
    }
  };
  new LIB().construct();
})();
 
 
本日のまとめ
今回のリファクタリングポイントは、基本設定情報の__optionを、this.optionsに格納して、game-over時に、defaultに戻しやすくしています。
それに伴い、それぞれの関数での値受け渡しで、変数名を簡易にするようにしてみました。
ダイアログと、その文字表示も、もう少し汎用的な関数にしてみたので、コーディングの効率化になりましたね。
そして、ラケットとボールの衝突判定ですが、角判定というのを入れてみました。
角じゃない当たり判定は、そのままボールの角度を上下反転させるだけですが、
角の場合は、左右も反転させるようにしています。
ブロック崩しの狙いを定めるために必要な要素ですよね。
ちなみに、角判定は、ボールの中心座標からの半径距離を計算して、厳密にコリジョン判定を行っていますよ。
高校の時の数学を思い出しましたね。
全体のソースは、githubにpushしているので、そちらから取得してください。
https://github.com/yugeta/game_block
 
0 件のコメント:
コメントを投稿