お絵描きゲーム

今は携帯全盛ですが、一昔前までは通勤電車で「お絵描きゲーム」に夢中になっている方を良く見かけたものです。
それほど面白く多くの人に親しまれていたドットで絵を描く「Logic Puzzle」を JavaScript で作成します。
次のリンクをクリックすると β版のページ 「いちご」のパズルが楽しめます。

ゲームの概要

  1. Win32 API で作成したページ先頭の画像を見て下さい。
    縦横に15個のマスが並んでいます。
    マスの上には、縦方向にマークする数字が、マスの左には横方向にマークする数字が表示されています。
    指定された数のマスを塗りつぶしてドット絵を完成させるゲームです。
  2. Logic Puzzle の最も基本的なマークの方法に付いて説明します。
    例として15個の列に9個マークする場合を考えます。
    9個マークを左詰めで格納すると次のようになります。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    
    次にマークを右詰めで格納します。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    ・・・・・・■■■■■■■■■     右に詰めたとき
    
    このとき中央の重なった3個のマークが確定できます。
    ■■■■■■■■■・・・・・・     左に詰めたとき
    ・・・・・・■■■■■■■■■     右に詰めたとき
    ・・・・・・★★★・・・・・・     マークが確定
    
  3. 最も基本的なマークが確定したら、そこからは思考を凝らして進めて行きます。
    難問を解くには数時間(数日)かかることもあるようです。

