11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

N/S高等学校Advent Calendar 2022

Day 6

【JavaScript】簡単なナンプレを作ってみました!

Last updated at Posted at 2022-12-05

はじめに

私はN予備校といいう教材でプログラミングの勉強を始めたN高生です。
教材には種類があり、私は【プログラミング入門 webアプリ】という物を学んでいます。
そして、プログラミング入門の中でコンテストが開催され、アウトプットのために参加しました。
提出期限があり、作り始めた日から一ヶ月ほどでした。
そこでナンプレを作成したので、Javascriptを主に作成時の難しかった事を記事にします。

今回作成したもの

nanpure_1_png

ナンプレへ

ナンプレとは

縦・横全ての列と、3×3のマスごとのブロックに1〜9が一つずつ入る数字が入ることがルールです。
nanpure_2_png

難しいと感じたこと

数字を表示すつマス目を作る

まず、マス目を作ることが難しかったです。
cssをどんな感じに使えばいいのかがわかりませんでした。
最初は、HTMLのファイルにdivを81個書いて一個一個cssで作るというのが思い浮かんだのですがもっといい方法があるだろうと思い調べてました。

cloneNode(javascript)とnth-child(css)などを使った作り方があることがわかりました。
参考 JavaScriptでオセロゲームを作成する方法を現役エンジニアが解説【初心者向け】

マス目 ソースコード

index.html
<div id="gameBox">
  <!-- テンプレート -->
  <div id="square-template" class="square">
      <div class="number"></div>
  </div>
</div>
create.js
const createSquares = () => {
    const gameBox = document.getElementById("gameBox");
    const squareTemplate = document.getElementById("square-template");
    for (let i = 0; i < 81; i++) {
        const square = squareTemplate.cloneNode(true); 
        square.removeAttribute("id");
        //HTMLのgameBoxに追加 
        gameBox.appendChild(square); 
        const number = square.querySelector('.number');

        //後々編集するため目印をつける
        number.setAttribute("data-index", i);
        square.addEventListener('click', () => {
            onClickSquare(i);
        })
    }
};
nanpure.css
*,*::before,
*::after {
  box-sizing: border-box;
}
.gameBox {
  display: flex;
  flex-wrap: wrap;
  margin: 0 auto;
  width: 454px;
  height: 454px;
}
.square {
  position: relative;
  width: 50px;
  height: 50px;
  border: solid black;
  border-width: 0 4px 4px 0;
  cursor: pointer;
}
/* 枠線下なし */
.square:nth-child(-n + 9) {
  border-width: 4px 4px 4px 0;
  height: 54px;
}
/* 段落下げ*/
.square:nth-child(9n + 1) {
  border-width: 0 4px 4px 4px;
  width: 54px;
}

.square:first-child {
  border-width: 4px;
  width: 54px;
  height: 54px;
}

for文一回ごとの処理

const square = squareTemplate.cloneNode(true); 
square.removeAttribute("id");
//HTMLのgameBoxに追加 
gameBox.appendChild(square); 
const number = square.querySelector('.number');

テンプレートをクローンし、全てにsquare-templateがついてしまうのでidを取り除き、HTMLのgameBoxに追加しています。
そしてquerySelectorを使い追加した要素の中にあるclass="number"のdivを取得しています。
querySelectorとは、任意のHTMLの要素を取得できる関数です。
classならquerySelector('.class') idならquerySelector('#id')で取得することができます。

//後々編集するため目印をつける
number.setAttribute("data-index", i);
square.addEventListener('click', () => {
onClickSquare(i);
})

取得したnumberのdivにsetAttributeで目印を付け、
そしてaddEventListenerでクリック時の関数onClickSquare(i)を設定しています。

cssについてはflexで横並べにし、nth-childで段落を下げたりしています。

マス目に入れる数字を作る

数列作りは改善が難しかったです。
私は最初、縦と横と同じボックスにない数字からランダムに数字を決めて行き範囲を段々狭めるというやり方で作り、これだけに400行も使ってしまいました。
期限もあるので一旦完成させることを優先して他の機能を作った後に改善しました。
whileの存在を知って試行錯誤して1/4にすることができました。ここのおかげでループ文の使い方が少し上手になったと思います。

数列作り ソースコード

const number = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//引数で与えられた配列の順番を混ぜる
function lineCreate(line) {
    for (let i = (line.length - 1); 0 < i; i--) {
        let r = Math.floor(Math.random() * (i + 1));
        let tmp = line[i];
        line[i] = line[r];
        line[r] = tmp;
    }
    return line;
}

//引数1の配列の中に入っていない1〜9の数字を引数2の配列に入れる
function inBox(line, box) {
    for (let i = 1; i < 10; i++) {
        if (line.indexOf(i) === -1) {
            box.push(i)
        }
    }
};

