カードを自然な感じで重ねる判定条件を考える

# 背景
HTML5のCanvasとJavascriptを使って、スパイダーソリティア（ゲーム）を開発をしています。

# 問題点
掴んだカードを他のカードの上に移動させる場合、移動先のカードを判定する必要があります。

マウスポインタの座標を基準に判定すると、マウスポインタが移動先のカードの上にのっている必要がある為、カードの画像は重なっていても、移動先として判定されないケースがあります。

# 解決策
カードを掴んでいる状態では、ユーザーはマウスポインタではなく、表示されているカードの位置を基準にしていると考えられます。

そこで、判定基準をマウスポインタ座標からカードの重心（中心）に変更することで、ユーザーの認知に近い自然な判定条件を目指します。

# カードの重心を計算する
カードの重心を計算する実装はシンプルです。

マウスポインタ座標(x, y)からカードを掴んだときの左上からのズレ量(offset_x, offset_y)を引くことで、今表示しているカードの左上座標が求まります。

この左上座標にカード幅の半分、カード高さの半分を足すと、カードの重心になります。

```javascript:center.js
const center_x = (x - offset_x + card_width / 2);   // X方向の重心
const center_y = (y - offset_y + card_height / 2);  // Y方向の重心
```

# まとめ
マウスポインタ座標からカードの重心基準に変更することで、自然な操作感にすることができました。

# サンプルコード
最後に、今回説明した内容のサンプルコードを掲載しておきます。

```javascript:center.js
const card_width = 50;                                            // カードの幅
const card_height = 80;                                           // カードの高さ
const src_rect = new DOMRect(10, 100, card_width, card_height);    // 移動元カードがある位置
const dest_rect = new DOMRect(120, 100, card_width, card_height);  // 移動先カードがある位置

const canvas = document.getElementById("canvas");
canvas.addEventListener('mousedown', onMouseDown, false);
canvas.addEventListener('mouseup', onMouseUp, false);
canvas.addEventListener('mousemove', onMouseMove, false);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "darkgreen";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.fillRect(src_rect.left, src_rect.top, src_rect.width, src_rect.height);
ctx.fillStyle = "yellow";
ctx.fillRect(dest_rect.left, dest_rect.top, dest_rect.width, dest_rect.height);

var holding = false;  // カードをつかんでいる状態ならtrue
var offset_x = 0;     // マウスポインタ座標とカード左端座標のズレ幅
var offset_y = 0;     // マウスポインタ座標とカード上端座標のズレ幅

function onMouseDown(event){
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  if(x >= src_rect.left && x <= src_rect.right){
    if(y >= src_rect.top && y <= src_rect.bottom){
      offset_x = x - src_rect.left;
      offset_y = y - src_rect.top;
      holding = true;
    }
  }
  event.preventDefault();
}

function onMouseUp(event){
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  const center_x = (x - offset_x + card_width / 2);   // X方向の重心
  const center_y = (y - offset_y + card_height / 2);  // Y方向の重心
  if(center_x >= dest_rect.left && center_x <= dest_rect.right){
    if(center_y >= dest_rect.top && center_y <= dest_rect.bottom){
      window.alert("重心基準で重なった!");
    }
  }
  holding = false;
  event.preventDefault();
}

function onMouseMove(event){
  if(holding){
    const rect = canvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top; 
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "darkgreen";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "yellow";
    ctx.fillRect(dest_rect.left, dest_rect.top, dest_rect.width, dest_rect.height);
    ctx.fillStyle = "red";
    ctx.fillRect(x - offset_x, y - offset_y, src_rect.width, src_rect.height);
    event.preventDefault();
  }
}
```

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Center</title>
</head>
<body>
  <canvas id="canvas" width="300" height="300"></canvas>
  <script src="./center.js"></script>
</body>
</html>
```