(C)WorldWideSoftware

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="utf-8">
  <title>へクス(ヘキサ)のマップ2</title>
 </head>
 <body>
  <canvas id="bg" width="480" height="480"></canvas><br>
  <a href="http://www.wwsft.com">(C)WorldWideSoftware</a><br>
【説明】六角形を組み合わせて構成されるへクスのマップのサンプルです。
カーソルキーでカーソルを移動、スペースキーを押すとその位置から全マスに対し距離の検索を行います。
<strong>紺色のヘクスはキャラクターが入れない場所という想定です。</strong>
検索した結果、例えば10の位置からは10→9→8→‥→2→1→0と辿ることで最短距離で目的地に移動できます。
<br>
<a href="hexa01src.html">ソースコードの確認</a>

  <script>
  //描画面(キャンバス)の準備
  var cvs = document.getElementById("bg");
  var ctx = cvs.getContext("2d");
  ctx.lineWidth = 4;//線の太さ
  ctx.font = "24px monospace";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";

  //キー入力
  var key = 0;
  onkeydown = function(ev) { key = ev.keyCode; }
  onkeyup = function(ev) { key = 0; }

  //定数
  var HEXA_W = 10;//横のマスの数 但し偶数行は横9マスとする
  var HEXA_H = 10;//縦のマスの数
  var HEXA_D = 48;//へクス間の画面上の距離(ドット数)

  //二次元配列でマップを管理する
  var hexa = [
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 9],//←9は入れないマス
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
   [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],//←1はキャラ(兵士、戦車etc)が入れない場所を想定、カーソルは入れる
   [0, 0, 0, 1, 0, 1, 0, 0, 0, 9],
   [0, 0, 0, 1, 0, 0, 1, 1, 0, 0],
   [0, 0, 0, 1, 0, 1, 0, 1, 0, 9],
   [0, 0, 0, 0, 0, 1, 0, 1, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 1, 0, 9],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  ];

  var dist = [//こちらは距離の検索で使う
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  ];

  function resetDis() {//距離データをリセット
   var x, y;
   for(y=0; y<HEXA_H; y++) {
    for(x=0; x<HEXA_W; x++) {
     dist[y][x] = -1;
    }
   }
  }

  //6方向への移動量(方向は左を0とし時計回り)
  var XP0 = [-1,  0,  1,  1,  1,  0];//偶数列(y%2==0)
  var YP0 = [ 0, -1, -1,  0,  1,  1];//

  var XP1 = [-1, -1,  0,  1,  0, -1];//奇数列(y%2==1)
  var YP1 = [ 0, -1, -1,  0,  1,  1];//

/*
(x,y)マスの6方向にある配列はこれ↓であり、その値を定義している
 y%2==0                  y%2==1
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
│ │ │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ │ │1│2│ │ │ │1│2│ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ │0│■│3│ │ │ │0│■│3│ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ │ │5│4│ │ │ │5│4│ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │ │ │ │
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
*/

  //あるマス(今回はカーソルの位置)をスタート地点とし、そこからの距離を検索する
  function calcDis(dis) {
   var d, x, y, nx, ny;
   for(y=0; y<HEXA_H; y++) {
    for(x=0; x<HEXA_W; x++) {
     if(dist[y][x] == dis) {//現在の距離
      for(d=0; d<6; d++) {//6方向を調べる
       if(y%2 == 0) {
        nx = x + XP0[d];//【重要】この記述が上で定義した6方向
        ny = y + YP0[d];
       }
       else {
        nx = x + XP1[d];//【重要】この記述が上で定義した6方向
        ny = y + YP1[d];
       }
       if(0 <= nx && nx <= HEXA_W-1 && 0 <= ny && ny <= HEXA_H-1) {
        if(hexa[ny][nx] == 0 && dist[ny][nx] == -1) dist[ny][nx] = dis+1;//隣マスに距離の値をセット
       }
      }
     }
    }
   }
  }

  //へクスのキャンバス上の座標
  //配列[y][x]の(x,y)の値から画面上の座標を求める
  function hexaX(x, y) {
   var X = HEXA_D/2 + x*HEXA_D;
   if(y%2 == 0) X += HEXA_D/2;
   return X;
  }

  function hexaY(x, y) {
   var Y = HEXA_D/2 + y*HEXA_D;
   return Y;
  }

  //カーソル
  var cur_x = 0;
  var cur_y = 0;
  function moveCur() {//方向キーでカーソルを移動
   var x = cur_x;
   var y = cur_y;
   if(key == 37) x--;
   if(key == 39) x++;
   if(key == 38) {
    if(y%2==1 && x==HEXA_W-1) x--;//右端で引っかからないように
    y--;
   }
   if(key == 40) {
    if(y%2==1 && x==HEXA_W-1) x--;//右端で引っかからないように
    y++;
   }
   if(0 <= x && x <= HEXA_W-1 && 0 <= y && y <=HEXA_H-1) {//移動可能な範囲
    if(hexa[y][x] < 9) {//入れるマス
     cur_x = x;
     cur_y = y;
    }
   }
  }

  function drawHex(x, y, r) {//六角形の表示
   var i, vx, vy;
   ctx.beginPath();
   for(i=0; i<6; i++) {//三角関数で六角形の頂点位置を計算
    vx = hexaX(x,y) + r*Math.cos(Math.PI*(30+i*60)/180);
    vy = hexaY(x,y) + r*Math.sin(Math.PI*(30+i*60)/180);
    if(i == 0) ctx.moveTo(vx, vy);
    else       ctx.lineTo(vx, vy);
   }
   ctx.closePath();
   ctx.fill();
  }

  function drawBG() {//背景全体の描画
   var x, y;
   ctx.fillStyle = "black";
   ctx.fillRect(0, 0, 480, 480);
   for(y=0; y<HEXA_H; y++) {
    for(x=0; x<HEXA_W; x++) {
     if(hexa[y][x] < 9) {
      if(hexa[y][x] == 0) ctx.fillStyle = "blue";
      if(hexa[y][x] == 1) ctx.fillStyle = "navy";
      drawHex(x, y, 26);
      if(dist[y][x] >= 0) {//距離の表示
       ctx.fillStyle = "rgb("+(dist[y][x]*16)+",255,0)";//距離の値が大きいほど黄色に近い色にする
       ctx.fillText(dist[y][x], hexaX(x,y), hexaY(x,y));
      }
     }
    }
   }
  }

  //メイン処理
  var idx = 0;
  var tmr = 0;

  function mainProc() {
   tmr++;

   if(idx == 0) {//キー入力
    moveCur();//カーソルの移動
    drawBG();//BG描画

    //カーソルの表示
    var s = 24+tmr%4;//カーソルのアニメーション
    ctx.strokeStyle = "red";
    ctx.strokeRect(hexaX(cur_x,cur_y)-s, hexaY(cur_x,cur_y)-s, s*2, s*2);

    if(key == 32 && hexa[cur_y][cur_x] == 0) {//スペースキーで距離検索の処理へ
     resetDis();
     dist[cur_y][cur_x] = 0;//検索開始位置
     idx = 1;
     tmr = -9;
    }
   }

   if(idx == 1) {//距離の検索
    drawBG();//BG描画
    if(tmr < 150 && tmr%10 == 0) calcDis(parseInt(tmr/10));//10フレームごとに距離を検索する
    if(tmr > 150) idx = 0;
   }
  }

  resetDis();
  setInterval(mainProc, 100);
  </script>
 </body>
</html>

←動作確認に戻る