function nanpureCreate() {
    //1行め
    firstNumber = lineCreate(number);
    let flag = true;
    while (flag) {
        //完成した数字が入る
        let createBoxNumber = [firstNumber, [], [], [], [], [], [], [], []];
        //範囲を絞り込み横用 縦一列が入る
        let tmpNumber0 = [[], [], [], [], [], [], [], [], []];
        //範囲を絞り込み縦用 横一列が入る
        let heightLine = [[], [], [], [], [], [], [], [], []];
        //範囲を絞り込み3 * 3 のボックス用
        let box33 = [[], [], [], [], [], [], [], [], []];
        //絞り込み縦用に入れる
        heightLine[0] = createBoxNumber[0]
        //範囲絞り込み横・ボックス用に入れる
        for (let i = 0; i < 9; i++) {
            tmpNumber0[i] = tmpNumber0[i].concat(createBoxNumber[0][i])
            box33[Math.floor(i / 3)] = box33[Math.floor(i / 3)].concat(createBoxNumber[0][i])
        }
        let bp;
        //2~9行目 72個を範囲を絞り込みながら一つずつを作成する
        for (let j = 1; j < 9; j++) {
            //行ごとの1〜9の順番を決める
            for (let i = 0; i < 9; i++) {
                let tmpA = [] //候補用
                let tmpBox;
                //入力位置把握用 縦と横の行の数字から
                let whileNumberJ = Math.floor(j / 3)
                let whileNumberI = Math.floor(i / 3)
                //jとiから把握した今回決める数字と同じボックスをtmpBoxに入れる
                tmpBox = box33[whileNumberJ * 3 + whileNumberI]
                //使われていない数字を探す
                inBox((tmpNumber0[i].concat(heightLine[j], tmpBox)), tmpA)
                let tmpB = lineCreate(tmpA);
                // 候補の中からランダムで決めていくと9個目が被ってしまう場合があるので
                if (isNaN(tmpB[0])) {
                    bp = 0;
                    break;
                }
                //縦・横・3×3用の配列に入れる
                heightLine[j] = heightLine[j].concat(tmpB[0])
                tmpNumber0[i] = tmpNumber0[i].concat(tmpB[0])
                box33[whileNumberJ * 3 + whileNumberI] = box33[whileNumberJ * 3 + whileNumberI].concat(tmpB[0])
                //9個目なら完成用の配列に入れる
                if (i === 8) {
                    createBoxNumber[j] = createBoxNumber[j].concat(heightLine[j])
                }
            }
            //やり直し
            if (bp === 0) {
                bp;
                break
            }
            if (j === 8) {
                //完成をしたものを返す
                return nanpureNumber = createBoxNumber;
            }
        };
    };
};
//引数で与えられた配列の順番を混ぜる
function lineCreate(line) {
    for (let i = (line.length - 1); 0 < i; i--) {
        let r = Math.floor(Math.random() * (i + 1));
        let tmp = line[i];
        line[i] = line[r];
        line[r] = tmp;
    }
    return line;
}
};

引数の配列をランダムに順番を入れ替える関数です。
参考 配列の要素の並びをシャッフルする

