前田稔のドットパズル


一昔前までは通勤電車で「お絵描きパズル」に夢中になっている方を良く見かけたものです。
それほど面白く多くの人に親しまれているドットで絵を描く「Logic Puzzle(お絵描きパズル)」です。

ゲームの概要

  1. Win32 API で作成したページ先頭の画像を見て下さい。
    縦横に15個のマスが並んでいます。
    マスの上には、縦方向にマークする数字が、マスの左には横方向にマークする数字が表示されています。
    指定された数のマスを塗りつぶしてドット絵を完成させるゲームです。
    スマホの小さな画面でも、拡大・縮小すること無く操作出来るように工夫しました。
  2. Logic Puzzle の最も基本的なマークの方法に付いて説明します。
    例として15個の列に9個マークする場合を考えます。
    9個マークを左詰めで格納すると次のようになります。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    
    次にマークを右詰めで格納します。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    ・・・・・・■■■■■■■■■     右に詰めたとき
    
    このとき中央の重なった3個のマークが確定できます。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    ・・・・・・■■■■■■■■■     右に詰めたとき
    ・・・・・・★★★・・・・・・     マークが確定
    
  3. 行の右(または 列の下)をクリックすると、赤いマークが表示され行(列)が選択されます。
    選択された行(列)は、重なるマークや確定マークが調べられて下の大きなマスに拡大表示されます。
    大きなマスをタップして、マークを設定して下さい。
    設定が終われば「設定」をクリックすると取り出した行(列)が修正されます。
    これで決まり切った面倒な操作をコンピュータに任せることが出来ます。
  4. 最も基本的なマークが確定したら、そこからは思考を凝らして進めて行きます。
    難問を解くには数時間(数日)かかることもあるようです。
    プログラムの詳細は JavaScript のお絵描きゲームを参照して下さい。

