【プログラムを作ろう】 ナンプレ編 #1

こんにちは!
エルフィールドでエンジニアとして働いている、H.Tと申します。
今回は、数回にわたって実際にプログラムを作っていく過程を紹介していきます。
プログラミングを学ぶ際、いきなり完成したコードを見ても、その裏でどんな思考や試行錯誤があったのかは分かりづらいものです。
そこで、今回のシリーズでは、その「思考の過程」や「試行錯誤」をできるだけリアルにお伝えしたいと思っています。
単に完成品を目指すのではなく、途中でのつまづきや改善のアイデアも大切にしながら進めていきますので、開発の流れを追うことでプログラム作成における成長を感じていただけるはずです。
✨ 全体の構成について
シリーズは全6回にわたり、以下のような流れで進めていきます。
今回はその1回目として、まずは「枠組み作成」に焦点を当てます。
今後の回に向けて、しっかりとした土台を作っていきましょう!
1回目 → 枠組み作成
2回目 → パズル完成目標
3回目 → 方針変更
4回目 → ブロックに注目
5回目 → チェック&表示機能
6回目 → 最終確認と完成
■ 前置き
プログラミングには試行錯誤がつきものです。
完成したコードを見ても、「確かに動いているけれど、なぜそうしたのか?」という部分が見えづらいこともありますよね。
そこでこのシリーズでは、その試行錯誤のプロセスも赤裸々に紹介していきます。
実際の開発における思考や考え方を、少しでもお伝えできればと思っています。
■ テーマについて
今回作るのは、JavaScriptで実装する「ナンバープレイス」です。
この題材を選んだ理由は以下の通りです!
- 見た目の作成が比較的簡単
- ルールが広く知られており、説明が省略できる
- ロジックに一定の難しさがあり、試行錯誤を楽しむのに適している
※なお、JavaScriptやHTMLの文法については本記事では詳細に触れません。
また、完成品の配布が目的ではなく、「思考メモ風」の内容を重視しています。
そのため、コードの不備や動作についての保証はありませんので、ご了承ください🙏
■ ナンバープレイスのルール整理
ナンバープレイスでは、以下のルールに従ってマスに数字を配置していきます。
- 各行に1〜9の数字を重複なく1つずつ配置
- 各列にも1〜9の数字を重複なく1つずつ配置
- 3×3のブロック(太線で囲まれた部分)にも1〜9の数字を重複なく配置
この「3×3のエリア」を「ブロック」と呼び、数字を入力する81個の区画を「マス」と呼びます。
■ 初期の設計方針
最初に考えたのは、全81マスの数字データをどう保持するかという点です。
最初はJavaScriptの二次元配列を使おうかと思いましたが、今回はあえてHTMLのテーブルを使って表示と管理を兼ねる方法を選びました。
各マスには、行・列・ブロックの情報をtdタグのclass属性に付けることで、
後から簡単に検索できるようにしています。
「これが正解!」というわけではなく、今回はこの方法で進めます。
■ ブロックの識別方法
各マスがどのブロックに属しているかを、行番号・列番号から判断します。
下の画像が、今回のプログラムを表示したもので、b00、b01、・・・がブロック名になります。
ここまではちゃんと出来てますね。
■ コード全文
<!DOCTYPE html>
<html>
<head>
<title>Number Place</title>
<style>
#app TABLE{border-collapse: collapse;}
#app TD{width: 3rem; height: 3rem; text-align: center; border: solid 1px #CCC ;}
#app TD:nth-child(1), #app TD:nth-child(3n){border-left-color: #333;}
#app TD:nth-child(3n){border-right-color: #333;}
#app TR:nth-child(1) TD, #app TR:nth-child(3n) TD{border-top-color: #333;}
#app TR:nth-child(3n) TD{border-bottom-color: #333;}
</style>
</head>
<body>
<div id=”app”></div>
<script>
const list08 = [0,1,2,3,4,5,6,7,8]
function init(){
myTable = document.createElement(‘table’)
list08.forEach(i => {
tmpTR = document.createElement(‘tr’)
list08.forEach(j => {
tmpTD = document.createElement(‘td’)
tmpBlock = ‘b’ + Math.trunc(i/3) + Math.trunc(j/3)
tmpTD.setAttribute(‘class’, (‘r’ + i) + ‘ ‘ + (‘c’ + j) + ‘ ‘ + tmpBlock)
// ‘r’ + i は行番号、’c’ + j は列番号、tmpBlock はブロック番号
tmpTD.append(tmpBlock)
tmpTR.append(tmpTD)
})
myTable.append(tmpTR)
})
document.querySelector(‘#app’).append(myTable)
}
init()
</script>
</body>
</html>
■ プログラムのポイント解説
まず、以下のコードで 0~8 の9個の数字を配列として作成。
list08 = [0,1,2,3,4,5,6,7,8]
このリストをlist08.forEach(i => { でループし、行と列のループ処理をしています!
実は、もともとは [1,2,3,4,5,6,7,8,9] のように1始まりで考えていましたが、
結局は0から始める方がプログラム上、扱いやすいため変更しました。
プログラマ以外の方にとって、「番号が0から始まる」のは違和感があるかもしれません。
ですが、今回のように「連続する値をグループに分ける」ケースでは、
0から始まることでロジックがシンプルになるんです。
たとえば、ナンプレのブロック(3×3エリア)を考えた場合
- 0~2列目:ブロック0
- 3~5列目:ブロック1
- 6~8列目:ブロック2
このように、列(または行)の番号を 3で割った商を使えば、どのブロックに属するかが一発で分かります。
→ 0から始めれば「3つずつ」で綺麗に区切れます。
今回のプログラムでは、マスがどの行・列・ブロックに属しているかを判定するために、以下のようなコードを書いています。
tmpBlock = ‘b’ + Math.trunc(i/3) + Math.trunc(j/3)
tmpTD.setAttribute(‘class’, (‘r’ + i) + ‘ ‘ + (‘c’ + j) + ‘ ‘ + tmpBlock)
※ここで使っている Math.trunc() は、小数点以下を切り捨てて商だけを取り出す関数です。
このように、各マスに「行(r0〜r8)」「列(c0〜c8)」「ブロック(b00〜b22)」の情報をclass属性として埋め込んでおけば、後から検索しやすくなり、ロジック処理が格段に楽になります。
今日はここまでです!
次回は、このブロック情報を活用して、実際に数字を配置していきます。
ルールに沿って、すべてのマスが埋まった<完成状態>を目指してみましょう💪