//引数1の配列の中に入っていない1〜9の数字を引数2の配列に入れる
function inBox(line, box) {
    for (let i = 1; i < 10; i++) {
        if (line.indexOf(i) === -1) {
            box.push(i)
    }
}

これはマスに入れる候補を探す関数です。
for文を使い、lineに入っていない1〜9までの数字をindexOfを使いboxにpushしています。

関数nanpureCreateの説明

while文を使い、完成するまで繰り返しています。

//1行め
firstNumber = lineCreate(number);

1番上の行を作っています。
この行は1回作れば大丈夫なのでwhile内に入れていません。

//完成した数字が入る
let createBoxNumber = [firstNumber, [], [], [], [], [], [], [], []];
//範囲の絞り込み横用 縦一列が入る
let tmpNumber0 = [[], [], [], [], [], [], [], [], []];
//範囲の絞り込み縦用 横一列が入る
let heightLine = [[], [], [], [], [], [], [], [], []];
//範囲の絞り込み3 * 3 のボックス用
let box33 = [[], [], [], [], [], [], [], [], []];

候補の数字を絞り込む用の配列達です。

//絞り込み縦用に入れる
heightLine[0] = createBoxNumber[0]
//範囲絞り込み横・ボックス用に入れる
for (let i = 0; i < 9; i++) {
    tmpNumber0[i] = tmpNumber0[i].concat(createBoxNumber[0][i])
    box33[Math.floor(i / 3)] = box33[Math.floor(i / 3)].concat(createBoxNumber[0][i])
}

1行目を絞り込む用の配列に入れています。

2〜9行目

for文を使い72回繰り返しています。

//2~9行目 72個を範囲を絞り込みながら一つずつを作成する
for (let j = 1; j < 9; j++) {
  //行ごとの1〜9の順番を決める
  for (let i = 0; i < 9; i++) {
      let tmpA = [] //候補用
      let tmpBox;
      //入力位置把握用 縦と横の行の数字から
      let whileNumberJ = Math.floor(j / 3)
      let whileNumberI = Math.floor(i / 3)
      //jとiから把握した今回決める数字と同じボックスをtmpBoxに入れる
      tmpBox = box33[whileNumberJ * 3 + whileNumberI]
      //使われていない数字を探す
      inBox((tmpNumber0[i].concat(heightLine[j], tmpBox)), tmpA)
      let tmpB = lineCreate(tmpA);
      // 候補の中からランダムで決めていくと9個目が被ってしまう場合があるので
      if (isNaN(tmpB[0])) {
          bp = 0;
          break;
      }
      //縦・横・3×3用の配列に入れる
      heightLine[j] = heightLine[j].concat(tmpB[0])
      tmpNumber0[i] = tmpNumber0[i].concat(tmpB[0])
      box33[whileNumberJ * 3 + whileNumberI] = box33[whileNumberJ * 3 + whileNumberI].concat(tmpB[0])
      //9個目なら完成用の配列に入れる
      if (i === 8) {
          createBoxNumber[j] = createBoxNumber[j].concat(heightLine[j])
      }
  }
  //9番目の数字が被ってしまったためやり直し
  if (bp === 0) {
      bp;
      break
  }
  if (j === 8) {
      //完成をしたものを返す
      return nanpureNumber = createBoxNumber;
  }
};
let tmpBox;
//入力位置把握用 縦と横の行の数字から
let whileNumberJ = Math.floor(j / 3)
let whileNumberI = Math.floor(i / 3)
//jとiから把握した今回決める数字と同じボックスをtmpBoxに入れる
tmpBox = box33[whileNumberJ * 3 + whileNumberI]

jは横の行でiが横の行です。
縦と横の行から今どこの場所の数字を決めているのかを把握して、入力する場所のボックスがBox33の何番目かを特定しています。
そしてそのボックスをtmpBoxに入れています。

let tmpA = [] //候補用
//使われていない数字を探す
inBox((tmpNumber0[i].concat(heightLine[j], tmpBox)), tmpA)
let tmpB = lineCreate(tmpA);

同じ縦の行と横の行に使われていない数字を探してtmpAに入れています。
tmpAが1から順に入っているので入れ替えてtmpBに入れています。
tmpBに複数入っている時があるのでtmpB[0]を使います。

// 候補の中からランダムで決めていくと9個目が被ってしまう場合があるので
if (isNaN(tmpB[0])) {
    bp = 0;
    break;
}

ランダムで作っているので9個目が被ってしまう場合になってしまう時があります。
breakなどでループを初めからやり直します。bp(breakPointの略です。)は複数whileやforを使っているのでbreakのif用です。
最初から全部をwhileをやり直ししているのですがfor文を使って作っている場所をwhileを使って途中からやり直しにしようとしました。ですが行き詰まってしまい改善できないまま期限がきてしまいました。。

//縦・横・3×3用の配列に入れる
heightLine[j] = heightLine[j].concat(tmpB[0])
tmpNumber0[i] = tmpNumber0[i].concat(tmpB[0])
box33[whileNumberJ * 3 + whileNumberI] = box33[whileNumberJ * 3 + whileNumberI].concat(tmpB[0])
//9個目なら完成用の配列に入れる
if (i === 8) {
    createBoxNumber[j] = createBoxNumber[j].concat(heightLine[j])
}

jとiを使い絞り込み用の配列に入れています。
そして行が完成してら完成用の配列に入れています。

if (j === 8) {
    //完成をしたものを返す
    return nanpureNumber = createBoxNumber;
}

最後に全て完成したらwhileを終わらせます。

問題作成

問題作成です。どう作るか悩みました。
そしてランダムに数字ごとに同じ個数消しすことにしました。この方法だと解がない問題が作成されてしまいます。
しっかりと解ける問題を作るには消しても解が一つしかない場所を消す必要があるのですがそのためには問題から解を導く解法プログラムが必要です。
私は作成することができず、魔法のボタンという正解の数字を入力できるものを作りました。後ほど説明します。

question(level) ソースコード

1〜9をlevelの個数空白にします。

function question(level) {
    //問題作成がしにくいため正解の配列の形を変える
    for (let i = 0; i < 81; i++) {
        let a = (Math.floor(i / 9));//商
        let b = (Math.floor(i % 9));//余り
        nanpureA.push(nanpureNumber[a][b]);
    }
    //消した数の確認用 1~9をlevelの個数ずつ空白にする
    let deletePt = [[], [], [], [], [], [], [], [], []]
    let flag = true
    while (flag) {
        pt = 0;
        a = Math.floor(Math.random() * (81));
        //空白ではない時
        if (nanpureA[a].length !== 0) {
            //上限以下か?
            if (deletePt[nanpureA[a] - 1].length !== level) {
                deletePt[nanpureA[a] - 1].push(0)
                nanpureA[a] = [];
            } else {
                continue;
            }
        }
        if (nanpureA[a].length === 0) {
            let pt = 0;
            for (let j = 0; j < 9; j++) {
                pt += deletePt[j].length;
            }
            if (pt === (level * 9)) {
                break;
            }
        }

    };
    return magicLimit = level, magicPoint = 0, colorIdNumber = 0;
}
//問題作成がしにくいため正解の配列の形を変える
for (let i = 0; i < 81; i++) {
    let a = (Math.floor(i / 9));//商
    let b = (Math.floor(i % 9));//余り
    nanpureA.push(nanpureNumber[a][b]);
}

問題作成をしやすい形に配列の形を変えています。

//空白ではない時
if (nanpureA[a].length !== 0) {
  //上限以下か?
  if (deletePt[nanpureA[a] - 1].length !== level) {
      deletePt[nanpureA[a] - 1].push(0)
      nanpureA[a] = [];
  } else {
      continue;
  }
}
deletePt[nanpureA[a] - 1]

nanpureAに入っているのは1〜9で配列のカウントは0からなので−1しています。

if (deletePt[nanpureA[a] - 1].length !== level) {
  } else {
      continue;
  }

その数字をすでにlevelの個数空白にしていたらやり直しします。

deletePt[nanpureA[a] - 1].push(0)

deletePtに1〜9が何個ずつ消したかを確認するために0をpushしています。

if (nanpureA[a].length === 0) {
  let pt = 0;
  for (let j = 0; j < 9; j++) {
      pt += deletePt[j].length;
  }
  if (pt === (level * 9)) {
      break;
  }
}

deletePt内にある個数を全て足した数がlevel×9の場合終了させます。

クリック時の関数 onClickSquare(i) ソースコード

魔法のボタンの部分だけ説明します。

const onClickSquare = (index) => {
    //入力するのが空白の場合
    if (isNaN(boxNumber)) {

        // 消しゴム用 クリック位置の正解のリストと答えのリストが同じではない場合 空白を入力
        if (answerNumber[index] !== nanpureA[index]) {
            document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
        }
        //入力するのが数字の場合
    } else {
        //入力する位置の答えのリストが空白の場合
        if (!isNaN(nanpureA[index])) {
            if (nanpureA[index] !== answerNumber[index]) {
                //魔法のボタン用
                if (boxNumber === 10) {
                    //使用回数以下なのか
                    if (magicLimit > magicPoint) {
                        document.querySelector(`[data-index='${index}']`).classList.add('DColor');
                        //再生時にclassを剥がすよう
                        RBlist.push(index);
                        document.querySelector(`[data-index='${index}']`).classList.remove('redColor');
                        document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${answerNumber[index]}</p>`;
                        //答えのリストに入れる
                        nanpureA[index] = answerNumber[index]

                        boxColor();
                        for (let i = 0; i < 9; i++) {
                            a = nanpureNumber[i].indexOf(answerNumber[index])
                            b = 9 * i + a
                            if (nanpureA[b].length !== 0) {
                                document.querySelector(`[data-index='${b}']`).setAttribute("data-state", 3)
                            }
                        }

                        //クリアか?
                        clear();
                        magicPoint++;
                        alert(`使用可能回数 残り${magicLimit - magicPoint}回`)
                        boxColor();
                        //使用上限の確認
                        if (magicLimit === magicPoint) {
                            boxNumber = [];
                            //前回に選択していたボタンの色もどし
                            colorBack();
                            document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons3');
                            document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('noneButton');
                            colorIdNumber = 1;
                            N(1);
                        }
                    }
                }
                //正解のリストと入力する数字が同じ場合
                if (answerNumber[index] === boxNumber) {
                    document.querySelector(`[data-index='${index}']`).classList.add('DColor');
                    RBlist.push(index);
                    document.querySelector(`[data-index='${index}']`).classList.remove('redColor');
                    document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
                    document.querySelector(`[data-index='${index}']`).setAttribute("data-state", 3)
                    //答えのリストに入れる
                    nanpureA[index] = boxNumber
                    //全部埋まっていて正解の場合に
                    clear();
                    //正解のリストと入力する数字が同じではない場合
                } else {
                    //答えのリストと正解のリストが同じではない場合
                    if (answerNumber[index] !== nanpureA[index]) {
                        //赤くする
                        document.querySelector(`[data-index='${index}']`).classList.add('redColor')
                        document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
                        RBlist.push(index);
                    }
                }
            }
        }
    }

};

魔法のボタン

一旦css操作系を少し消しています。

//魔法のボタン用
if (boxNumber === 10) {
  //使用回数以下なのか
  if (magicLimit > magicPoint) {
      document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${answerNumber[index]}</p>`;
      //答えのリストに入れる
      nanpureA[index] = answerNumber[index]
      magicPoint++;
      alert(`使用可能回数 残り${magicLimit - magicPoint}回`)
      //使用上限の確認
      if (magicLimit === magicPoint) {
          boxNumber = [];
          document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons3');
          document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('noneButton');
      }
  }
}
document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${answerNumber[index]}</p>`;

querySelectorで位置を取得して答えが入っている配列 answerNumberから入力しています。
indexはonClickSquare(i)のiの数字です。
magicLimitはquestion(level)のlevelと同じです。

nanpureA[index] = answerNumber[index]

nanpureAの配列でクリアか確認をするのでこの配列に答えを入れています。

//クリア確認
function clear() {
    let total = 0;
    for (let i = 0; i < 81; i++) {
        total += nanpureA[i];
        if ((i + 1) % 9 === 0) {
            if (total % 45 !== 0) {
                break
            }
        }
        if (total === 405) {
            alert('clear');
        }
    }
}

他の場所にある crear()関数です。

if (total === 405) {
    alert('clear');
}

ナンプレを解くと全ての数字の和が405になるので合計で確認しています。

//使用上限の確認
if (magicLimit === magicPoint) {
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons3');
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('noneButton');
}

そして使用上限になったらclassList.add・removeでボタンを消しています。

最後に

見難かったり、遠回り方法で実装しているところがあったと思いますが見てくださりありがとうございます。
今回はコンテストに応募するため作品のナンプレをJavaScriptで作ってみたことを記事にしました。記事を書いていて作ったの時期より知識が増えていて改善点が浮かんだり、書くために見直してみて無駄に書いている部分などの発見がありました。
今回記事を書いてみて改めて振り返りやアウトプットすることは大切と感じました。

全体のコード 紹介していない部分も含みます

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ナンプレ</title>
    <link rel="stylesheet" href="nanpure.css">
</head>

<body>
    <nav>
        <ul>
            <li onclick="reset(3)" class="btn">再生成 簡単</li>
            <li onclick="reset(5)" class="btn">再生成 難しい</li>
            <li onclick="alert(explain)" class="btn">ルール説明</li>
        </ul>
    </nav>
    <div class="wrapper">
        <h1>ナンプレ</h1>
        
        <!-- 9*9マスの場所-->
        <div id="gameBox" class="gameBox"></div>
        <div class="buttonArea" id="buttonArea">
            <table>
                <tr>
                    <td><input type="button" value="1" onclick="N(1)" id="buttonId1" class="buttons"></td>
                    <td><input type="button" value="2" onclick="N(2)" id="buttonId2" class="buttons"></td>
                    <td><input type="button" value="3" onclick="N(3)" id="buttonId3" class="buttons"></td>
                    <td><input type="button" value="4" onclick="N(4)" id="buttonId4" class="buttons"></td>
                    <td><input type="button" value="5" onclick="N(5)" id="buttonId5" class="buttons"></td>
                    <td><input type="button" value="6" onclick="N(6)" id="buttonId6" class="buttons"></td>
                    <td><input type="button" value="7" onclick="N(7)" id="buttonId7" class="buttons"></td>
                    <td><input type="button" value="8" onclick="N(8)" id="buttonId8" class="buttons"></td>
                    <td><input type="button" value="9" onclick="N(9)" id="buttonId9" class="buttons"></td>
                    <td><input type="button" value="" onclick="N0()" id="eraser" class="eraser"></td>
                </tr>
            </table>
        </div>
        <div class="buttonArea2">
            <table>
                <tr>
                    <td><input type="button" value="魔法のボタン" onclick="magicButton()" id="buttonId10" class="buttons3">
                    </td>
                </tr>
            </table>
        </div>
    </div>
    
    <!-- テンプレート用 -->
    <div id="square-template" class="square">
        <div class="number"></div>
    </div>
    <!-- ボタン系 -->
    <script src="./function/button.js"></script>
    <!-- 問題作成など -->
    <script src="./function/create.js"></script>
    <!-- 実行 -->
    <script src="./function/nanpureMain.js"></script>
</body>

</html>
button.js
//前回押したボタンの色もどし
function colorBack() {
    if (colorIdNumber === 0) {
        document.getElementById('eraser').classList.remove('eraser2');
        document.getElementById('eraser').classList.add('eraser');
    } else if (10 > colorIdNumber) {
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons2');
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('buttons');
    }
    if (colorIdNumber === 10) {
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons4');
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('buttons3');
    }
}
//消しゴム用
function N0() {
    boxNumber = []
    //前に押したボタンの色を変える
    colorBack();
    colorIdNumber = 0;
    document.getElementById('eraser').classList.remove('eraser');
    document.getElementById('eraser').classList.add('eraser2');
    boxColor()
}
//ボタン用
function N(Num) {
    boxNumber = Num;
    //数字の背景を戻す
    boxColor();
    //ボタンと同じ数字の背景の色を変える
    for (let i = 0; i < 9; i++) {
        a = nanpureNumber[i].indexOf(Num)
        b = 9 * i + a
        if (nanpureA[b].length !== 0) {
            document.querySelector(`[data-index='${b}']`).setAttribute("data-state", 3)
        }
    }
    //前回と今押したボタン色変えと保存
    colorBack();
    colorIdNumber = boxNumber
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons');
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('buttons2');
}
//魔法のボタン用
function magicButton() {
    if (magicLimit > magicPoint) {
        boxNumber = 10;
        colorBack();
        colorIdNumber = boxNumber
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons');
        document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('buttons2');
    }
    colorIdNumber = boxNumber;
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons3');
    document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('buttons4');
    boxColor();
}
//クリア確認
function clear() {
    let total = 0;
    for (let i = 0; i < 81; i++) {
        total += nanpureA[i];
        if ((i + 1) % 9 === 0) {
            if (total % 45 !== 0) {
                break
            }
        }
        if (total === 405) {
            alert('clear');
        }
    }
}
//クリック時に
const onClickSquare = (index) => {
    //入力するのが空白の場合
    if (isNaN(boxNumber)) {

        // 消しゴム用 クリック位置の正解のリストと答えのリストが同じではない場合 空白を入力
        if (answerNumber[index] !== nanpureA[index]) {
            document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
        }
        //入力するのが数字の場合
    } else {
        //入力する位置の答えのリストが空白の場合
        if (!isNaN(nanpureA[index])) {
            if (nanpureA[index] !== answerNumber[index]) {
                //魔法のボタン用
                if (boxNumber === 10) {
                    //使用回数以下なのか
                    if (magicLimit > magicPoint) {
                        document.querySelector(`[data-index='${index}']`).classList.add('DColor');
                        //再生時にclassを剥がすよう
                        RBlist.push(index);
                        document.querySelector(`[data-index='${index}']`).classList.remove('redColor');
                        document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${answerNumber[index]}</p>`;
                        //答えのリストに入れる
                        nanpureA[index] = answerNumber[index]

                        boxColor();
                        for (let i = 0; i < 9; i++) {
                            a = nanpureNumber[i].indexOf(answerNumber[index])
                            b = 9 * i + a
                            if (nanpureA[b].length !== 0) {
                                document.querySelector(`[data-index='${b}']`).setAttribute("data-state", 3)
                            }
                        }

                        //クリアか?
                        clear();
                        magicPoint++;
                        alert(`使用可能回数 残り${magicLimit - magicPoint}回`)
                        boxColor();
                        //使用上限の確認
                        if (magicLimit === magicPoint) {
                            boxNumber = [];
                            //前回に選択していたボタンの色もどし
                            colorBack();
                            document.getElementById('buttonId' + `${colorIdNumber}`).classList.remove('buttons3');
                            document.getElementById('buttonId' + `${colorIdNumber}`).classList.add('noneButton');
                            colorIdNumber = 1;
                            N(1);
                        }
                    }
                }
                //正解のリストと入力する数字が同じ場合
                if (answerNumber[index] === boxNumber) {
                    document.querySelector(`[data-index='${index}']`).classList.add('DColor');
                    RBlist.push(index);
                    document.querySelector(`[data-index='${index}']`).classList.remove('redColor');
                    document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
                    document.querySelector(`[data-index='${index}']`).setAttribute("data-state", 3)
                    //答えのリストに入れる
                    nanpureA[index] = boxNumber
                    //全部埋まっていて正解の場合に
                    clear();
                    //正解のリストと入力する数字が同じではない場合
                } else {
                    //答えのリストと正解のリストが同じではない場合
                    if (answerNumber[index] !== nanpureA[index]) {
                        //赤くする
                        document.querySelector(`[data-index='${index}']`).classList.add('redColor')
                        document.querySelector(`[data-index='${index}']`).innerHTML = `<p>${boxNumber}</p>`;
                        RBlist.push(index);
                    }
                }
            }
        }
    }

};
create.js
//入力する数字
let boxNumber = [];
//入力した位置を保存用
let RBlist = [];
//背景を初期の色に
function boxColor() {
    for (let i = 0; i < 81; i++) {
        let a = (Math.floor(i / 9));//商
        let b = (Math.floor(i % 9));//余り
        if (Math.floor(a / 3) !== 1) {
            if (Math.floor(b / 3) !== 1) {
                document.querySelector(`[data-index='${i}']`).setAttribute("data-state", 1)
            } else {
                document.querySelector(`[data-index='${i}']`).setAttribute("data-state", 2)
            }
        } else {
            if (Math.floor(b / 3) === 1) {
                document.querySelector(`[data-index='${i}']`).setAttribute("data-state", 1)
            } else {
                document.querySelector(`[data-index='${i}']`).setAttribute("data-state", 2)
            }
        }
    }
};
// 正解の配列を作成
function nanpureCreate() {
    const number = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    //引数で与えられた配列の順番を混ぜる
    function lineCreate(line) {
        for (let i = (line.length - 1); 0 < i; i--) {
            let r = Math.floor(Math.random() * (i + 1));
            let tmp = line[i];
            line[i] = line[r];
            line[r] = tmp;

        }
        return line;
    }
    //引数1の配列の中に入っていない1〜9の数字を引数2の配列に入れる
    function inBox(line, box) {
        for (let i = 1; i < 10; i++) {
            if (line.indexOf(i) === -1) {
                box.push(i)
            }
        }
    };
    firstNumber = lineCreate(number);
    let flag = true;
    while (flag) {
        //完成した数字が入る
        let createBoxNumber = [firstNumber, [], [], [], [], [], [], [], []];
        //範囲を絞り込み横用 縦一列が入る
        let tmpNumber0 = [[], [], [], [], [], [], [], [], []];
        //範囲を絞り込み縦用 横一列が入る
        let heightLine = [[], [], [], [], [], [], [], [], []];
        //範囲を絞り込み3 * 3 のボックス用
        let box33 = [[], [], [], [], [], [], [], [], []];
        //絞り込み縦用に入れる
        heightLine[0] = createBoxNumber[0]
        //範囲絞り込み横・ボックス用に入れる
        for (let i = 0; i < 9; i++) {
            tmpNumber0[i] = tmpNumber0[i].concat(createBoxNumber[0][i])
            box33[Math.floor(i / 3)] = box33[Math.floor(i / 3)].concat(createBoxNumber[0][i])
        }
        let bp;
        //2~9行目 72個を範囲を絞り込みながら一つずつを作成する
        for (let j = 1; j < 9; j++) {
            for (let i = 0; i < 9; i++) {
                let tmpA = [] //候補用
                let tmpBox;
                //入力位置把握用 縦と横の行の数字から
                let whileNumberJ = Math.floor(j / 3)
                let whileNumberI = Math.floor(i / 3)
                //jとiから把握した今回決める数字と同じボックスをtmpBoxに入れる
                tmpBox = box33[whileNumberJ * 3 + whileNumberI]
                //使われていない数字を探す
                inBox((tmpNumber0[i].concat(heightLine[j], tmpBox)), tmpA)
                let tmpB = lineCreate(tmpA);
                // 候補の中からランダムで決めていくと9個目が被ってしまう場合があるので
                if (isNaN(tmpB[0])) {
                    bp = 0;
                    break;
                }
                //縦・横・3×3用の配列に入れる
                heightLine[j] = heightLine[j].concat(tmpB[0])
                tmpNumber0[i] = tmpNumber0[i].concat(tmpB[0])
                box33[whileNumberJ * 3 + whileNumberI] = box33[whileNumberJ * 3 + whileNumberI].concat(tmpB[0])
                //9個目なら完成用の配列に入れる
                if (i === 8) {
                    createBoxNumber[j] = createBoxNumber[j].concat(heightLine[j])
                }
            }
            //やり直し
            if (bp === 0) {
                bp;
                break
            }
            if (j === 8) {
                //完成をしたものを返す
                return nanpureNumber = createBoxNumber;
            }
        };
    };
};
//正解のリストを作成 
function answerCreate() {
    let answera = [];

    for (let i = 0; i < 81; i++) {
        a = (Math.floor(i / 9));
        b = (Math.floor(i % 9));
        answera.push(nanpureNumber[a][b]);
    }
    return answerNumber = answera, nanpureA = []; //問題作成用
};
// 問題作成 level × 9個消す
function question(level) {
    //正解の配列の形を変える
    for (let i = 0; i < 81; i++) {
        let a = (Math.floor(i / 9));//商
        let b = (Math.floor(i % 9));//余り
        nanpureA.push(nanpureNumber[a][b]);
    }
    //消した数の確認用
    let deletePt = [[], [], [], [], [], [], [], [], []]
    let flag = true
    while (flag) {
        pt = 0;
        a = Math.floor(Math.random() * (81));
        //空白ではない時
        if (nanpureA[a].length !== 0) {
            //上限以下か?
            if (deletePt[nanpureA[a] - 1].length !== level) {
                deletePt[nanpureA[a] - 1].push(0)
                nanpureA[a] = [];
            } else {
                continue;
            }
        }
        if (nanpureA[a].length === 0) {
            let pt = 0;
            for (let j = 0; j < 9; j++) {
                pt += deletePt[j].length;
            }
            if (pt === (level * 9)) {
                break;
            }
        }

    };
    return magicLimit = level, magicPoint = 0, colorIdNumber = 0;
}
//ボックス作成
const createSquares = () => {
    const gameBox = document.getElementById("gameBox");
    const squareTemplate = document.getElementById("square-template");
    for (let i = 0; i < 81; i++) {
        const square = squareTemplate.cloneNode(true); //テンプレートから要素をクローン
        square.removeAttribute("id"); //テンプレート用のid属性を削除
        //HTMLのgameBoxに追加 マス目のHTML要素を追加
        gameBox.appendChild(square);

        const number = square.querySelector('.number');

        //後々編集するため目印をつける
        number.setAttribute("data-index", i);
        //問題を入力
        document.querySelector(`[data-index='${i}']`).innerHTML = `<p>${nanpureA[i]}</p>`;
        document.querySelector(`[data-index='${i}']`).classList.add('numberColor');

        //クリック時の設定
        square.addEventListener('click', () => {
            onClickSquare(i);
        })
    }
};
//再生成
function reset(level) {
    colorBack();
    //色を剥がす
    if (RBlist.length !== 0) {
        for (let i = 0; i < RBlist.length; i++) {
            document.querySelector(`[data-index='${RBlist[i]}']`).classList.remove('redColor', 'DColor')
        }
    }
    RBlist = [];
    boxColor();
    nanpureA = [];
    nanpureCreate();
    answerCreate();
    question(level);
    for (let i = 0; i < 81; i++) {
        document.querySelector(`[data-index='${i}']`).innerHTML = `<p>${[]}</p>`;
        document.querySelector(`[data-index='${i}']`).innerHTML = `<p>${nanpureA[i]}</p>`;
    }
    //魔法のボタン表示
    document.getElementById('buttonId10').classList.remove('noneButton');
    document.getElementById('buttonId10').classList.add('buttons3');
    N(1);
};
nanpureMain.js
explain = ("ルール       デフォルト難易度 簡単\nどの横の列にも1〜9が一つずつ入る\nどの縦の列にも1〜9一つずつが入る\nどの3×3ブロックにも1〜9一つずつが入る\n\n下にある数字をクリックして選択をして、空いているマスをクリックすると入力できます。難易度は上にある再生成から変更ができます\n魔法のボタンを選択してクリックすると正解の数字を入力できます。   使用回数は難易度によって変わります。")