プログラムの開発手順

  1. Canvas にマスを描きます。(dot_line.html)
    ソースコードを掲載していない場合は、html を実行して右クリックから「ソースの表示」で確認することが出来ます。
    Canvas にマスを描く を右クリックしてみて下さい。
    <html>
    <head>
    <meta charset=utf-8>
    <link rel="stylesheet" href="javascript.css" type="text/css">
    <title>お絵描き</title>
    </head>
    
    <body>
    <canvas id="mycanvas" width="240" height="240"></canvas>
    <style>
    canvas
    {   border: 1px solid silver;  }
    </style>
    
    <script>
    var canvas = document.getElementById('mycanvas');
    c = canvas.getContext('2d');
    
        // 線の色と太さ
        mode= 15;
        c.strokeStyle = 'gray';
        c.lineWidth = 2;
        c.beginPath();
        for(var i=0; i<mode+1; i++)
        {   c.moveTo(0, i*13);
            c.lineTo(mode*13, i*13);
            c.moveTo(i*13, 0);
            c.lineTo(i*13, mode*13);
        }
        c.stroke();
    </script>
    
    </body>
    </html>
    
  2. c.moveTo() と c.lineTo() で横線と縦線を引いてマスを描画します。
    Canvas のプログラムは Canvas を定義線を描画 を参照して下さい。

  3. マスの上と左に Counter Class を使ってパズルの数字を表示します。(dot_num.html)
    パズルの数字を表示 をクリックしてみて下さい。
    <html>
    <head>
    <meta charset=utf-8>
    <title>お絵描き</title>
    <script>
    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);
                var n= 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);
                return;
            }
            var pos = num*this.Sw;
            var s1= 'style="clip:rect(0px,' + (pos+this.Sw) + 'px,' + this.Sh + 'px,' + pos + 'px);';
            var s2 = 'position:absolute;left:' + (x-pos) + 'px;top:' + y + 'px;';
            var s = '<img src="' + this.Img + '"' + s1 + s2 + '">';
            document.write(s);
        }
    }
    </script>
    </head>
    
    <body bgcolor=#e0d8d0>
    <canvas id="mycanvas" width="360" height="360"></canvas>
    <style>
    canvas
    {   border: 1px solid silver;  }
    </style>
    
    <script>
    var canvas = document.getElementById('mycanvas');
    c = canvas.getContext('2d');
    
        mode= 15;
        cor= 'black';
        // 線の色と太さ
        c.strokeStyle = 'gray';
        c.lineWidth = 2;
        c.beginPath();
        for(var i=0; i<mode+1; i++)
        {   c.moveTo(0+70, i*16+70);
            c.lineTo(mode*16+70, i*16+70);
            c.moveTo(i*16+70, 70);
            c.lineTo(i*16+70, mode*16+70);
        }
        c.stroke();
    
        var xt = [[2],[4,2],[7,2],[2,6],[3,6],[2,1,5],[6,5],[1,4,3],[4,3,4],[3,5,2],[7,3,1],[2,2,3,3],[7,3],[1,2,4],[7]];
        var yt = [[6],[8,1],[4,3,3],[3,3,5],[2,9,1],[2,2,2,3],[4,9],[4,2,1,1],[5,6],[5,1,1],[6,3],[7,1],[2,7],[2,2],[1]];
    
        var cls = new Counter("numm.gif", 16, 16);
        for(var i=0; i<xt.length; i++)
           for(var j=0; j<xt[i].length; j++)
               cls.View_Num(xt[i][j], j*10+10, i*16+80);
        for(var i=0; i<yt.length; i++)
           for(var j=0; j<yt[i].length; j++)
               cls.View_Num(yt[i][j], i*16+80, j*12+10);
    </script>
    
    </body>
    </html>
    
  4. このゲームに使用する数字の画像(numm.gif)を作成しました。

    画像でカウンターを表示するプログラムは カウンタークラス を参照して下さい。
    お絵描きパズルでは、マスに2桁の数字が対応する場合があり、2桁のときは詰めて描画しています。

  5. クリックでマスを塗りつぶします。(dot_black.html)
    クリックで塗りつぶす
    <script>
      document.onmousedown =
        function(e)
        {   if (!e)  e= window.event;
            //window.alert("X:" + e.clientX + "  Y:" + e.clientY);
            var xp = Math.floor((e.clientX - 10)/16)*16+7;
            var yp = Math.floor((e.clientY - 10)/16)*16+7;
            //window.alert("XP:" + xp + "  YP:" + yp + "  cor:" + cor);
    
            c.fillStyle = 'black';
            c.fillRect(xp, yp, 14, 14);
        }
    
  6. クリックされた座標からマスの座標を計算して、マスを黒く塗りつぶします。

  7. セルのマーク&消去を設定します。(dot_mark.html)
    カラー&消去を設定
      document.onmousedown =
        function(e)
        {   if (!e)  e= window.event;
            if (e.clientY>380 && e.clientY<420)
            {   if (e.clientX<50)       cor= 'black';
                else if (e.clientX<100) cor= 'red';
                else if (e.clientX<150) cor= 'green';
                else if (e.clientX<200) cor= 'blue';
                else if (e.clientX<250) cor= 'yellow';
                else if (e.clientX<290) cor= 'white';
                else cor= 'null';
                return;
            } 
            var xp = Math.floor((e.clientX - 10)/16)*16+7;
            var yp = Math.floor((e.clientY - 10)/16)*16+7;
    
            c.fillStyle = cor;
            if (cor=='black' || cor=='white')
            {   c.fillRect(xp, yp, 14, 14);
                return;
            }
            if (cor=='null')
            {   c.clearRect(xp, yp, 14, 14);
                return;
            }
            c.beginPath();
            if (cor=='red') c.arc(xp+4, yp+4, 3, 0, 2 * Math.PI, false);
            else if (cor=='green')  c.arc(xp+10, yp+4, 3, 0, 2 * Math.PI, false);
            else if (cor=='blue')   c.arc(xp+4, yp+10, 3, 0, 2 * Math.PI, false);
            else c.arc(xp+10, yp+10, 3, 0, 2 * Math.PI, false);
            c.fill();
        }
    
        ・・・
    
    </script>
    <p>
      <div>黑  赤  緑  青  黄  白  消去</div><br>
    </p>
    
  8. お絵描きゲームでは、マスに印を付けたり、黒く塗りつぶしたり、取り消したりと試行錯誤を繰り返します。
    黑, 赤, 緑, 青, 黄, 白, 消去をクリックすると、マークの色と種類が設定されます。
    これで一応ゲームをプレイ出来るレベルになります。

  9. マークの基本手順をサポートしてα版の完成です。(dot_alpha.html)
    α版の完成
    function Cross(num, data)
    {   var ary= new Array();
        var i;
        window.alert("data:" + data);
        for(i=0; i<data.length; i++)
        {   for(j=0; j<data[i]; j++)    ary.push(i);
            if (ary.length<num) ary.push(-1);
        }
        var bk= new Array();
        for(i=data.length-1; i>=0; i--)
        {   for(j=0; j<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])  ary[i]= 1;
            else   ary[i]= 2;  
        }
        return ary;
    }
    </script>
    </head>
    
    <script>
        var xt = [[2],[4,2],[7,2],[2,6],[3,6],[2,1,5],[6,5],[1,4,3],[4,3,4],[3,5,2],[7,3,1],[2,2,3,3],[7,3],[1,2,4],[7]];
        var yt = [[6],[8,1],[4,3,3],[3,3,5],[2,9,1],[2,2,2,3],[4,9],[4,2,1,1],[5,6],[5,1,1],[6,3],[7,1],[2,7],[2,2],[1]];
        xn= yt.length;  // 横方向のマス
        yn= xt.length;  // 縦方向のマス
    
      document.onmousedown =
        function(e)
        {   if (!e)  e= window.event;
            var xp = Math.floor((e.clientX-75)/16);
            var yp = Math.floor((e.clientY-75)/16);
            //window.alert("XP:" + xp + "  YP:" + yp + "  cor:" + cor);
    
            if (yp>yn+1)
            {   if (e.clientX<50)       cor= 'black';
                else if (e.clientX<120) cor= 'white';
                else if (e.clientX<170) cor= 'red';
                else if (e.clientX<220) cor= 'green';
                else if (e.clientX<270) cor= 'blue';
                else if (e.clientX<320) cor= 'yellow';
                else cor= 'null';
                return;
            } 
    
            var xw = xp*16+71;
            var yw = yp*16+71;
    
            // 行のクロスを調べる
            if (xp>=xn && yp<yn)
            {   var ans= Cross(xn, xt[yp]);
                c.fillStyle = 'black';
                for(var i=0; i<xn; i++)     
                {   if (ans[i]==1)  c.fillRect(i*16+71, yw, 14, 14);
                    else   c.clearRect(i*16+71, yw, 14, 14);
                }
                c.fill();
                return;
            }
            // 列のクロスを調べる
            if (yp>=yn && xp<xn)
            {   var ans= Cross(yn, yt[xp]);
                c.fillStyle = 'black';
                for(var i=0; i<yn; i++)     
                {   if (ans[i]==1)  c.fillRect(xw, i*16+71, 14, 14);
                    else   c.clearRect(xw, i*16+71, 14, 14);
                }
                c.fill();
                return;
            }
            if (xp>=xn || yp>=yn)   return;
    
            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();
        }
    
        var canvas = document.getElementById('mycanvas');
        c = canvas.getContext('2d');
    
        cor= 'black';
        // 線の色と太さ
        c.strokeStyle = 'gray';
        c.lineWidth = 2;
        c.beginPath();
        // 横線
        for(var i=0; i<yn+1; i++)
        {   c.moveTo(70, i*16+70);
            c.lineTo(xn*16+70, i*16+70);
        }
        // 縦線
        for(var i=0; i<xn+1; i++)
        {   c.moveTo(i*16+70, 70);
            c.lineTo(i*16+70, yn*16+70);
        }
        for(var i=0; i<yn+1; i=i+5)
        {   c.moveTo(0, i*16+70);
            c.lineTo(70, i*16+70);
        }
        for(var i=0; i<xn+1; i=i+5)
        {   c.moveTo(i*16+70, 0);
            c.lineTo(i*16+70, 70);
        }
        c.stroke();
    
        var cls = new Counter("numm.gif", 16, 16);
        for(var i=0; i<xt.length; i++)
           for(var j=0; j<xt[i].length; j++)
               cls.View_Num(xt[i][j], j*10+10, i*16+80);
        for(var i=0; i<yt.length; i++)
           for(var j=0; j<yt[i].length; j++)
               cls.View_Num(yt[i][j], i*16+80, j*12+10);
    </script>
    <p>
      <div>黑  白   赤  緑  青  黄   消去</div><br>
    </p>
    
  10. 行または列の重なるマスを黒く塗りつぶす Cross() 関数を組み込みます。
    行の右(または 列の下)をクリックすると、その行または列の重なるマスが塗りつぶされます。
    これで決まり切った面倒な操作をコンピュータに任せることが出来ます。