問題集

  1. Kの大文字
  2. Zの大文字
  3. いちご

  4. うさぎ
  5. ヨット

    プログラムの作成

    1. 「お絵描きパズル」のプログラムでは、次の3個のファイルを使っています。
      ①各問題に共通で組み込まれている JavaScript のライブラリファイル dot.js です。
      ②各問題に共通の数字の画像ファイル numm.gif です。

      ③問題毎に作成される *.html ファイルです。
    2. dot.js のソースコードです。
      function Counter(img, sw, sh)
      {   this.Img= img;  //Image File(0~9の画像)
          this.Sw = sw;   //Sprite の幅
          this.Sh = sh;   //Sprite の高さ
      
          //「上, 右, 下, 左」の順
          this.View_Num = function(num, x, y)
          {   if (num>9)
              {   var n= Math.floor(num/10);
                  var pos = n*this.Sw;
                  var s1= 'style="clip:rect(0px,' + (pos+this.Sw) + 'px,' + this.Sh + 'px,' + pos + 'px);';
                  var s2 = 'position:absolute;left:' + (x-pos-2) + 'px;top:' + y + 'px;';
                  var s = '<img src="' + this.Img + '"' + s1 + s2 + '">';
                  document.write(s);
                  n= num%10;
                  pos = n*this.Sw;
                  s1= 'style="clip:rect(0px,' + (pos+this.Sw) + 'px,' + this.Sh + 'px,' + pos + 'px);';
                  s2 = 'position:absolute;left:' + (x-pos+2) + 'px;top:' + y + 'px;';
                  s = '<img src="' + this.Img + '"' + s1 + s2 + '">';
                  document.write(s);
                  return;
              }
              pos = num*this.Sw;
              s1= 'style="clip:rect(0px,' + (pos+this.Sw) + 'px,' + this.Sh + 'px,' + pos + 'px);';
              s2 = 'position:absolute;left:' + (x-pos) + 'px;top:' + y + 'px;';
              s = '<img src="' + this.Img + '"' + s1 + s2 + '">';
              document.write(s);
          }
      }
      // num行(num+100列)を抽出→s_line[], s_no, s_data[]
      function Select(num)
      {   if (num<0 || (num>90 && num-100<0)) return;
          s_line = []; 
          //選択マークのリセット(赤⇔null)
          if (s_no!=-1)
          {   if (s_no<90)    Mark3("null", -5, s_no);
              else            Mark3("null", s_no-100, -5);
          }
          if (num<90) Mark3("red", -5, num);
          else        Mark3("red", num-100, -5);
          s_no= num;
          if (num<90)
          {   for(var i=0; i<xn; i++) s_line[i]= t[num][i];
              s_data= xt[num];
          }
          else
          {   var nw= num-100;
              for(i=0; i<yn; i++) s_line[i]= t[i][nw];
              s_data= yt[nw];
          }
          //window.alert(s_line);
          for(i=0; i<xn; i++)
          {   var x= (i%5)*60+11;
              var y= Math.floor(i/5)*40+341;
              if (s_line[i]==1)
              {   c.fillStyle = 'black';
                  c.fillRect(x, y, 58, 38);
              }
              else if (s_line[i]==2)
              {   c.fillStyle = 'white';
                  c.fillRect(x, y, 58, 38);
              }
              else    c.clearRect(x, y, 58, 38);
          }
          Cross();
          //document.getElementById("cor").textContent= cor;
          SetMark("black");
      }
      // s_line→配列 t[][] に戻す
      function Set(num)
      {   Cross();            //s_line を戻す前に解析する
          if (num<100)
          {   for(var i=0; i<xn; i++)
              {   t[num][i]= s_line[i];
                  if (s_line[i]==1)   Mark("black", i, num);
                  if (s_line[i]==2)   Mark("white", i, num);
                  if (s_line[i]==0)   Mark("null", i, num);
              }
          }
          else
          {   var nw= num-100;
              for(i=0; i<yn; i++)
              {   t[i][nw]= s_line[i];
                  if (s_line[i]==1)   Mark("black", nw, i);
                  if (s_line[i]==2)   Mark("white", nw, i);
                  if (s_line[i]==0)   Mark("null", nw, i);
              }
          }
          cor= 'black';
          document.getElementById("cor").textContent= cor;
      }
      // 抽出行の解析(s_line[], s_no, s_data[]) s_line のマークは正しいとする
      function Cross()
      {   num= s_line.length;
          var sum = 0;        //s_data[] の合計
          for(var i=0; i<s_data.length; i++)
          {   sum += s_data[i];  }
          var c= 0;           //Mark Count
          var n= 0;           //null Count
          for(i=0; i<num; i++)
          {   if (s_line[i]==1)   c++;
              if (s_line[i]==0)   n++;
          }
          //window.alert("sum:" + sum + "  mark:" + c + "  null:" + n);
          if (c==sum)         //マーク確定(残りは空白)
          {   for(i=0; i<num; i++)
              {   if (s_line[i]!=1)   s_line[i]= 2;  }
              View_Sel(s_line);
              return;
          }
          if (c+n==sum)       //残りの null にマークする
          {   for(i=0; i<num; i++)
              {   if (s_line[i]==0)   s_line[i]= 1;
              }
              View_Sel(s_line);
              return;
          }
          //重なるマークを調べる
          var ary= new Array();
          for(i=0; i<s_data.length; i++)
          {   for(j=0; j<s_data[i]; j++)  ary.push(i);
              if (ary.length<num) ary.push(-1);
          }
          var bk= new Array();
          for(i=s_data.length-1; i>=0; i--)
          {   for(j=0; j<s_data[i]; j++)  bk.push(i);
              if (bk.length<num)  bk.push(-1);
          }
          for(i=ary.length; i<num; i++)
          {   ary.push(-1);
              bk.push(-1);
          }
          for(i=0; i<num; i++)
          {   if (ary[i]!=-1 && ary[i]==bk[num-i-1])  s_line[i]= 1;  }
          View_Sel(s_line);
      }
      // マスにマークを一個描画する
      function Mark(cor, xp, yp)
      {   if (xp>=xn || yp>=yn || xp<0 || yp<0)   return;
          Mark2(cor, xp, yp);
      }
      function Mark2(cor, xp, yp)
      {   var xw = xp*16+87;
          var yw = yp*16+87;
          c.fillStyle = cor;
          if (cor=='black' || cor=='white')
          {   c.fillRect(xw, yw, 14, 14);
              return;
          }
          if (cor=='null')
          {   c.clearRect(xw, yw, 14, 14);
              return;
          }
          c.beginPath();
          if (cor=='red') c.arc(xw+4, yw+4, 3, 0, 2 * Math.PI, false);
          else if (cor=='green')  c.arc(xw+10, yw+4, 3, 0, 2 * Math.PI, false);
          else if (cor=='blue')   c.arc(xw+4, yw+10, 3, 0, 2 * Math.PI, false);
          else c.arc(xw+10, yw+10, 3, 0, 2 * Math.PI, false);
          c.fill();
      }
      function Mark3(cor, xp, yp)
      {   var xw = xp*16+85;
          var yw = yp*16+85;
          c.fillStyle = cor;
          if (cor=='black' || cor=='white')
          {   c.fillRect(xw, yw, 10, 10);
              return;
          }
          if (cor=='null')
          {   c.clearRect(xw, yw, 10, 10);
              return;
          }
          c.beginPath();
          if (cor=='red') c.arc(xw+7, yw+7, 3, 0, 2 * Math.PI, false);
          c.fill();
      }
      // 左上にマークを描画
      function SetMark(mk)
      {   cor= mk;
          c.clearRect(20, 20, 14, 14);
          if (cor=='null')    return;
          c.fillStyle = cor;
          if (cor=='black' || cor=='white')
          {   c.fillRect(20, 20, 10, 10);
              return;
          }
          var w= 20+4;
          c.beginPath();
          if (cor=='red') c.arc(w, w, 3, 0, 2 * Math.PI, false);
          else if (cor=='green')  c.arc(w, w, 3, 0, 2 * Math.PI, false);
          else if (cor=='blue')   c.arc(w, w, 3, 0, 2 * Math.PI, false);
          else c.arc(w, w, 3, 0, 2 * Math.PI, false);
          c.fill();
      }
      // 選択行(s_line)を選択領域に描画
      function View_Sel(line)
      {   for(var i=0; i<xn; i++)
          {   if (line[i]==1) MarkS('black', i%5, Math.floor(i/5));
              if (line[i]==2) MarkS('white', i%5, Math.floor(i/5));
          }
      }
      // 抽出領域にマークを一個描画する
      function MarkS(cor, xp, yp)
      {   if (xp>5 || yp>3 || xp<0 || yp<0)   return;
          if (cor=='black')   s_line[yp*5+xp]= 1;
          if (cor=='white')   s_line[yp*5+xp]= 2;
          if (cor=='null')    s_line[yp*5+xp]= 0;
          var xw = xp*60+10+1;
          var yw = yp*40+340+1;
          c.fillStyle = cor;
          if (cor=='black' || cor=='white')
          {   c.fillRect(xw, yw, 58, 38);
              return;
          }
          if (cor=='null')
          {   c.clearRect(xw, yw, 58, 38);
              return;
          }
          c.beginPath();
          if (cor=='red') c.arc(xw+4, yw+4, 3, 0, 2 * Math.PI, false);
          else if (cor=='green')  c.arc(xw+10, yw+4, 3, 0, 2 * Math.PI, false);
          else if (cor=='blue')   c.arc(xw+4, yw+10, 3, 0, 2 * Math.PI, false);
          else c.arc(xw+10, yw+10, 3, 0, 2 * Math.PI, false);
          c.fill();
      }
      // 完成チェック
      function Fin()
      {   for(var i=0; i<yn; i++)
          {   for(var j=0; j<xn; j++)
              {   if (t[i][j]==0)
                  {   window.alert("未完成です");
                      return;
                  }
              }
          }
          for(i=0; i<xt.length; i++)
          {   var wk= xt[i];
              var p= 0;
              for(j=0; j<wk.length; j++)
              {   for(; p<xn && t[i][p]!=1; p++);
                  for(var c=0; p<xn && t[i][p]==1; c++, p++);
                  if (wk[j]!=c)
                  {   window.alert("Error 行:" + i);
                      return;
                  }
              }
          }
          for(i=0; i<yt.length; i++)
          {   wk= yt[i];
              p= 0;
              for(j=0; j<wk.length; j++)
              {   for(; p<yn && t[p][i]!=1; p++);
                  for(c=0; p<yn && t[p][i]==1; c++, p++);
                  if (wk[j]!=c)
                  {   window.alert("Error 列:" + i);
                      return;
                  }
              }
           }
           window.alert("*完成です");
      }
      // 罫線を描画する
      function View_Line()
      {   var i, j;
          // 線の色と太さ
          c.strokeStyle = 'gray';
          c.lineWidth = 2;
          c.beginPath();
          // 横線
          for(i=0; i<yn+1; i++)
          {   c.moveTo(base+70, i*16+base+70);
              c.lineTo(xn*16+base+70, i*16+base+70);
          }
          // 縦線
          for(i=0; i<xn+1; i++)
          {   c.moveTo(i*16+base+70, base+70);
              c.lineTo(i*16+base+70, yn*16+base+70);
          }
          // 枠の上と左
          c.moveTo(base, base);
          c.lineTo(xn*16+base+70, base);
          c.moveTo(base, base);
          c.lineTo(base, yn*16+base+70);
          // 5個ごとの横線
          for(i=0; i<=yn; i=i+5)
          {   c.moveTo(base, i*16+base+70);
              c.lineTo(xn*16+base, i*16+base+70);
          }
          // 5個ごとの縦線
          for(i=0; i<xn+1; i=i+5)
          {   c.moveTo(i*16+base+70, base);
              c.lineTo(i*16+base+70, yn*16+base);
          }
          c.stroke();
          // 数字の表示
          var cls = new Counter("numm.gif", 16, 16);
          for(i=0; i<xt.length; i++)
          {   for(j=0; j<xt[i].length; j++)
              {   cls.View_Num(xt[i][j], j*10+26, i*16+96); }
          }
          for(i=0; i<yt.length; i++)
          {  for(j=0; j<yt[i].length; j++)
             {   cls.View_Num(yt[i][j], i*16+96, j*12+26); }
          }
      }
      // 行・列の抽出枠を描画する
      function Select_Line()
      {   c.strokeStyle = 'gray';
          c.lineWidth = 2;
          c.beginPath();
          // 抽出行
          for(var i=0; i<4; i++)
          {   c.moveTo(10, i*40+340);     //横線
              c.lineTo(310, i*40+340);
          }
          for(i=0; i<6; i++)
          {   c.moveTo(i*60+10, 340);     //縦線
              c.lineTo(i*60+10, 460);
          }
          c.stroke();
      }
      // メニューを描画する
      function Menu()
      {   document.write('<div onclick= SetMark("black") style="position:absolute;left:30px;top:490px;">黑</div>');
          document.write('<div onclick= SetMark("white") style="position:absolute;left:90px;top:490px;">白</div>');
          document.write('<div onclick= SetMark("null") style="position:absolute;left:150px;top:490px;">消去</div>');
          document.write('<div onclick= Set(s_no) style="position:absolute;left:210px;top:490px;">設定</div>');
          document.write('<div onclick= SetMark("red") style="position:absolute;left:30px;top:530px;">赤</div>');
          document.write('<div onclick= SetMark("green") style="position:absolute;left:90px;top:530px;">緑</div>');
          document.write('<div onclick= SetMark("blue") style="position:absolute;left:150px;top:530px;">青</div>');
          document.write('<div onclick= SetMark("yellow") style="position:absolute;left:210px;top:530px;">黄</div>');
          document.write('<div onclick= Fin() style="position:absolute;left:270px;top:530px;">完成?</div>');
      }
      
    3. 問題毎に作成される *.html ファイルです。
      代表として英字の大文字Kをプレイする k.html を掲載します。
      <html>
      <head>
      <meta http-equiv="content-type" content="text/html; charset=utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1.0" />
      <title>K</title>
      <script src="dot.js">
      </script>
      </head>
      
      <body bgcolor=#e0d8d0>
      <canvas id="mycanvas" width="330" height="470"></canvas>
      <style>
      canvas
      {   border: 1px solid silver;  }
      </style>
      
      <script>
          xt = [[0],[2,2],[2,2],[2,2],[2,2],[2,2],[4],[3],[4],[2,2],[2,2],[2,2],[2,2],[2,2],[0]];
          yt = [[0],[0],[0],[13],[13],[3],[2,2],[2,2],[2,2],[2,2],[2,2],[1,1],[0],[0],[0]];
          xn= 15;     //yt.length;    // 横方向のマス
          yn= 15;     //xt.length;    // 縦方向のマス
          cor= 'black'; 
          base= 16;                   // 枠の基点
          s_line= new Array(xn);      // 解析エリア
          s_no= -1;                   // 行(0~15),列(100~115)
          s_data= [0];
          t= new Array(xn);           // 0:未定, 1:マーク, 2:空白
          for(var i=0; i<yn; i++)
          {   t[i]= new Array(xn);
          }
          for(var i=0; i<yn; i++)
              for(var j=0; j<xn; j++)
                  t[i][j]= 0;
      
        document.onmousedown =
          function(e)
          {   if (!e)  e= window.event;
              var xp = Math.floor((e.clientX-115)/16);
              var yp = Math.floor((e.clientY-115)/16);
              var click= e.clientX + "," + e.clientY;
              document.getElementById("click").textContent= click;
              var pos= xp + "," + yp;
              document.getElementById("pos").textContent= pos;
      
              // マスのクリック
              if (xp>=0 && xp<xn && yp>=0 && yp<yn)
              {   Mark(cor, xp, yp);
                  if (cor=='null')    t[yp][xp]= 0;
                  if (cor=='black')   t[yp][xp]= 1;
                  if (cor=='white')   t[yp][xp]= 2;
                  return;
              }
              // 行を抽出する
              if (xp<0 && yp<yn)
              {   Select(yp);
                  return;
              }
              // 列を抽出する
              if (yp<0 && xp<xn)
              {   Select(xp+100);
                  return;
              }
              if (yp<=yn) return; 
              // 抽出領域の座標(10,340 340,460)
              var xp = Math.floor((e.clientX-35)/60);
              var yp = Math.floor((e.clientY-365)/40);
              var pos= xp + "," + yp;
              document.getElementById("pos").textContent= pos;
              if (yp<3) 
              {   MarkS(cor, xp, yp)
                  return;
              }
          }
      
          var canvas = document.getElementById('mycanvas');
          c = canvas.getContext('2d');
          View_Line();
          Select_Line();
          Menu();
      </script>
      <p>
        <div id="click" style="position:absolute;left:26px;top:40px;">click</div><br>
        <div id="pos" style="position:absolute;left:26px;top:55px;">pos</div><br>
        <div style="position:absolute;left:26px;top:75px;">英字K</div><br>
      </p>
      
      </body>
      </html>