nanpureCreate();
answerCreate();
question(3);
createSquares();
N(1);
window.onload = () => {
    alert(explain)
};
nanpure.css
*,*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  background-color: rgb(255, 255, 255);
}

.gameBox {
  display: flex;
  flex-wrap: wrap;
  margin: 0 auto;
  width: 454px;
  height:454px;
}

.square {
  position: relative;
  width: 50px;
  height: 50px;
  border: solid black;
  border-width: 0 4px 4px 0;
  cursor: pointer;
  ;
}
/*
枠線と段落下げ
*/
.square:nth-child(-n + 9) {
  border-width: 4px 4px 4px 0;
  height: 54px;
}

.square:nth-child(9n + 1) {
  border-width: 0 4px 4px 4px;
  width: 54px;
}

.square:first-child {
  border-width: 4px;
  width: 54px;
  height: 54px;
}

.number[data-state="1"] {
  display: flex;
  flex-wrap: wrap;
  line-height: 0px;
  text-align: center;
  width: 46px;
  height: 46px;
  justify-content: center;
  font-weight: bold;
  font-size: x-large;
  background-color: rgba(57, 186, 232, 1);
}

.number[data-state="2"] {
  display: flex;
  flex-wrap: wrap;
  line-height: 0px;
  text-align: center;
  width: 46px;
  height: 46px;
  justify-content: center;
  font-weight: bold;
  font-size: x-large;
  background-color: rgba(185, 237, 248, 1);
}