公開ゲーム

  1. ホームページで公開する「お絵描きゲーム」を作成します。
    パズルのデータを直接プログラム内で定義するので、共通部分を JavaScript File(dotalpha.js) に記述して組み込みます。
    dotalpha.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);
                var n= 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);
                return;
            }
            var pos = num*this.Sw;
            var s1= 'style="clip:rect(0px,' + (pos+this.Sw) + 'px,' + this.Sh + 'px,' + pos + 'px);';
            var s2 = 'position:absolute;left:' + (x-pos) + 'px;top:' + y + 'px;';
            var s = '<img src="' + this.Img + '"' + s1 + s2 + '">';
            document.write(s);
        }
    }
    
    // マークゼロの行(列)を調べます(2:白)
    function Cross(num, data)
    {   var ary= new Array();
        var i;
        window.alert("data:" + data);
        if (data[0]==0)
        {   for(i=0; i<num; i++)    ary[i]= 2;
            return ary;
        }
        for(i=0; i<data.length; i++)
        {   for(j=0; j<data[i]; j++)    ary.push(i);
            if (ary.length<num) ary.push(-1);
        }
        var bk= new Array();
        for(i=data.length-1; i>=0; i--)
        {   for(j=0; j<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);
        }
        //window.alert("array:" + ary);
        //window.alert("back:" + bk);
        for(i=0; i<num; i++)
        {   if (ary[i]!=-1 && ary[i]==bk[num-i-1])  ary[i]= 1;
            else   ary[i]= 0;  
        }
        return ary;
    }
    
    // マスにマークを一個描画する
    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+base+71;
        var yw = yp*16+base+71;
        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 View_Line()
    {   // 線の色と太さ
        c.strokeStyle = 'gray';
        c.lineWidth = 2;
        c.beginPath();
        // 横線
        for(var 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(var 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+1, base+1);
        c.lineTo(xn*16+base+70, base+1);
        c.moveTo(base+1, base+1);
        c.lineTo(base+1, yn*16+base+70);
        for(var i=0; i<yn+1; i=i+5)
        {   c.moveTo(base, i*16+base+70);
            c.lineTo(base+70, i*16+base+70);
        }
        for(var i=0; i<xn+1; i=i+5)
        {   c.moveTo(i*16+base+70, base);
            c.lineTo(i*16+base+70, base+70);
        }
        c.stroke();
        // 数字の表示
        var cls = new Counter("numm.gif", 16, 16);
        for(var i=0; i<xt.length; i++)
           for(var j=0; j<xt[i].length; j++)
               cls.View_Num(xt[i][j], j*10+base+10, i*16+base+80);
        for(var i=0; i<yt.length; i++)
           for(var j=0; j<yt[i].length; j++)
               cls.View_Num(yt[i][j], i*16+base+80, j*12+base+10);
    }
    
  2. Counter() は、画像で数字を描画する Class です。
    Cross() は、行(列)を解析して確定するセルを調べます。
    Mark(), Mark2() は、セルにマーク(黑, 白, 削除)などを一個描画します。
    セルの罫線は base を基点に引かれています。
    71 は、行/列の表示を考慮した補正値です。
    View_Line() でセルの罫線を描画します。
    base はマスの罫線を描画する基点です。
  3. いちごの「お絵描きゲーム」をプレイする dot_ichigo.html です。
    <html>
    <head>
    <meta charset=utf-8>
    <title>いちご</title>
    <script src="dotalpha.js">
    </script>
    </head>
    
    <body bgcolor=#e0d8d0>
    <canvas id="mycanvas" width="340" height="340"></canvas>
    <style>
    canvas
    {   border: 1px solid silver;  }
    </style>
    
    <script>
        xt = [[2],[4,2],[7,2],[2,6],[3,6],[2,1,5],[6,5],[1,4,3],[4,3,4],[3,5,2],[7,3,1],[2,2,3,3],[7,3],[1,2,4],[7]];
        yt = [[6],[8,1],[4,3,3],[3,3,5],[2,9,1],[2,2,2,3],[4,9],[4,2,1,1],[5,6],[5,1,1],[6,3],[7,1],[2,7],[2,2],[1]];
        xn= yt.length;  // 横方向のマス
        yn= xt.length;  // 縦方向のマス
        cor= 'black';
        base= 16;       // マスの基点
    
      document.onmousedown =
        function(e)
        {   if (!e)  e= window.event;
            //window.alert("X:" + e.clientX + "  Y:" + e.clientY);
            var xp = Math.floor((e.clientX-base-75)/16);
            var yp = Math.floor((e.clientY-base-75)/16);
            //window.alert("XP:" + xp + "  YP:" + yp + "  cor:" + cor);
    
            // マスのクリック
            if (xp>=0 && xp<xn && yp>=0 && yp<yn)
            {   Mark(cor, xp, yp);
                return;
            }
            // 行のクロスを調べる
            if (xp<-4 && yp<yn)
            {   var ans= Cross(xn, xt[yp]);
                for(var i=0; i<xn; i++)     
                {   if (ans[i]==1)          Mark('black', i, yp);
                    else  if (ans[i]==2)    Mark('white', i, yp);
                    else                    Mark('null', i, yp);
                }
                return;
            }
            // 列のクロスを調べる
            if (yp<-4 && xp<xn)
            {   var ans= Cross(yn, yt[xp]);
                for(var i=0; i<yn; i++)     
                {   if (ans[i]==1)          Mark('black', xp, i);
                    else  if (ans[i]==2)    Mark('white', xp, i);
                    else                    Mark('null', xp, i);
                }
                return;
            }
            // 色の選択
            if (yp>yn+1)
            {   if (e.clientX<50)       cor= 'black';
                else if (e.clientX<120) cor= 'white';
                else if (e.clientX<170) cor= 'red';
                else if (e.clientX<220) cor= 'green';
                else if (e.clientX<270) cor= 'blue';
                else if (e.clientX<320) cor= 'yellow';
                else cor= 'null';
                return;
            }
        }
    
        var canvas = document.getElementById('mycanvas');
        c = canvas.getContext('2d');
        View_Line();
    </script>
    <p>
      <div>黑  白   赤  緑  青  黄   消去</div><br>
    </p>
    
    </body>
    </html>
    
  4. 他のパズルをプレイするときは xt と yt を書き換えて下さい。
    行または列の重なるマスを黒く塗りつぶす Cross() 関数を組み込んでいます。
    矩形の左(または上)をクリックすると、その行または列のヒントが表示されます。
    行の右, 列の下では間違ってクリックすることが良くあり、左(または上)に変更しました。
    JavaScript メインメニューの「お絵描きゲーム」から、すぐ遊べるようにリンクを張っています。
    誰でも簡単に起動できるので「お絵描きゲーム」を楽しんで下さい。

  1. 本来ならお絵描きゲームのデータはファイルから入力するのが本筋でしょう。
    所がマウスのクリック制御と Canvas 描画の関係から、データ入力のページとゲーム操作のページを分けなければなりません。
    そのとき問題となるのがデータのサイズが大きく、そのままでは渡せないことです。(最大 256 Byte)
    そこで Version-2 ではデータを圧縮して渡すことにしました。
    数字のデータは一件ずつ空白で区切られていますが、これを16進数もどきに変換して全て一文字に変換します。
    そうすると区切りが不要になり、約半分のサイズになります。
    結果として、ビッグサイズのパズル以外はプレイ出来るようになりました。
    16進数もどきの数字の定義です。
    012~91011~1516~24
    012~9AB~FG~P
    numt= ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P'];
    
  2. バージョンアップに伴い、以下の改良をします。
  3. バージョンアップしたゲームをプレイするには *.dot ファイルをダウンロードしてパソコンに格納して下さい。
    dot_getfile.html を起動してダウンロードした *.dot ファイルを選択して下さい。
    パズルを選択すると Game Play が表示されるので、クリックするとお絵描きパズルが始まります。
    サイズの大きいパズルは "DOT File Size Over" が表示されて実行できません。
    お絵描きパズルのファイル入力をクリックするとファイルを選択してゲームを開始します。

    DOT File の Down Load

    お絵描きパズルの Down Load

  1. ビッグサイズのパズル(圧縮しても 256 Byte を超える)を PHP の助けを借りてプレイします。
    ビッグサイズのデータは GET では渡せないので POST で渡さなければなりません。
    GET と POST の説明は GET パラメータを取得 POST で送信 を参照して下さい。
    dot_data.html でデータファイルを入力して dot_game.php を呼び出します。
  2. データファイルを入力して dot_game.php を呼び出す dot_data.html のソースコードです。
    Javascript のファイル入力は Read Text File を参照して下さい。
    改行コード(\n)を「;」に置き換えて渡します。
    PHP のプログラムでは、パソコンからビルトインサーバーを起動して呼び出しています。('http://localhost:8000/dot_game.php')
    テストが終わった段階で、PHP をサポートしているサーバーにアップロードします。
    <html>
    <head>
    <meta charset=utf-8>
    <title>お絵描き</title>
    </head>
    
    <body bgcolor=#e0d8d0>
    <h1>お絵描きパズル</h1>
    データファイルを選択して下さい。<br><br>
    <form name="test">
    <input type="file" id="selfile" /><br/>
    </form>
    
    <script>
    function postForm(value)
    {   var form = document.createElement('form');
        var request = document.createElement('input');
     
        form.method = 'POST';
        form.action = 'http://localhost:8000/dot_game.php';
     
        request.name = 'data';
        request.value = value;
     
        form.appendChild(request);
        document.body.appendChild(form);
     
        form.submit();
    }
    
        var obj1 = document.getElementById("selfile");
        //ダイアログでファイルが選択された時
        obj1.addEventListener("change",function(evt)
        {   var file = evt.target.files;
            var reader = new FileReader();
            reader.readAsText(file[0]);
      
            //読込終了後の処理
            reader.onload = function(ev)
            {   var str= reader.result;
                str= str.replace(/\n/g, ';');
                postForm(str);
            }
        },false);
    </script>
    
    </body>
    </html>
    
  3. ファイルからデータを入力する「お絵描きゲーム」を PHP にバトンを渡して完成させます。
    この先は PHP の「お絵描きパズル」を参照して下さい。

  1. 「お絵描きパズル」は結構難しく、問題が間違っているのでは?と思うこともしばしばあります。
    そこで最後にプログラムでパズルを解いてみましょう。
  2. 「お絵描きパズル」のデータは dotdata.js にタイプして dot_auto.html に組み込みます。
    //K, 月, 五重の塔, ヨット, クルール
        xdata=
         [[[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]],
          [[3,1,5],[1,3,3],[1,1,4,2],[9,1],[1,6,1],[3,5,1],[1,6,1],[9],[1,4],[3,5,1],[1,8,1],[10,1],[2,4,2],[1,1,3,3],[2,1,5]],
          [[1],[1],[3],[9],[7],[1,1],[7],[9],[1,1,1],[2,7],[2,9],[3,1,1],[2,7,1],[1,9,2],[2,1,1,3],[3,9,3],[3,11,4],[3,7,4],[4,7,5],[4,7,5]],
          [[1,1],[2,2],[2,2],[3,3],[3,1,1],[4,1,2],[4,1,2],[2,1,1,2],[6,1,1],[3,4],[4],[5],[13],[11],[9]],
          [[1,1,1,1],[1,1,1,1],[1,3],[4,5],[13],[13],[2,3,1,2],[2,1,1,2,1],[2,3,4],[15],[16],[4,2,5],[3,2,2,3],[13,2],[2,11,4],[3,5,5,4],[4,1,1,1,1,1,5],[4,1,1,1,1,1,5],[4,9,5],[5,7,6]],
            ・・・
         ];
    
    //K, 月, 五重の塔, ヨット, クルール
        ydata=
         [[[0],[0],[0],[13],[13],[3],[2,2],[2,2],[2,2],[2,2],[2,2],[1,1],[0],[0],[0]],
          [[2,3,2],[1,2,1,1,1,1,2],[2,1,1,1,1,2,1],[1,2,1,1,1,3],[2,3,2],[15],[13],[13],[6,4],[3,3],[1,1,1],[1,2,1,1],[2,1,2],[3,3],[5,5]],
          [[12],[4,6],[1,5],[2],[1],[1,1,1,1,2],[2,2,2,2,5],[17],[3,2,2,2,5],[5,2,2,2,5],[3,2,2,2,5],[17],[2,2,2,2,5],[1,1,1,1,2],[1],[2],[4],[6],[7],[8]],
          [[1],[2,1],[3,2],[4,3],[4,3],[4,1,3],[6,1,3],[9,3],[3],[9,3],[3,4],[3,1,4],[2,1,3],[2,1,2],[3,1]],
          [[5],[6],[3,6],[4,5,4],[13,1],[3,4,6],[6,2,4,2],[3,1,2,8],[2,3,3,3,2],[8,2,4],[2,3,4,3,2],[3,1,2,8],[8,2,4,2],[4,4,6],[14,1],[4,6,4],[5,6],[3,7],[7],[6]],
            ・・・
         ];
    <script src="dotdata.js">
    
    そして dot_menu_auto.html からラジオボタンでパズルを選択して dot_auto.html を呼び出します。
    <form action="dot_auto.html" method="get">
      <input type=radio name="str" value="0/英字K" /> 英字K
      <input type=radio name="str" value="1/月" /> 月
          ・・・
      <input type=radio name="str" value="57/地獄少女" /> 地獄少女<br>
    
      <input type="submit" value="実行" />
    </form>
    
    また「保存パズルの実行ファイルを選択して下さい」からダウンロードフォルダーの puzzle.dot を選択して中断処理を再開することが出来ます。
            //読込終了後の処理
            reader.onload = function(ev)
            {   var str= reader.result;
                var wk= "dot_auto.html?str=" + str;
                location.href = wk;
            }
    
    puzzle.dot は dot_auto.html の中断メニューで保存される「操作履歴」で通常の DOT ファイルではありません。
  3. パズルを選択する dot_menu_auto.html のソースコードです。
    <html>
    <head>
    <meta charset=utf-8>
    <title>Dot Menu</title>
    </head>
    
    <body bgcolor=#e0d8d0>
    <h2>お絵描きパズルを選択して下さい</h2>
    
    <form action="dot_auto.html" method="get">
      <input type=radio name="str" value="0/英字K" /> 英字K
      <input type=radio name="str" value="1/月" /> 月
      <input type=radio name="str" value="2/五重の塔" /> 五重の塔
      <input type=radio name="str" value="3/ヨット" /> ヨット
      <input type=radio name="str" value="4/クルール" /> クルール<br>
         ・・・
    
      <input type="submit" value="実行" />
    </form>
    
    <hr>
    
    保存パズルの実行ファイルを選択して下さい。<br>
    <form name="test">
    <input type="file" id="selfile" /><br/>
    </form>
    
    <script>
        var obj1 = document.getElementById("selfile");
        //ダイアログでファイルが選択された時
        obj1.addEventListener("change",function(evt)
        {   var file = evt.target.files;
            var reader = new FileReader();
            reader.readAsText(file[0]);
      
            //読込終了後の処理
            reader.onload = function(ev)
            {   var str= reader.result;
                var wk= "dot_auto.html?str=" + str;
                location.href = wk;
            }
        },false);
    </script>
    </body>
    </html>
    
  4. dot_auto.html が呼び出された時、パラメータ(str)には「DOT データの番号」または「中断処理の操作履歴」が渡されます。
    例えば「いちご」のパズルを選択したときは、次のパラメータが渡されます。
    14/いちご
    
    中断処理の操作履歴は、例えば次のような形式で渡されます。
    操作履歴は / で区切られプレイの再現(やり直し)に使用されます。
    0/英字K/white,0,0/white,1,0/black,3,5/14/-
    
  5. プログラムでパズルを解く主要関数です。
    行または列を1行取得して Think() 関数を呼び出します。
    // 行・列(s_line, s_data) を解析して確定マークを設定する
    function Think()
    {   Debug();
        var len= s_line.length;
        var wk= [];
        l_line= Lset(s_line, s_data);
        r_line= Rset(s_line, s_data);
        wk= Match(s_line, l_line, r_line);
        //DebugDot(wk);
        return wk;
    }
    
    s_data を左詰めで数字をマスに割り当てて l_line に格納します。
    // s_data を左詰めでマップする
    function Lset(s_line, s_data)
    {   var i, j, p;
        var len= s_line.length;
        var mkt= [];
        for(i=0; i<len; i++)    mkt.push(-1);
        for(p=0, i=0; i<s_data.length; i++)
        {   for(j=0; j<s_data[i] && p<len; j++, p++)  mkt[p]= i;
            p++;
        }
        mkt= Adjust(mkt, s_line);
        return mkt;
    }
    
    s_data を右詰めで数字をマスに割り当てて r_line に格納します。
    // 反転して data を右詰めでマップする
    function Rset(s_line, s_data)
    {   var len= s_line.length;
        var dlen= s_data.length;
        var wln= s_line.slice(0);
        var wdt= s_data.slice(0);
        wln= wln.reverse();
        wdt= wdt.reverse();
        var wkt= Lset(wln, wdt);
        mkt= wkt.reverse();
        for(var i=0; i<len; i++)
        {   if (mkt[i]!=-1) mkt[i]= dlen-mkt[i]-1;  }
        return mkt;
    }
    
    行・列の既存のマークと空白に合わせて数字を調整します。
    この関数がパズルを解く「最も主要なキーポイント」です。
    // mkt(idx:00,111,2, ...) を s_line(0,1,2) のマークに合わせて調整する
    function Adjust(mkt, s_line)
    {   var i, j, rt, pt, ml, lt;
        var len= s_line.length;
        var wt= mkt.slice(0);           //リターン値
        for(i=0; i<len; i++)
        {   if (s_line[i]==2 && wt[i]!=-1)
            {   for(rt=i; rt<len && s_line[rt]==2; rt++);
                for(pt=i; pt>=0 && wt[pt]!=-1; pt--);
                pt++;
                wt= StrMove(wt, rt, pt, len-rt);
            }
        }
        for(i=len-1; i>0; i--)
        {   if (s_line[i]==1 && wt[i]==-1)
            {   for(rt=i; rt>=0 && wt[rt]==-1; rt--);
                rt++;
                for(pt=rt-1; pt>=0 && wt[pt]!=-1; pt--);
                pt++;
                ml= rt-pt;
                lt= i-ml+1;
                wt= StrMove(wt, lt, pt, ml);
            }
        }
        return wt;
    }
    
    Match() でマークを確定します。
    // LLINE と RLINE を照合してマークする
    function Match(s_line, l_line, r_line)
    {   var len= s_line.length;
        var wt= s_line.slice(0);
        var ln= -1;
        var rn= -1;
        for(var i=0; i<len; i++)
        {   if (l_line[i]!=-1) ln= l_line[i];
            if (r_line[i]!=-1) rn= r_line[i];
            if (l_line[i]==r_line[i])
            {   if (l_line[i]!=-1)   wt[i]= 1;
                else if (ln==rn)     wt[i]= 2;
            }
        }
        return wt;
    }
    
  6. プログラムで「お絵描きパズル」が解けるようになると、マウスのクリックだけで解答が得られます。
    それでは余りにも芸が無いので、プログラムの作成は読者に任せます。
    最近ネットで検索していて「プログラムを駆使しても解けない」難問のページに出くわしました。
    読者諸氏も挑戦してみて下さい。

前田稔の超初心者のプログラム入門
超初心者のプログラム入門(JavaScript)