「1ページプログラミング」シリーズ
プログラム初心者、初学者のための様々な解説は世の中に溢れていますが、意外と「実際につくってみた」とそのソースコードの公開は少なめで、非常に人気があります。
例えばWin32APIでWindowsでテトリス作ってみた、動画など、大好きです。
そこで、このブログでは様々なゲームや仕組みのプログラミングを1ページで行っていきます。
このシリーズの特徴
1ページプログラミングの特徴は一枚のファイルで、メインのプログラム、解説を全部含んでいる、ということです。javascript によるプログラムなら、html、css、jsを全部含んでいます。
必要なライブラリは出来るかぎりCDNから、その他必要な場合は全て同梱します。
javascript によるプログラムの場合、1ページを全部コピーするだけで動きます。
また、「冒頭から読み進めていくだけで処理と実装の流れを理解する」ことに焦点を当てています。
使い方
このコードをコピーして、自分のフォルダに保存します。(index.html等の名前で保存してください)
そしてそれをGoogle Chromeで開くだけです。
コードは解説も含んでおり、初学者のために書いているため、中級以上のプログラマが初学者を育成する場面でもご利用いただけます。
丸コピで面接等に出すのはやめましょう。免責事項として、そういった際の一切の責任を負いません、よろしくお願いします。
ゲームの仕様
このゲームは互いに陣地を取り合うマツバルゲームです。(○とか✖︎とかの表示ではなく、色です。) 縦横斜めどこかが揃うと終了になります。
コード本体
初版: 2021.2.11
表記修正: 2021.2.14
バグ修正: 2021.3.2
遊びやすく修正: 2021.3.31
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
<!DOCTYPE html> <html lang="ja" dir="ltr"> <head> <meta charset="utf-8"> <!-- まず 文字コードを宣言 --> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- viewport 宣言 --> <script src="https://code.jquery.com/jquery-3.3.1.js"></script> <!-- jqueryをCDNから読み込みます。 --> <title>まるばつゲーム</title> <style type="text/css"> /* CSSで可能な UI や 演出 は CSS でやってみましょう */ *{box-sizing: border-box;} .board{ display: flex; flex-wrap: wrap; width: 300px; height: 300px;} .board div{ background-color: white; transition: all .3s ease-in; cursor: pointer; width: 100px; height: 100px; border:1px solid gray;} .board div:hover{ background-color: rgba(3,85,23,0.3);} .board div.PL1{} .board div.PL1::after{ content: "○"; font-size: 80px; position: relative; top: -7px; left: 10px; color: blue;} .board div.PL2{} .board div.PL2::after{ content: "×"; font-size: 130px; position: relative; top: -60px; left: 7px; color: red;} </style> </head> <body> <!-- 盤面を作成します。 --> <div class="board"> <div number=0></div> <div number=1></div> <div number=2></div> <div number=3></div> <div number=4></div> <div number=5></div> <div number=6></div> <div number=7></div> <div number=8></div> </div> <script> // ▼▼▼▼ コード解説ここから! ▼▼▼▼ // まるばつゲームで必要なプログラム的な要素は // 盤面のUI // 盤面DATA // です。 // UIはjQueryで、DATAは配列にデータを格納します。 // // UI: // 一つ一つの盤面上のマス目をクリックした時、どこをクリックしたか読み取るために工夫してみましょう。 // 何番目のdivか、という計算で取得する等、他の方法もあります。 // 何らかの方法で、プログラムとUIを互いに操作可能にする方法が必要です。 // ( 今回はUIからプログラムが呼び出すだけで十分です。 ) // また配列として盤面状態を保存します。 // これは盤面状態の把握をプログラムがやりやすくするためです。 // // その際いくつかのわかりやすい表記方法を行うため、定数を宣言します。 // これは実体は数値ですが、表記を意味のある変数名にすることでわかりやすくする技法です。 // こういった方法はよく使われます。定数のため、グローバル宣言します。 NONE = 0 // 何も設定されていない状態をNONEとする(実体は0) PL1 = 1 // プレイヤー1の取得したマスをPL1とする(実体は1) PL2 = -1 // プレイヤー2の取得したマスをPL2とする(実体は-1) // では初期状態のボードを配列として作成します。 // 二次元配列ですね。 // こうすると、あるマスの位置は X(横), Y(縦)で表されます。 // この配列など、全体からグローバルにアクセスされる変数にU_の接頭辞を付けます。 // G_ でないのは好みです。 U_board = [ [NONE,NONE,NONE], [NONE,NONE,NONE], [NONE,NONE,NONE] ] // 盤面操作をさせたくないときのフラグです。 U_LOCK = false // 縦(y): 0, 横(x):0 の時 (一番左上) // U_board[y][x] // ですね。 // ( U_board[x][y] でないことに注意してください。) // 最後に、現在の手番を作成します。 // 初手はPL1 (プレイヤー1)なので、PL1を入れます。(実体は 1 ) NOW_PL = PL1 // 今回は理解のしやすさを優先して、$()の中に関数を書いていきます。 $(function(){ // HTMLが全てロードされてからでないとプログラム側からDOMを操作できません。$() の中にいれると、ロード後に実行されます。 $(".board div").click(function(e){ // マス目をクリックした時の挙動になります。 if( U_LOCK ){ return } // もしすでにロックされていたら、何もしないでここで終了させます。 // jQueryのclickの中で e の引数には様々な情報が入っています。 // クリックされた本体の情報は e.target ですので、これをもう一度$()に入れます。 // そして $().attr で attributes である number の数値を取得します。 let number = $(e.target).attr("number") // *1 // 0から8までの連番から、X,Yの値に変換します。 // まず、 Y は 横(X)の最大数で割り、小数点を切り捨てるために parseInt (強制的に整数にする) すると、変換できます。 Y = parseInt(number / 3) // 次に X は、 横(X)の最大数で割った時のあまり、になります。あまりを出す演算子( % )で一発です。 X = number % 3 // まるばつゲームでは、 空いていないところには置けません。 // なので、置こうとしている 場所 ( X,Y )が空いているか調べます。(NONE かどうか) // もし空いていない場合、アラートを出してすぐ終わらせます。 if( U_board[Y][X] != NONE ){ // ここは空いていない // アラートして何もせず終わらせます。 alert("あいてません") return // 関数の内部で return すると、するにその関数が終わり、下に処理が移動しません。 } // 上の番兵を通り過ぎているので、ここに処理が来る場合は、絶対に ( X,Y ) はNONEではないです。 // 空いているので、ここを 現在のプレイヤーのものにします。 // ついでに色をいれるため、クラスも入れておきます。 U_board[Y][X] = NOW_PL $(e.target).addClass(((NOW_PL==PL1)? "PL1":"PL2")) // 三頂演算子でクラス名に変換します。 // 次のプレイヤーに渡す前に、ここでこのプレイヤーの勝利判定が必要です。 // マス目に色がおかれる度に、勝利判定を行います。 // 様々な判定方法があるのですが、一番シンプルで確実な方法は、 // 「あるマスから縦横斜め前列にたいして二回つながっているかどうかを判定する」 // であるかと思います。 // 「渡されたマスから縦横斜め全列にたいして二回つながっているかどうか判定」する関数を作成します。 // 関数定義は、本来、 $() の外側で作成することが望ましいです。読みやすい流れにするために、 今回はここに書きます。 // まず、ある点 (x,y) について判定する関数を作成します。 // この関数は "N", "PL1", "PL2" を返します。 getVal = function(pos){ let x = pos[0] let y = pos[1] // typeof a == "undefined" は a が変数として設定されているかどうか調べるjsの慣用句です。 if( typeof U_board[y] == "undefined" || typeof U_board[y][x] == "undefined" ){ // その配列の場所に値が設定されているかどうかを調べるやりかたです。 // undefined の場合、値が設定されていない( NONE でもない )ので、範囲外です。 return "N" }else if( U_board[y][x] == PL1 ){ // PL1のとき、PL1という文字列を返します return "PL1" }else if( U_board[y][x] == PL2 ){ // PL2のとき、PL2という文字列を返します。 return "PL2" }else{ // それ以外の時、Nを返します。 return "N" } } // 次に三つの(x,y)が揃っているかどうか判定する関数を作成します。 // pos1,pos2,pos3 を getValで m1,m2,m3 に変換します。 // getVal の 戻り値は、N,PL1,PL2 です。 // よって、 ここからハイフン区切りの文字列を作成すると、 // PL1-PL1-PL1 か // PL2-PL2-PL2 になったとき、これは揃っていると言えます。 isCompleteLine = function(pos1,pos2,pos3){ let m1 = getVal(pos1) let m2 = getVal(pos2) let m3 = getVal(pos3) let line = [m1,m2,m3].join("-") // [].join("")は配列を区切り文字で合成します。 if( line == "PL1-PL1-PL1" ){ return PL1 }else if( line == "PL2-PL2-PL2" ){ return PL2 }else{ return NONE } } // 上の二つの関数を使って、「あるマスから全方向に判定する関数」を作成します。 // この関数はマス達を返しますので、 get** という関数名になっています。 // 完成ラインがあるかどうかだけ判定する場合は has** // 渡されたものが完成ラインかどうか判定する場合は is** などがいいです。 getCompleteLine = function(x,y){ // まず初期値を決めます。 let ret = NONE // ret => return value (戻り値) として名前付けてます。これはひとそれぞれです。 // ret = ret || isCompleteLine(....) という書き方は、演算子の短絡評価(最小評価)といいます。 // ret が falseの時、isCompleteLineが実行されます。 // ひとつでも true になると、以降が実行されません。 ret = ret || isCompleteLine([x,y],[x,y+1],[x,y+2]) // 下 ret = ret || isCompleteLine([x,y],[x,y-1],[x,y-2]) // 上 ret = ret || isCompleteLine([x,y],[x-1,y],[x-2,y]) // 左 ret = ret || isCompleteLine([x,y],[x+1,y],[x+2,y]) // 右 ret = ret || isCompleteLine([x,y],[x+1,y+1],[x+2,y+2]) // 下右 ret = ret || isCompleteLine([x,y],[x-1,y+1],[x-2,y+2]) // 下左 ret = ret || isCompleteLine([x,y],[x+1,y-1],[x+2,y-2]) // 上右 ret = ret || isCompleteLine([x,y],[x-1,y-1],[x-2,y-2]) // 上左 // 自分を真ん中とする ret = ret || isCompleteLine([x,y],[x-1,y],[x+1,y]) // 横方向 ret = ret || isCompleteLine([x,y],[x,y-1],[x,y+1]) // 縦方向 ret = ret || isCompleteLine([x,y],[x-1,y+1],[x+1,y-1]) // 斜め方向1 ret = ret || isCompleteLine([x,y],[x-1,y-1],[x+1,y+1]) // 斜め方向2 return ret } // 盤面の全てにたいしてこのメソッドを使ってもいいのですが、この場合は「今うったところ」を起点して判定すればいいはずです。 let result = getCompleteLine(X,Y) // もしresultがNONEでないならば、どちらかが勝利しています。 // setTimeoutによって結果までの間を少し開けることで、演出が完了してから結果が出ます。 if( result == PL1 ){ setTimeout(function(){ alert("プレイヤー1の勝利") },50) U_LOCK = true }else if(result == PL2){ setTimeout(function(){ alert("プレイヤー2の勝利") },50) U_LOCK = true } // 次のプレイヤーのために、現在のプレイヤーを反転します。 // 1と-1は -1 を掛け算することで互いに反転可能な便利な数値のため、よく使われます。 // 現在の値がなんであろうと、この一行で反転できます。 NOW_PL = NOW_PL * -1 }) }) </script> </body> </html> |