.number[data-state="3"] {
  display: flex;
  flex-wrap: wrap;
  line-height: 0px;
  text-align: center;
  width: 46px;
  height: 46px;
  justify-content: center;
  font-weight: bold;
  font-size: x-large;
  background-color: rgb(164, 212, 31);
  ;
}

.numberColor {
  color: rgb(0, 0, 0);
}

.DColor {
  color: rgb(17, 60, 160);
}

.redColor {
  color: rgb(133, 42, 42);
}

#square-template {
  display: none;
}

.buttonArea {
  display: flex;
  justify-content: center;
}

.buttons {
  display: block;
  width: 40px;
  height: 40px;
  font-size: x-large;
  font-weight: bold;
  background: rgba(185, 237, 248, 1);
}

.buttons2 {
  display: block;
  width: 40px;
  height: 40px;
  font-size: x-large;
  font-weight: bold;
  background: rgb(164, 212, 31);
}

.buttons3 {
  display: block;
  width: 160px;
  height: 40px;
  font-size: x-large;
  font-weight: bold;
  background: rgba(185, 237, 248, 1);
}

.buttons4 {
  display: block;
  width: 160px;
  height: 40px;
  font-size: x-large;
  font-weight: bold;
  background: rgb(164, 212, 31);
}

.wrapper {
  width: 100%;
  height: 100vh;
  display: flex;
  margin: auto;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: x-large;
}

.eraser {
  display: block;
  width: 40px;
  height: 40px;
  background: rgba(185, 237, 248, 1) url(./img/eraser.png) center / 40px auto;
}

.eraser2 {
  display: block;
  width: 40px;
  height: 40px;
  background: rgb(164, 212, 31) url(./img/eraser.png) center / 40px auto;
}

nav {
  width: 100%;
  height: 70px;
  background-color: dimgray;
  padding-top: 5px;
  box-sizing: border-box;
  position: fixed;
  top: 0;
  left: 0;
}

ul {
  display: flex;
}

li {
  list-style: none;
  display: block;
  text-decoration: none;
  color: white;
  margin-right: 35px;
  font-size: x-large;
}

li:hover {
  color: rgb(214, 237, 248);
}

.buttonArea2 {
  margin: center;
  margin-left: 276px;
}

.noneButton {
  display: none;
}
11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?