お絵描きゲーム

今は携帯全盛ですが、一昔前までは通勤電車で「お絵描きゲーム」に夢中になっている方を良く見かけたものです。
それほど面白く多くの人に親しまれていたドットで絵を描く「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. 本来ならお絵描きゲームのデータはファイルから入力するのが本筋でしょう。
    所が JavaScript でファイルからデータを入力しようとすると、入力のクリックとパズルのクリックが共存出来ません。
    また、入力して数字を描画すると 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_getfile.html をクリックするとファイルを選択してゲームを開始します。

    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)