日曜日, 5月 4, 2025
ホームニューステックニュースTypeScriptとCanvas APIでテトリス風パズルゲームを作ってみよう(初心者向けステップ解説) #JavaScript - Qiita

TypeScriptとCanvas APIでテトリス風パズルゲームを作ってみよう(初心者向けステップ解説) #JavaScript – Qiita



TypeScriptとCanvas APIでテトリス風パズルゲームを作ってみよう(初心者向けステップ解説) #JavaScript - Qiita

概要

今回は、ブラウザ上で動作するテトリス風落ち物パズルゲームを、TypeScriptとHTMLのCanvas APIを用いてゼロから実装する工程を、細かなステップに分けて解説していきます。ソースコードを参考に、どのようにゲームが作られていくのか、その過程を一緒に見ていきましょう。

本記事は、「ゲーム開発に興味があるけれど、何から始めれば良いか分からない」「TypeScriptやCanvas APIに触れてみたい」といった初心者の方を対象としています。基本的な描画から始まり、ブロックの操作、落下、当たり判定、ライン消去、スコア加算といったゲームの主要な要素を順を追って実装していきます。

完成版のソースコードはこちらにおいておきます。

実装イメージ

実装は簡易的に、18×9のマスを作り、ブロックが積み上がるゲームエリアとします。
1ブロックのサイズは30pxとして、ゲームエリアのCanvasのサイズは、縦方向は540px(18行x30px)、横方向は270px(9列x30px)とします。
game_area.jpeg

ブロックの種類は下記の8種類を使います。便宜上、それぞれのブロックの形にアルファベットを振っています。
block.jpg

実装

事前準備:HTMLとCSSでゲーム画面の土台を作る

ゲームのコードを書き始める前に、ゲームを表示するためのHTMLファイルと、見た目を整えるためのCSSファイルを用意します。

index.htmlファイルでは、ゲームの描画領域となる要素を2つ(メイン画面用とNextブロック表示用)、そしてスコアを表示するための要素を配置します。scriptタグで、これから作成するmain.tsファイルを読み込むように設定しておきます。



 lang="en">
  
     charset="UTF-8" />
     name="viewport" content="width=device-width, initial-scale=1.0" />
    </span>Block Puzzle Game<span class="nt"/>
    <span class="nt"><link/> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"src/style.css"</span> <span class="nt">/></span>
  <span class="nt"/>
  <span class="nt"/>
    <span class="nt"><div> <span class="na">class=</span><span class="s">"game-container"</span><span class="nt">></span>
      <span class="nt"><canvas> <span class="na">id=</span><span class="s">"gameCanvas"</span> <span class="na">width=</span><span class="s">"270"</span> <span class="na">height=</span><span class="s">"540"</span><span class="nt">></span></canvas></span>
      <span class="nt"><div> <span class="na">class=</span><span class="s">"side-panel"</span><span class="nt">></span>
        <span class="nt"><p> <span class="na">id=</span><span class="s">"scoreBoard"</span><span class="nt">></span>Score: 00000<span class="nt"/></p></span>
        <span class="nt"><canvas> <span class="na">id=</span><span class="s">"nextBlockCanvas"</span> <span class="na">width=</span><span class="s">"150"</span> <span class="na">height=</span><span class="s">"150"</span><span class="nt">></span></canvas></span>
      <span class="nt"/></div></span>
    <span class="nt"/></div></span>
    <span class="nt"><script><![CDATA[<span class="na">type=]]></script></span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"/src/main.ts"</span><span class="nt">></span>
  <span class="nt"/>
<span class="nt"/>
</span></span></span></span></span></code></pre>
</div>
</div>
<p data-sourcepos="52:1-52:249"><code>style.css</code>ファイルでは、これらのHTML要素に対して、画面中央に配置したり、Canvas要素に枠線をつけたり、スコア表示のフォントを設定したりといった基本的なスタイリングを行います。</p>
<div class="code-frame" data-lang="css" data-sourcepos="54:1-100:3">
<div class="highlight">
<pre><code><span class="c">/* style.css */</span>
<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="c">/* 子要素(.game-container)をフレックスアイテムに */</span>
  <span class="nl">justify-content</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="c">/* 子要素を水平方向の中央に配置 */</span>
  <span class="c">/* ... その他のスタイル */</span>
<span class="p">}</span>
<span class="c">/* ゲームエリアのスタイル */</span>
<span class="nc">.game-container</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="c">/* 子要素(canvasとside-panel)をフレックスアイテムに */</span>
  <span class="nl">justify-content</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="c">/* 子要素を水平方向の中央に配置 */</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="n">flex-start</span><span class="p">;</span> <span class="c">/* 子要素を上揃えに */</span>
  <span class="py">gap</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span> <span class="c">/* 子要素間の隙間 */</span>
<span class="p">}</span>
<span class="c">/* ブロックを移動できるエリアのスタイル */</span>
<span class="nf">#gameCanvas</span> <span class="p">{</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#000</span><span class="p">;</span> <span class="c">/* 枠線 */</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#eee</span><span class="p">;</span> <span class="c">/* 背景色 */</span>
<span class="p">}</span>
<span class="c">/* サイドパネル(ネクストブロックやスコアボード)のスタイル */</span>
<span class="nc">.side-panel</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span> <span class="c">/* 子要素をフレックスアイテムに */</span>
  <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span> <span class="c">/* 子要素を縦方向に並べる */</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="c">/* 子要素を水平方向(主軸と垂直な方向)の中央に揃える */</span>
<span class="p">}</span>
<span class="c">/* 次に落ちてくるブロックを表示するキャンバスのスタイル */</span>
<span class="nf">#nextBlockCanvas</span> <span class="p">{</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#000</span><span class="p">;</span> <span class="c">/* 1ピクセルの実線で黒い枠線 */</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="m">#ddd</span><span class="p">;</span> <span class="c">/* 薄い灰色の背景色 */</span>
  <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="c">/* 下に10ピクセルの余白 */</span>
<span class="p">}</span>
<span class="c">/* スコア表示要素のスタイル */</span>
<span class="nf">#scoreBoard</span> <span class="p">{</span>
  <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">24px</span><span class="p">;</span> <span class="c">/* 下に24ピクセルの余白 */</span>
  <span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span> <span class="c">/* テキストを中央揃え */</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="m">24px</span><span class="p">;</span> <span class="c">/* フォントサイズを24ピクセルに */</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span> <span class="c">/* フォントを太字に */</span>
  <span class="nl">font-family</span><span class="p">:</span> <span class="s1">"Courier New"</span><span class="p">,</span> <span class="nb">monospace</span><span class="p">;</span> <span class="c">/* フォントファミリーを指定(等幅フォント) */</span>
  <span class="nl">background-color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span> <span class="c">/* 白い背景色 */</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span> <span class="c">/* 内側に10ピクセルの余白 */</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">#000</span> <span class="m">1px</span> <span class="nb">solid</span><span class="p">;</span> <span class="c">/* 1ピクセルの実線で黒い枠線 */</span>
  <span class="nl">min-width</span><span class="p">:</span> <span class="m">180px</span><span class="p">;</span> <span class="c">/* 最小幅を180ピクセルに */</span>
  <span class="nl">max-width</span><span class="p">:</span> <span class="m">180px</span><span class="p">;</span> <span class="c">/* 最大幅を180ピクセルに(結果的に幅が180pxに固定される) */</span>
  <span class="nl">white-space</span><span class="p">:</span> <span class="nb">nowrap</span><span class="p">;</span> <span class="c">/* テキストを改行しない */</span>
  <span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span> <span class="c">/* 要素からはみ出したコンテンツを非表示 */</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="102:1-102:148">これで、ゲーム画面を表示するための土台が完成しました。次に、いよいよTypeScriptのコードを書き始めます。</p>
<h3 data-sourcepos="104:1-104:45">
<span id="step-1-ゲームキャンバスの描画" class="fragment"/><a href="#step-1-%E3%82%B2%E3%83%BC%E3%83%A0%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%90%E3%82%B9%E3%81%AE%E6%8F%8F%E7%94%BB"><i class="fa fa-link"/></a>Step 1: ゲームキャンバスの描画</h3>
<p data-sourcepos="106:1-106:105">まずは、ゲームのメイン画面となる<code>gameCanvas</code>に描画するための準備をします。</p>
<p data-sourcepos="108:1-108:247"><code>main.ts</code>ファイルの冒頭で、HTMLで用意した<code><canvas/></code>要素を取得し、その要素から2D描画のためのコンテキストを取得します。このコンテキストを使って、様々な図形を描画していきます。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="110:1-114:3">
<div class="highlight">
<pre><code><span class="c1">// main.ts</span>
<span class="kd">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">gameCanvas</span><span class="dl">"</span><span class="p">)</span> <span class="kd">as </span><span class="nx">HTMLCanvasElement</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">)</span><span class="o">!</span><span class="p">;</span> <span class="c1">// 2D描画コンテキストを取得</span>
</code></pre>
</div>
</div>
<p data-sourcepos="116:1-116:133">また、ゲームボードのサイズやブロック1マスあたりのサイズなどを定数として定義しておきます。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="118:1-122:3">
<div class="highlight">
<pre><code><span class="kd">const</span> <span class="nx">grid</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span> <span class="c1">// 1マスあたりのピクセルサイズ</span>
<span class="kd">const</span> <span class="nx">rows</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span> <span class="c1">// ボードの縦マス数</span>
<span class="kd">const</span> <span class="nx">cols</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>  <span class="c1">// ボードの横マス数</span>
</code></pre>
</div>
</div>
<p data-sourcepos="124:1-124:252">このゲームでは、ゲームボード自体やブロックは小さな正方形の集まりで表現します。そこで、指定された座標とサイズ、色で正方形を一つ描画する基本的な関数<code>drawSquare</code>を作成します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="126:1-141:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">drawSquare</span><span class="p">(</span>
  <span class="nx">x</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
  <span class="nx">y</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
  <span class="nx">color</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">ctx</span><span class="p">:</span> <span class="nx">CanvasRenderingContext2D</span><span class="p">,</span>
  <span class="nx">size</span><span class="p">:</span> <span class="kr">number</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">color</span><span class="p">;</span> <span class="c1">// 塗りつぶしの色を指定</span>
  <span class="c1">// 指定したマス目の位置 (x * size, y * size) から、サイズ (size, size) の四角形を塗りつぶす</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="nx">x</span> <span class="o">*</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">y</span> <span class="o">*</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">size</span><span class="p">);</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">strokeStyle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">white</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// 線の色を指定</span>
  <span class="c1">// 指定したマス目の位置から、サイズ (size, size) の四角形の境界線を描画する</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nf">strokeRect</span><span class="p">(</span><span class="nx">x</span> <span class="o">*</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">y</span> <span class="o">*</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">size</span><span class="p">,</span> <span class="nx">size</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="143:1-143:138"><code>drawSquare</code>関数は、これからブロックやボードを描画する際に何度も利用する基本的な部品となります。</p>
<h3 data-sourcepos="145:1-145:42">
<span id="step-2-ブロックの定義と描画" class="fragment"/><a href="#step-2-%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%A8%E6%8F%8F%E7%94%BB"><i class="fa fa-link"/></a>Step 2: ブロックの定義と描画</h3>
<p data-sourcepos="147:1-147:117">落ちてくるブロックの形状や色を定義し、それを<code>gameCanvas</code>に描画できるようにします。</p>
<p data-sourcepos="149:1-149:57">まず、ブロックの色と形状を定義します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="151:1-187:3">
<div class="highlight">
<pre><code><span class="kd">const</span> <span class="nx">colors</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">red</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">blue</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">purple</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">teal</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">green</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">orange</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">brown</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">gold</span><span class="dl">"</span><span class="p">];</span> <span class="c1">// ブロックの色</span>

<span class="kd">const</span> <span class="nx">shapes</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
  <span class="p">],</span> <span class="c1">// I型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// J型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// L型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// O型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// S型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// T型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// Z型</span>
  <span class="p">[</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
    <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="p">],</span> <span class="c1">// U型</span>
<span class="p">];</span>
</code></pre>
</div>
</div>
<p data-sourcepos="189:1-189:171"><code>shapes</code>配列は、それぞれのブロックの形状を2次元配列で表現しています。<code>1</code>がある場所がブロックの存在するセルを表します。</p>
<p data-sourcepos="191:1-191:165">次に、ゲーム中に操作する「落ちてくるブロック」の状態を管理するための型(インターフェース)と変数を用意します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="193:1-203:3">
<div class="highlight">
<pre><code><span class="kr">interface</span> <span class="nx">Piece</span> <span class="p">{</span>
  <span class="nl">shape</span><span class="p">:</span> <span class="kr">number</span><span class="p">[][];</span> <span class="c1">// 形状 (shapesの要素)</span>
  <span class="nl">color</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="c1">// 色 (colorsの要素)</span>
  <span class="nl">x</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="c1">// ゲームボード上のX座標 (列)</span>
  <span class="nl">y</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="c1">// ゲームボード上のY座標 (行)</span>
<span class="p">}</span>

<span class="kd">let</span> <span class="nx">currentPiece</span><span class="p">:</span> <span class="nx">Piece</span><span class="p">;</span> <span class="c1">// 現在操作中のブロック</span>
<span class="c1">// let nextPiece: Piece; // 次のブロック (Step 7で追加)</span>
</code></pre>
</div>
</div>
<p data-sourcepos="205:1-205:188"><code>currentPiece</code>は、現在プレイヤーが操作しているブロックの形状、色、そしてゲームボード上の現在位置(左上のセルの座標)を保持します。</p>
<p data-sourcepos="207:1-207:132">これらの定義を元に、特定の<code>Piece</code>オブジェクトを<code>gameCanvas</code>に描画する関数<code>drawPiece</code>を作成します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="209:1-222:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">drawPiece</span><span class="p">(</span><span class="nx">piece</span><span class="p">:</span> <span class="nx">Piece</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">:</span> <span class="nx">CanvasRenderingContext2D</span><span class="p">,</span> <span class="nx">size</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// pieceオブジェクトのshape配列をループ処理</span>
  <span class="nx">piece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">row</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
    <span class="nx">row</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">value</span><span class="p">,</span> <span class="nx">x</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// valueが1 (ブロックが存在するセル) なら描画</span>
        <span class="c1">// pieceの現在位置(piece.x, piece.y)に、shape内の相対位置(x, y)を加算して</span>
        <span class="c1">// ゲームボード上の絶対位置を計算し、drawSquareで描画</span>
        <span class="nf">drawSquare</span><span class="p">(</span><span class="nx">piece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">piece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">piece</span><span class="p">.</span><span class="nx">color</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">,</span> <span class="nx">size</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="224:1-224:393">この関数は、<code>Piece</code>オブジェクトの形状(<code>shape</code>)を調べ、<code>1</code>になっているセルに対応する位置に、<code>piece.color</code>を使って<code>drawSquare</code>でブロックを描画します。描画位置は、<code>piece.x</code>と<code>piece.y</code>(ブロック全体の基準位置)に、形状内の相対位置(<code>x</code>と<code>y</code>)を加えたゲームボード上の絶対位置になります。</p>
<p data-sourcepos="226:1-226:230">最後に、ゲームボード自体の描画関数<code>drawBoard</code>も作成します。これは、Step 6以降で固定されたブロックを描画するために使用しますが、この時点ではまだボードは空です。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="228:1-242:3">
<div class="highlight">
<pre><code><span class="kd">let</span> <span class="nx">board</span><span class="p">:</span> <span class="kr">number</span><span class="p">[][]</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nx">rows</span> <span class="p">},</span> <span class="p">()</span> <span class="o">=></span> <span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span> <span class="c1">// ゲームボードの状態を表す2次元配列 (最初は全て空きマス0)</span>

<span class="kd">function</span> <span class="nf">drawBoard</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">context</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span> <span class="c1">// Canvas全体をクリア</span>
  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">row</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">row</span> <span class="o"> <span class="nx">rows</span><span class="p">;</span> <span class="nx">row</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ボードの各行をループ</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">col</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">col</span> <span class="o"> <span class="nx">cols</span><span class="p">;</span> <span class="nx">col</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ボードの各列をループ</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">board</span><span class="p">[</span><span class="nx">row</span><span class="p">][</span><span class="nx">col</span><span class="p">])</span> <span class="p">{</span> <span class="c1">// そのセルにブロックが存在する場合 (値が0以外)</span>
        <span class="c1">// boardの値 (色のインデックス+1) を使って色を取得し、drawSquareで描画</span>
        <span class="nf">drawSquare</span><span class="p">(</span><span class="nx">col</span><span class="p">,</span> <span class="nx">row</span><span class="p">,</span> <span class="nx">colors</span><span class="p">[</span><span class="nx">board</span><span class="p">[</span><span class="nx">row</span><span class="p">][</span><span class="nx">col</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span><span class="p">],</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">grid</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</span></span></code></pre>
</div>
</div>
<p data-sourcepos="244:1-244:297"><code>drawBoard</code>関数は、<code>board</code>配列を走査し、0以外の値が入っているセル(固定されたブロックがある場所)に色付きのブロックを描画します。値が1以上なのは、<code>colors</code>配列のインデックスに+1したものを格納しているためです。</p>
<h3 data-sourcepos="246:1-246:42">
<span id="step-3-キーボード操作で移動" class="fragment"/><a href="#step-3-%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E6%93%8D%E4%BD%9C%E3%81%A7%E7%A7%BB%E5%8B%95"><i class="fa fa-link"/></a>Step 3: キーボード操作で移動</h3>
<p data-sourcepos="248:1-248:222">プレイヤーが矢印キーを使ってブロックを左右に移動させたり、回転させたりできるように、キーボードイベントを捕捉して対応する処理を呼び出すようにします。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="250:1-274:3">
<div class="highlight">
<pre><code><span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">keydown</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">ArrowLeft</span><span class="dl">"</span><span class="p">)</span> <span class="nf">movePiece</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// 左矢印キーでmovePiece(-1)を呼び出し</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">ArrowRight</span><span class="dl">"</span><span class="p">)</span> <span class="nf">movePiece</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// 右矢印キーでmovePiece(1)を呼び出し</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">ArrowUp</span><span class="dl">"</span><span class="p">)</span> <span class="nf">rotatePiece</span><span class="p">();</span> <span class="c1">// 上矢印キーでrotatePiece()を呼び出し</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">ArrowDown</span><span class="dl">"</span><span class="p">)</span> <span class="nf">hardDrop</span><span class="p">();</span> <span class="c1">// 下矢印キーでhardDrop()を呼び出し (Step 4/5で実装)</span>
<span class="p">});</span>

<span class="kd">function</span> <span class="nf">movePiece</span><span class="p">(</span><span class="nx">dx</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+=</span> <span class="nx">dx</span><span class="p">;</span> <span class="c1">// ブロックのX座標をdxだけ増減</span>
  <span class="c1">// ※ 注意: この時点ではまだ壁や他のブロックとの衝突判定は考慮していません。</span>
  <span class="c1">//   次のステップ以降で衝突判定を実装し、移動可能かチェックします。</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">rotatePiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// 形状を表す2次元配列を90度回転させるロジック</span>
  <span class="kd">const</span> <span class="nx">rotatedShape</span> <span class="o">=</span> <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">map</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=></span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">row</span><span class="p">)</span> <span class="o">=></span> <span class="nx">row</span><span class="p">[</span><span class="nx">index</span><span class="p">]).</span><span class="nf">reverse</span><span class="p">()</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">backup</span> <span class="o">=</span> <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">;</span> <span class="c1">// 回転前の形状をバックアップ</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span> <span class="o">=</span> <span class="nx">rotatedShape</span><span class="p">;</span> <span class="c1">// 回転後の形状をセット</span>
  <span class="c1">// ※ 注意: この時点ではまだ回転後の衝突判定は考慮していません。</span>
  <span class="c1">//   次のステップ以降で衝突判定を実装し、回転可能かチェックします。</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="276:1-276:290"><code>movePiece(dx)</code>関数は、<code>currentPiece</code>のX座標を引数<code>dx</code>の値(左移動なら-1、右移動なら1)だけ変更します。<code>rotatePiece()</code>関数は、現在のブロックの形状を90度回転させた新しい形状を計算し、<code>currentPiece.shape</code>にセットします。</p>
<p data-sourcepos="278:1-278:264">この時点では、まだ壁や他のブロックとの衝突判定は行っていません。そのため、ブロックが壁を突き抜けたり、他のブロックに重なったりしてしまいますが、これは次のステップで解決します。</p>
<h3 data-sourcepos="280:1-280:75">
<span id="step-4-時間が立つとブロックが落ちていく処理の実装" class="fragment"/><a href="#step-4-%E6%99%82%E9%96%93%E3%81%8C%E7%AB%8B%E3%81%A4%E3%81%A8%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%8C%E8%90%BD%E3%81%A1%E3%81%A6%E3%81%84%E3%81%8F%E5%87%A6%E7%90%86%E3%81%AE%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 4: 時間が立つとブロックが落ちていく処理の実装</h3>
<p data-sourcepos="282:1-282:126">落ち物ゲームの主要な要素の一つである、時間経過によるブロックの自動落下を実装します。</p>
<p data-sourcepos="284:1-284:158">ゲームの描画や状態更新を一定間隔で行うために、ブラウザの<code>requestAnimationFrame</code>を使ったゲームループを構築します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="286:1-317:3">
<div class="highlight">
<pre><code><span class="kd">let</span> <span class="nx">dropStart</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nf">now</span><span class="p">();</span> <span class="c1">// 前回の落下処理または描画更新が行われた時刻</span>
<span class="kd">let</span> <span class="nx">dropInterval</span> <span class="o">=</span> <span class="mi">500</span><span class="p">;</span> <span class="c1">// ブロックが1マス落下するまでの時間(ミリ秒)</span>

<span class="kd">function</span> <span class="nf">update</span><span class="p">(</span><span class="nx">timestamp</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// timestampはrequestAnimationFrameが提供する現在の時刻</span>

  <span class="c1">// 前回の処理からの経過時間を計算</span>
  <span class="kd">const</span> <span class="nx">deltaTime</span> <span class="o">=</span> <span class="nx">timestamp</span> <span class="o">-</span> <span class="nx">dropStart</span><span class="p">;</span>

  <span class="c1">// 経過時間が落下間隔を超えていたらブロックを落とす</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">deltaTime</span> <span class="o">></span> <span class="nx">dropInterval</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">dropPiece</span><span class="p">();</span> <span class="c1">// ブロックを1マス落下させる関数を呼び出し</span>
    <span class="nx">dropStart</span> <span class="o">=</span> <span class="nx">timestamp</span><span class="p">;</span> <span class="c1">// dropStartを現在の時刻に更新</span>
  <span class="p">}</span>

  <span class="c1">// ゲーム画面を最新の状態に描画</span>
  <span class="nf">drawBoard</span><span class="p">();</span> <span class="c1">// 固定されたブロックを描画</span>
  <span class="nf">drawPiece</span><span class="p">(</span><span class="nx">currentPiece</span><span class="p">,</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">grid</span><span class="p">);</span> <span class="c1">// 現在落下中のブロックを描画</span>

  <span class="c1">// 次のフレームでのupdate関数の実行をブラウザに依頼</span>
  <span class="nf">requestAnimationFrame</span><span class="p">(</span><span class="nx">update</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// ゲーム開始時に最初に一度だけ呼び出す処理</span>
<span class="kd">function</span> <span class="nf">initializeGame</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ... 初期化処理(ブロック生成など、Step 7で追加) ...</span>
    <span class="nf">requestAnimationFrame</span><span class="p">(</span><span class="nx">update</span><span class="p">);</span> <span class="c1">// ゲームループを開始</span>
<span class="p">}</span>

<span class="c1">// initializeGame(); // ゲーム開始 (initializeGameはStep 7で定義)</span>
</code></pre>
</div>
</div>
<p data-sourcepos="319:1-319:399"><code>update(timestamp)</code>関数が、ブラウザの描画更新タイミングに合わせて繰り返し呼び出されます。この関数の中で、<code>performance.now()</code>を使って前回の処理からの経過時間を計測し、<code>dropInterval</code>で設定した時間(例: 500ms)ごとに<code>dropPiece()</code>関数を呼び出すことで、ブロックが自動で落下する仕組みを作ります。</p>
<p data-sourcepos="321:1-321:130"><code>dropPiece()</code>関数は、シンプルに<code>currentPiece.y</code>を1つ増やすことでブロックを1マス下に移動させます。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="323:1-329:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// ブロックを1マス下に移動</span>
  <span class="c1">// ※ 注意: この時点では落下後の衝突判定はまだ考慮していません。</span>
  <span class="c1">//   次のステップで衝突判定を実装し、落下可能かチェックします。</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="331:1-331:309"><code>update</code>関数の最後で<code>requestAnimationFrame(update)</code>を呼び出すことで、再帰的に<code>update</code>関数が呼び出され、ゲームループが継続します。また、ゲーム開始時に一度だけ<code>requestAnimationFrame(update)</code>を呼び出すことで、ゲームループを開始します。</p>
<h3 data-sourcepos="333:1-333:54">
<span id="step-5-壁とブロックの衝突判定の実装" class="fragment"/><a href="#step-5-%E5%A3%81%E3%81%A8%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E8%A1%9D%E7%AA%81%E5%88%A4%E5%AE%9A%E3%81%AE%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 5: 壁とブロックの衝突判定の実装</h3>
<p data-sourcepos="335:1-335:312">このゲームでは、ブロックがゲームボードの境界(壁や底)を突き抜けたり、既に固定されている他のブロックと重なったりすることはできないようにします。これらの「衝突」を検出する処理を実装し、操作や落下を制限します。</p>
<p data-sourcepos="337:1-337:227">Step 4で作成した<code>dropPiece</code>関数や、Step 3で作成した<code>movePiece</code>, <code>rotatePiece</code>関数の中に、衝突判定のロジックを追加します。衝突判定を行うための<code>collision()</code>関数を実装します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="339:1-362:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">collision</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
  <span class="c1">// currentPieceの形状を構成する各セルについてループ</span>
  <span class="k">return</span> <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nf">some</span><span class="p">((</span><span class="nx">row</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=></span> <span class="c1">// 形状の各行</span>
    <span class="nx">row</span><span class="p">.</span><span class="nf">some</span><span class="p">(</span>
      <span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">x</span><span class="p">)</span> <span class="o">=></span>
        <span class="nx">value</span> <span class="o">&&</span> <span class="c1">// そのセルがブロックの一部であるか (valueが1か)</span>
        <span class="p">(</span>
          <span class="c1">// ゲームボードの境界外に出ていないかチェック</span>
          <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">x</span> <span class="o"> <span class="mi">0</span> <span class="o">||</span> <span class="c1">// 左端の境界判定</span>
          <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">x</span> <span class="o">>=</span> <span class="nx">cols</span> <span class="o">||</span> <span class="c1">// 右端の境界判定</span>
          <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">y</span> <span class="o">>=</span> <span class="nx">rows</span> <span class="o">||</span> <span class="c1">// 下端 (底) の境界判定</span>
          <span class="c1">// 既にボードに固定されているブロックと重なっていないかチェック</span>
          <span class="c1">// ※ ただし、currentPiece.y + y が負になる可能性 (ボードより上) も考慮が必要ですが、</span>
          <span class="c1">//    ここではシンプル化のため、ボード内の座標を前提としています。</span>
          <span class="c1">//    厳密には board[currentPiece.y + y] が undefined にならないかのチェックも必要ですが、</span>
          <span class="c1">//    形状のy座標は通常0以上から始まるため、y >= 0 と currentPiece.y >= 0 であれば問題ありません。</span>
          <span class="c1">//    ゲーム開始時のy=0で即衝突判定するケースなどを考慮します。</span>
          <span class="p">(</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">y</span> <span class="o">>=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">board</span><span class="p">[</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">y</span><span class="p">][</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">x</span><span class="p">])</span>
        <span class="p">)</span>
    <span class="p">)</span>
  <span class="p">);</span>
<span class="p">}</span>
</span></code></pre>
</div>
</div>
<p data-sourcepos="364:1-364:261"><code>collision()</code>関数は、<code>currentPiece</code>の形状を構成する各セルについて、そのセルがゲームボード上のどの位置に来るかを計算し、以下のいずれかに当てはまる場合に<code>true</code>(衝突している)を返します。</p>
<ul data-sourcepos="366:1-370:0">
<li data-sourcepos="366:1-366:82">ゲームボードの左端より左に出ている (<code>currentPiece.x + x )</code></li>
<li data-sourcepos="367:1-367:86">ゲームボードの右端より右に出ている (<code>currentPiece.x + x >= cols</code>)</li>
<li data-sourcepos="368:1-368:83">ゲームボードの底より下に出ている (<code>currentPiece.y + y >= rows</code>)</li>
<li data-sourcepos="369:1-370:0">計算された位置に、既に<code>board</code>配列上でブロックが固定されている (<code>board[currentPiece.y + y][currentPiece.x + x]</code> が0以外)</li>
</ul>
<p data-sourcepos="371:1-371:91">この<code>collision()</code>関数を、移動、回転、落下の各関数に組み込みます。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="373:1-412:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">movePiece</span><span class="p">(</span><span class="nx">dx</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+=</span> <span class="nx">dx</span><span class="p">;</span> <span class="c1">// とりあえず移動させてみる</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 衝突したら</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">-=</span> <span class="nx">dx</span><span class="p">;</span> <span class="c1">// 移動をキャンセル (元に戻す)</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">rotatePiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">rotatedShape</span> <span class="o">=</span> <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">map</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=></span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">row</span><span class="p">)</span> <span class="o">=></span> <span class="nx">row</span><span class="p">[</span><span class="nx">index</span><span class="p">]).</span><span class="nf">reverse</span><span class="p">()</span>
  <span class="p">);</span>
  <span class="kd">const</span> <span class="nx">backup</span> <span class="o">=</span> <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">;</span> <span class="c1">// 回転前の形状をバックアップ</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span> <span class="o">=</span> <span class="nx">rotatedShape</span><span class="p">;</span> <span class="c1">// 回転後の形状を仮セット</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 回転後の形状で衝突したら</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span> <span class="o">=</span> <span class="nx">backup</span><span class="p">;</span> <span class="c1">// 回転をキャンセル (元の形状に戻す)</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// とりあえず下に移動</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 下に移動して衝突したら</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 移動をキャンセル (元のY座標に戻す)</span>
    <span class="c1">// ※ 注意: この後、ブロックを固定する処理に進みます (Step 6)</span>
  <span class="p">}</span>
  <span class="c1">// ※ 注意: この時点ではまだ時間経過による自動落下で落下できなかった場合の固定処理は未実装です。</span>
  <span class="c1">//   次のステップで固定処理を実装します。</span>
<span class="p">}</span>

<span class="c1">// 下矢印キーで呼び出される hardDrop() も、collision() を使って最下部を判定します (Step 5/6 で完成)</span>
<span class="kd">function</span> <span class="nf">hardDrop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">while </span><span class="p">(</span><span class="o">!</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 衝突しない間、下に移動し続ける</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 衝突した位置から1マス戻る (最下部確定)</span>
  <span class="c1">// ※ 注意: この後、ブロックを固定する処理に進みます (Step 6)</span>
  <span class="c1">// lockStart = performance.now(); // ハードドロップ後の猶予 (Step 10で追加)</span>
  <span class="c1">// dropPiece(); // 通常の落下処理に任せる (Step 10で修正)</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="414:1-414:138">これで、ブロックが壁や他のブロックにめり込むことなく、正しく操作・落下するようになりました。</p>
<h3 data-sourcepos="416:1-416:60">
<span id="step-6-ブロックを積み上げて固定する実装" class="fragment"/><a href="#step-6-%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%82%92%E7%A9%8D%E3%81%BF%E4%B8%8A%E3%81%92%E3%81%A6%E5%9B%BA%E5%AE%9A%E3%81%99%E3%82%8B%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 6: ブロックを積み上げて固定する実装</h3>
<p data-sourcepos="418:1-418:192">ブロックがそれ以上下に移動できなくなった際に、そのブロックをゲームボードの一部として固定し、次のブロックが出現するようにします。</p>
<p data-sourcepos="420:1-420:263"><code>dropPiece()</code>関数内で、落下後に<code>collision()</code>が<code>true</code>になった場合(つまり、下に移動しようとしたが衝突した、落下できない状態になった場合)に、ブロックをボードに固定する処理を呼び出します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="422:1-476:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 下に移動して衝突した (落下できない)</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 元のY座標に戻す</span>

    <span class="c1">// ブロックをゲームボードに固定する処理を呼び出し</span>
    <span class="nf">placePiece</span><span class="p">();</span>

    <span class="c1">// 次のブロックを生成し、現在のブロックと入れ替える (Step 7で完成)</span>
    <span class="c1">// currentPiece = nextPiece;</span>
    <span class="c1">// nextPiece = randomPiece();</span>
    <span class="c1">// drawNextPiece();</span>
    <span class="c1">// lockStart = null; // 固定猶予をリセット (Step 10で追加)</span>

    <span class="c1">// ライン消去とスコア加算のチェック (Step 8/9で追加)</span>
    <span class="c1">// clearLines();</span>

    <span class="c1">// 新しいブロックが出現した時点で即衝突したらゲームオーバー (Step 7/??で追加)</span>
    <span class="c1">// if (collision()) {</span>
    <span class="c1">//   gameOver();</span>
    <span class="c1">// }</span>

  <span class="p">}</span>
  <span class="c1">// else { // 落下できた場合は猶予をリセット (Step 10で追加)</span>
  <span class="c1">//   lockStart = null;</span>
  <span class="c1">// }</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">placePiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// currentPieceの形状を構成する各セルについてループ</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">row</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
    <span class="nx">row</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">value</span><span class="p">,</span> <span class="nx">x</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// そのセルがブロックの一部であれば (valueが1)</span>
        <span class="c1">// ゲームボード上の該当セルに、ブロックの色に対応する値を書き込む</span>
        <span class="c1">// colors配列のインデックス (0-7) に+1した値 (1-8) を格納することで、0 (空き) と区別し色も保持</span>
        <span class="nx">board</span><span class="p">[</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">y</span><span class="p">][</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">x</span> <span class="o">+</span> <span class="nx">x</span><span class="p">]</span> <span class="o">=</span>
          <span class="nx">colors</span><span class="p">.</span><span class="nf">indexOf</span><span class="p">(</span><span class="nx">currentPiece</span><span class="p">.</span><span class="nx">color</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="c1">// hardDrop() も、最下部に到達したらplacePiece() を呼び出すようにする</span>
<span class="kd">function</span> <span class="nf">hardDrop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">while </span><span class="p">(</span><span class="o">!</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>

  <span class="nf">placePiece</span><span class="p">();</span> <span class="c1">// 最下部に固定</span>

  <span class="c1">// ... (ライン消去、次のブロック生成などの後処理) ...</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="478:1-478:430"><code>placePiece()</code>関数は、<code>currentPiece</code>が現在位置に固定されたものとして、その形状を<code>board</code>配列に書き込みます。<code>board</code>配列の該当セルに、ブロックの色を識別するための値(<code>colors</code>配列のインデックス + 1)をセットします。これにより、そのブロックはゲームボードの一部となり、<code>drawBoard()</code>関数で描画されるようになります。</p>
<p data-sourcepos="480:1-480:119">また、<code>hardDrop()</code>関数も、最下部に到達した後に<code>placePiece()</code>を呼び出すように修正します。</p>
<h3 data-sourcepos="482:1-482:70">
<span id="step-7-nextブロックのキャンバスとロジックの実装" class="fragment"/><a href="#step-7-next%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%90%E3%82%B9%E3%81%A8%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%81%AE%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 7: Nextブロックのキャンバスとロジックの実装</h3>
<p data-sourcepos="484:1-484:190">次に落ちてくるブロックをプレイヤーに示すためのNextブロック表示エリアを実装し、ゲームの進行に合わせて表示を更新するようにします。</p>
<p data-sourcepos="486:1-486:105">事前準備でHTMLに用意した<code>nextBlockCanvas</code>要素の描画コンテキストを取得します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="488:1-497:3">
<div class="highlight">
<pre><code><span class="kd">const</span> <span class="nx">nextBlockCanvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span>
  <span class="dl">"</span><span class="s2">nextBlockCanvas</span><span class="dl">"</span>
<span class="p">)</span> <span class="kd">as </span><span class="nx">HTMLCanvasElement</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">nextContext</span> <span class="o">=</span> <span class="nx">nextBlockCanvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">"</span><span class="s2">2d</span><span class="dl">"</span><span class="p">)</span><span class="o">!</span><span class="p">;</span> <span class="c1">// Nextブロック用コンテキスト</span>
<span class="kd">const</span> <span class="nx">nextBlockGrid</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span> <span class="c1">// Nextブロックエリアの1マスサイズ (今回はゲームボードと同じ)</span>
<span class="kd">const</span> <span class="nx">nextBlockSize</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// Nextブロックエリアを何マス分として扱うか (ブロックは最大4マスなので5マスあれば収まる)</span>

<span class="kd">let</span> <span class="nx">nextPiece</span><span class="p">:</span> <span class="nx">Piece</span><span class="p">;</span> <span class="c1">// 次のブロックを保持する変数</span>
</code></pre>
</div>
</div>
<p data-sourcepos="499:1-499:84">ブロックをランダムに生成する<code>randomPiece()</code>関数を作成します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="501:1-511:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">randomPiece</span><span class="p">():</span> <span class="nx">Piece</span> <span class="p">{</span>
  <span class="c1">// shapes配列からランダムに形状を選ぶ</span>
  <span class="kd">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">shapes</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">shape</span> <span class="o">=</span> <span class="nx">shapes</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span>
  <span class="c1">// ゲームボードの上部中央に出現するように初期X座標を計算</span>
  <span class="kd">const</span> <span class="nx">startX</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">((</span><span class="nx">cols</span> <span class="o">-</span> <span class="nx">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
  <span class="c1">// ランダムな形状、対応する色、計算した初期位置でPieceオブジェクトを作成して返す</span>
  <span class="k">return</span> <span class="p">{</span> <span class="nx">shape</span><span class="p">,</span> <span class="na">color</span><span class="p">:</span> <span class="nx">colors</span><span class="p">[</span><span class="nx">index</span><span class="p">],</span> <span class="na">x</span><span class="p">:</span> <span class="nx">startX</span><span class="p">,</span> <span class="na">y</span><span class="p">:</span> <span class="mi">0</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="513:1-513:204">Nextブロックエリアに<code>nextPiece</code>を描画する関数<code>drawNextPiece()</code>を作成します。Nextブロックエリアの中央に表示されるように位置を調整する計算を含めます。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="515:1-526:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">drawNextPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// NextブロックCanvasをクリア</span>
  <span class="nx">nextContext</span><span class="p">.</span><span class="nf">clearRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">nextBlockCanvas</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span> <span class="nx">nextBlockCanvas</span><span class="p">.</span><span class="nx">height</span><span class="p">);</span>
  <span class="c1">// Nextブロックエリアの中央に描画するためのオフセットを計算</span>
  <span class="kd">const</span> <span class="nx">offsetX</span> <span class="o">=</span> <span class="p">(</span><span class="nx">nextBlockSize</span> <span class="o">-</span> <span class="nx">nextPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">offsetY</span> <span class="o">=</span> <span class="p">(</span><span class="nx">nextBlockSize</span> <span class="o">-</span> <span class="nx">nextPiece</span><span class="p">.</span><span class="nx">shape</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
  <span class="c1">// オフセットを適用した新しいPieceオブジェクトを作成し、drawPieceで描画</span>
  <span class="kd">const</span> <span class="nx">nextPieceCentered</span><span class="p">:</span> <span class="nx">Piece</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">nextPiece</span><span class="p">,</span> <span class="na">x</span><span class="p">:</span> <span class="nx">offsetX</span><span class="p">,</span> <span class="na">y</span><span class="p">:</span> <span class="nx">offsetY</span> <span class="p">};</span>
  <span class="nf">drawPiece</span><span class="p">(</span><span class="nx">nextPieceCentered</span><span class="p">,</span> <span class="nx">nextContext</span><span class="p">,</span> <span class="nx">nextBlockGrid</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="528:1-528:279">ゲーム開始時や、ブロックが固定されて次のブロックが出現する際に、これらの関数を呼び出すようにします。ゲーム開始時の初期化関数<code>initializeGame()</code>を定義し、ゲームループの開始前に一度呼び出します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="530:1-546:3">
<div class="highlight">
<pre><code><span class="c1">// initializeGame 関数を定義</span>
<span class="kd">function</span> <span class="nf">initializeGame</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// ゲームボードを初期化(全て空きマスに)</span>
  <span class="nx">board</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nx">rows</span> <span class="p">},</span> <span class="p">()</span> <span class="o">=></span> <span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
  <span class="c1">// 最初のブロックと次のブロックを生成</span>
  <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="c1">// Nextブロックを描画</span>
  <span class="nf">drawNextPiece</span><span class="p">();</span>
  <span class="c1">// ゲームループを開始</span>
  <span class="nf">requestAnimationFrame</span><span class="p">(</span><span class="nx">update</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// ゲーム開始!</span>
<span class="nf">initializeGame</span><span class="p">();</span>
</code></pre>
</div>
</div>
<p data-sourcepos="548:1-548:257">そして、Step 6で実装した<code>dropPiece()</code>関数内で、ブロックを固定(<code>placePiece()</code>)した後に、<code>currentPiece</code>と<code>nextPiece</code>を入れ替え、新しい<code>nextPiece</code>を生成し、<code>drawNextPiece()</code>を呼び出す処理を追加します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="550:1-601:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>

    <span class="nf">placePiece</span><span class="p">();</span> <span class="c1">// ブロック固定</span>

    <span class="c1">// 次のブロックを現在のブロックにする</span>
    <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nx">nextPiece</span><span class="p">;</span>
    <span class="c1">// 新しい次のブロックを生成</span>
    <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
    <span class="c1">// Nextブロック表示を更新</span>
    <span class="nf">drawNextPiece</span><span class="p">();</span>
    <span class="c1">// lockStart = null; // 固定猶予をリセット (Step 10で追加)</span>

    <span class="c1">// ライン消去とスコア加算のチェック (Step 8/9で追加)</span>
    <span class="c1">// clearLines();</span>

    <span class="c1">// 新しいブロックが出現した時点で、即座に他のブロックと衝突している場合、ゲームオーバー</span>
    <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
      <span class="nf">gameOver</span><span class="p">();</span> <span class="c1">// ゲームオーバー処理 (別途定義)</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="c1">// else { // 落下できた場合は猶予をリセット (Step 10で追加)</span>
  <span class="c1">//   lockStart = null;</span>
  <span class="c1">// }</span>
<span class="p">}</span>

<span class="c1">// ゲームオーバー処理の定義 (リセットなど)</span>
<span class="kd">function</span> <span class="nf">gameOver</span><span class="p">()</span> <span class="p">{</span>
  <span class="nf">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Game Over!</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// 例としてアラート表示</span>
  <span class="nf">resetGame</span><span class="p">();</span> <span class="c1">// ゲーム状態をリセットする関数 (別途定義)</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">resetGame</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// スコアをリセット (Step 8で追加)</span>
  <span class="c1">// updateScore(0); // スコア表示を更新 (Step 9で追加)</span>
  <span class="nx">board</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nx">rows</span> <span class="p">},</span> <span class="p">()</span> <span class="o">=></span> <span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span> <span class="c1">// ボードをリセット</span>
  <span class="nf">initializePieces</span><span class="p">();</span> <span class="c1">// ブロックを再生成 (initializePiecesはinitializeGameから名前変更または一部抽出)</span>
<span class="p">}</span>

<span class="c1">//initializeGame から一部抽出・改変した initializePieces 関数(リセット時などに使用)</span>
<span class="kd">function</span> <span class="nf">initializePieces</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nf">drawNextPiece</span><span class="p">();</span>
<span class="p">}</span>

<span class="c1">// ゲーム開始は initializeGame() を使用</span>
<span class="c1">// initializeGame(); // main.tsの末尾で呼び出す</span>
</code></pre>
</div>
</div>
<p data-sourcepos="603:1-603:313">これで、ブロックが固定されるたびにNextブロックが表示され、次のブロックがゲームボードの上部に出現するようになりました。また、新しいブロックが出現した時点で既に衝突していた場合のゲームオーバー判定も加わりました。</p>
<h3 data-sourcepos="605:1-605:55">
<span id="step-8-1行そろうとブロックを消す実装" class="fragment"/><a href="#step-8-1%E8%A1%8C%E3%81%9D%E3%82%8D%E3%81%86%E3%81%A8%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%82%92%E6%B6%88%E3%81%99%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 8: 1行そろうとブロックを消す実装</h3>
<p data-sourcepos="607:1-607:216">このゲームのゲーム性として、横一列にブロックが揃うとラインが消去され、上のブロックが下に落ちてくるようにします。このライン消去処理を実装します。</p>
<p data-sourcepos="609:1-609:121">Step 6でブロックを固定(<code>placePiece()</code>)した後に、<code>clearLines()</code>関数を呼び出すようにします。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="611:1-671:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">clearLines</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">linesCleared</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// 今回の操作で消去したライン数をカウント</span>

  <span class="c1">// ゲームボードの下から順番に各行をチェック</span>
  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">rows</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">y</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">y</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// その行の全てのセルにブロックがあるか? (値が0以外か)</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">board</span><span class="p">[</span><span class="nx">y</span><span class="p">].</span><span class="nf">every</span><span class="p">((</span><span class="nx">value</span><span class="p">)</span> <span class="o">=></span> <span class="nx">value</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
      <span class="c1">// 全て埋まっていた場合、その行をボード配列から削除</span>
      <span class="nx">board</span><span class="p">.</span><span class="nf">splice</span><span class="p">(</span><span class="nx">y</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
      <span class="c1">// ボードの一番上に新しい空の行 (全て0) を追加</span>
      <span class="nx">board</span><span class="p">.</span><span class="nf">unshift</span><span class="p">(</span><span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
      <span class="c1">// 行を削除したので、同じy座標をもう一度チェックする必要があるため、yをインクリメント</span>
      <span class="nx">y</span><span class="o">++</span><span class="p">;</span>
      <span class="c1">// 消去したライン数をカウント</span>
      <span class="nx">linesCleared</span><span class="o">++</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="c1">// ※ スコア加算処理は次のステップで追加</span>
  <span class="c1">// if (linesCleared > 0) { ... updateScore(...) ... }</span>
<span class="p">}</span>

<span class="c1">// dropPiece関数内で placePiece() の後に clearLines() を呼び出すように修正</span>
<span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="nf">placePiece</span><span class="p">();</span>
    <span class="c1">// ここでライン消去を呼び出す</span>
    <span class="nf">clearLines</span><span class="p">();</span> <span class="c1">// 
    <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nx">nextPiece</span><span class="p">;</span>
    <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
    <span class="nf">drawNextPiece</span><span class="p">();</span>
    <span class="c1">// lockStart = null; // Step 10</span>
    <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
      <span class="nf">gameOver</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="c1">// else { lockStart = null; } // Step 10</span>
<span class="p">}</span>

<span class="c1">// hardDrop関数内でも placePiece() の後に clearLines() を呼び出すように修正</span>
<span class="kd">function</span> <span class="nf">hardDrop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">while </span><span class="p">(</span><span class="o">!</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="nf">placePiece</span><span class="p">();</span>
  <span class="c1">// ここでライン消去を呼び出す</span>
  <span class="nf">clearLines</span><span class="p">();</span> <span class="c1">// 
  <span class="c1">// lockStart = performance.now(); // Step 10</span>
  <span class="c1">// dropPiece(); // Step 10 で修正</span>
  <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nx">nextPiece</span><span class="p">;</span> <span class="c1">// hardDrop後も次のブロックが登場</span>
  <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nf">drawNextPiece</span><span class="p">();</span>
  <span class="c1">// lockStart = null; // Step 10</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
      <span class="nf">gameOver</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">}</span>
</span></span></code></pre>
</div>
</div>
<p data-sourcepos="673:1-673:598"><code>clearLines()</code>関数は、ボードの最も下の行から順番に上に向かって、各行が全てブロックで埋まっているか(<code>every</code>メソッドを使って判定)を確認します。もし全て埋まっていれば、その行を<code>board</code>配列から削除し、配列の先頭(一番上)に新しい空の行を追加します。これにより、上の行が一段下に移動したように見えます。削除した行の位置をもう一度チェックする必要があるため、ループ変数の<code>y</code>をインクリメントしているのがポイントです。</p>
<p data-sourcepos="675:1-675:75">これで、ラインが揃うと消去されるようになりました。</p>
<h3 data-sourcepos="677:1-677:61">
<span id="step-9-1行そろうとスコアが加算される実装" class="fragment"/><a href="#step-9-1%E8%A1%8C%E3%81%9D%E3%82%8D%E3%81%86%E3%81%A8%E3%82%B9%E3%82%B3%E3%82%A2%E3%81%8C%E5%8A%A0%E7%AE%97%E3%81%95%E3%82%8C%E3%82%8B%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 9: 1行そろうとスコアが加算される実装</h3>
<p data-sourcepos="679:1-679:138">ラインを消去した際に、消去したライン数に応じてプレイヤーのスコアを加算し、画面に表示します。</p>
<p data-sourcepos="681:1-681:124">事前準備でHTMLに用意した<code>scoreBoard</code>要素を取得します。スコアを保持する変数も用意します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="683:1-712:3">
<div class="highlight">
<pre><code><span class="kd">const</span> <span class="nx">scoreBoard</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">scoreBoard</span><span class="dl">"</span><span class="p">)</span> <span class="kd">as </span><span class="nx">HTMLElement</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// 現在のスコア</span>

<span class="c1">// スコア表示を更新する関数</span>
<span class="kd">function</span> <span class="nf">updateScore</span><span class="p">(</span><span class="nx">points</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">score</span> <span class="o">+=</span> <span class="nx">points</span><span class="p">;</span> <span class="c1">// スコアを加算</span>
  <span class="c1">// スコアボードのテキストを更新</span>
  <span class="nx">scoreBoard</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="s2">`Score: </span><span class="p">${</span><span class="nx">score</span><span class="p">.</span><span class="nf">toString</span><span class="p">().</span><span class="nf">padStart</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span><span class="p">;</span> <span class="c1">// 5桁表示の例</span>
<span class="p">}</span>

<span class="c1">// ゲーム開始時やリセット時にスコアを0にリセットし、表示を更新</span>
<span class="kd">function</span> <span class="nf">resetGame</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="nf">updateScore</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// 初期スコア表示を更新</span>
  <span class="nx">board</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nx">rows</span> <span class="p">},</span> <span class="p">()</span> <span class="o">=></span> <span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
  <span class="nf">initializePieces</span><span class="p">();</span>
<span class="p">}</span>

<span class="c1">// initializeGame 関数内でも初期スコア表示を呼び出す</span>
<span class="kd">function</span> <span class="nf">initializeGame</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">board</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">({</span> <span class="na">length</span><span class="p">:</span> <span class="nx">rows</span> <span class="p">},</span> <span class="p">()</span> <span class="o">=></span> <span class="nc">Array</span><span class="p">(</span><span class="nx">cols</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
  <span class="nx">score</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// スコア初期化</span>
  <span class="nf">updateScore</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// スコア表示を初期化</span>
  <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span>
  <span class="nf">drawNextPiece</span><span class="p">();</span>
  <span class="nf">requestAnimationFrame</span><span class="p">(</span><span class="nx">update</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="714:1-714:175">そして、Step 8で作成した<code>clearLines()</code>関数内に、消去したライン数(<code>linesCleared</code>)に応じてスコアを加算するロジックを追加します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="716:1-729:3">
<div class="highlight">
<pre><code><span class="kd">function</span> <span class="nf">clearLines</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">linesCleared</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="c1">// ... (ライン消去処理) ...</span>

  <span class="c1">// 消去したライン数が1以上の場合</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">linesCleared</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 消去したライン数に応じたポイントを計算 (例: 1ライン100点, 2ライン300点, 3ライン500点, 4ライン800点)</span>
    <span class="kd">const</span> <span class="nx">points</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="mi">800</span><span class="p">][</span><span class="nx">linesCleared</span><span class="p">];</span>
    <span class="c1">// スコア更新関数を呼び出し</span>
    <span class="nf">updateScore</span><span class="p">(</span><span class="nx">points</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="731:1-731:405"><code>updateScore(points)</code>関数は、引数で受け取ったポイントをグローバル変数<code>score</code>に加算し、<code>scoreBoard</code>要素のテキストを更新することで、現在のスコアを画面に表示します。<code>clearLines</code>関数内で、消去したライン数に応じて<code>[0, 100, 300, 500, 800]</code>のような配列を使って得点を計算し、<code>updateScore</code>を呼び出しています。</p>
<p data-sourcepos="733:1-733:84">これで、ラインを揃えることで得点が入るようになりました。</p>
<h3 data-sourcepos="735:1-735:97">
<span id="step-10-ブロックが落ちてもロックされるまでに少し時間を設ける実装" class="fragment"/><a href="#step-10-%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%8C%E8%90%BD%E3%81%A1%E3%81%A6%E3%82%82%E3%83%AD%E3%83%83%E3%82%AF%E3%81%95%E3%82%8C%E3%82%8B%E3%81%BE%E3%81%A7%E3%81%AB%E5%B0%91%E3%81%97%E6%99%82%E9%96%93%E3%82%92%E8%A8%AD%E3%81%91%E3%82%8B%E5%AE%9F%E8%A3%85"><i class="fa fa-link"/></a>Step 10: ブロックが落ちてもロックされるまでに少し時間を設ける実装</h3>
<p data-sourcepos="737:1-737:331">落ち物パズルゲームでは、ブロックが接地(他のブロックやボードの底に触れること)しても、すぐに固定されるわけではなく、少しの間操作の猶予がつけるとゲームの面白さが上がります。この「固定猶予(Lock Delay)」の仕組みを実装します。</p>
<p data-sourcepos="739:1-739:271">これは、Step 6でブロックを固定する処理を呼び出していた<code>dropPiece()</code>関数を修正することで実現します。固定猶予の時間を設定する定数と、猶予時間の計測を開始した時刻を記録する変数を導入します。</p>
<div class="code-frame" data-lang="typescript" data-sourcepos="741:1-799:3">
<div class="highlight">
<pre><code><span class="kd">const</span> <span class="nx">lockDelay</span> <span class="o">=</span> <span class="mi">300</span><span class="p">;</span> <span class="c1">// 固定猶予時間 (ミリ秒) - 例: 0.3秒</span>
<span class="kd">let</span> <span class="nx">lockStart</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// 固定猶予の計測開始時刻。猶予中でないときはnull</span>

<span class="kd">function</span> <span class="nf">dropPiece</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// まず下に移動させてみる</span>
  <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 下に移動して衝突した (落下できない)</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 元のY座標に戻す</span>

    <span class="c1">// 落下できない状態になったのが初めてなら、猶予時間の計測を開始</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">lockStart</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">lockStart</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nf">now</span><span class="p">();</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="c1">// 既に落下できない状態が継続していて、かつ猶予時間(lockDelay)が経過したかチェック</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">performance</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">-</span> <span class="nx">lockStart</span> <span class="o">>=</span> <span class="nx">lockDelay</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 猶予時間経過!ブロックを固定する</span>
        <span class="nf">placePiece</span><span class="p">();</span>
        <span class="nf">clearLines</span><span class="p">();</span> <span class="c1">// ライン消去</span>
        <span class="nx">currentPiece</span> <span class="o">=</span> <span class="nx">nextPiece</span><span class="p">;</span> <span class="c1">// 次のブロックへ</span>
        <span class="nx">nextPiece</span> <span class="o">=</span> <span class="nf">randomPiece</span><span class="p">();</span> <span class="c1">// 新しいNextブロック生成</span>
        <span class="nf">drawNextPiece</span><span class="p">();</span> <span class="c1">// Nextブロック描画更新</span>
        <span class="nx">lockStart</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// 固定猶予をリセット</span>

        <span class="c1">// 新しいブロックが出現した時点で即衝突ならゲームオーバー</span>
        <span class="k">if </span><span class="p">(</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
          <span class="nf">gameOver</span><span class="p">();</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="c1">// 猶予時間内であれば、ここではまだ固定しない</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="c1">// 下に移動できた場合(接地していない)、固定猶予は発生しないのでリセット</span>
    <span class="nx">lockStart</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// hardDrop() 関数も、落下後にlockStartを設定するように修正します。</span>
<span class="c1">// ハードドロップ後も、接地したブロックに少し操作猶予を与えるためです。</span>
<span class="kd">function</span> <span class="nf">hardDrop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">while </span><span class="p">(</span><span class="o">!</span><span class="nf">collision</span><span class="p">())</span> <span class="p">{</span>
    <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">currentPiece</span><span class="p">.</span><span class="nx">y</span> <span class="o">-=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 最下部位置に決定</span>

  <span class="nx">lockStart</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nf">now</span><span class="p">();</span> <span class="c1">// ハードドロップ後、固定猶予の計測を開始</span>

  <span class="c1">// ハードドロップ後の固定処理は、dropPieceの猶予時間判定に任せる</span>
  <span class="c1">// ※ 元コードでは hardDrop内でも placePiece等を呼んでいますが、</span>
  <span class="c1">//    ロック猶予をdropPieceに集約するなら、ここでplacePieceは呼びません。</span>
  <span class="c1">//    ただし、元のコードはハードドロップ即固定+猶予という少し異なる挙動なので、</span>
  <span class="c1">//    元のコードの意図通りにする場合は以下のようになります。</span>
  <span class="c1">// placePiece(); // 最下部に固定 (猶予時間内でも操作はできるが即固定されている状態)</span>
  <span class="c1">// clearLines();</span>
  <span class="c1">// currentPiece = nextPiece;</span>
  <span class="c1">// nextPiece = randomPiece();</span>
  <span class="c1">// drawNextPiece();</span>
  <span class="c1">// if (collision()) { gameOver(); }</span>
<span class="p">}</span>
</code></pre>
</div>
</div>
<p data-sourcepos="801:1-801:599"><code>lockDelay</code>と<code>lockStart</code>変数を使って、ブロックが接地した(落下できなくなった)タイミングで<code>lockStart</code>に現在の時刻を記録します。その状態が<code>lockDelay</code>ミリ秒以上続いた場合にのみ、ブロックの固定処理(<code>placePiece()</code>など)を実行します。猶予時間内にプレイヤーがブロックを左右に移動させたり回転させたりして、再び下に落下できるようになった場合は、<code>collision()</code>が<code>false</code>になるため<code>lockStart</code>が<code>null</code>にリセットされ、猶予期間は終了となります。</p>
<p data-sourcepos="803:1-803:161"><code>hardDrop()</code>後も<code>lockStart</code>を設定することで、ハードドロップ後もわずかな時間だけ操作の猶予が生まれるようになります。</p>
<h2 data-sourcepos="805:1-805:12">
<span id="まとめ" class="fragment"/><a href="#%E3%81%BE%E3%81%A8%E3%82%81"><i class="fa fa-link"/></a>まとめ</h2>
<p data-sourcepos="807:1-807:257">今回の記事では、TypeScriptとHTMLのCanvas APIを使って、ブラウザ上で動作するテトリスのような落ち物パズルゲームゲームをゼロから構築する過程を、10のステップに分けて丁寧に解説しました。</p>
<ol data-sourcepos="809:1-819:0">
<li data-sourcepos="809:1-809:35">Canvasを使った描画方法</li>
<li data-sourcepos="810:1-810:44">ブロックのデータ構造と描画</li>
<li data-sourcepos="811:1-811:41">キーボード入力による操作</li>
<li data-sourcepos="812:1-812:59">ゲームループと時間経過による自動落下</li>
<li data-sourcepos="813:1-813:47">壁や固定ブロックとの衝突判定</li>
<li data-sourcepos="814:1-814:26">ブロックの固定</li>
<li data-sourcepos="815:1-815:41">次のブロックの描画と出現</li>
<li data-sourcepos="816:1-816:20">ライン消去</li>
<li data-sourcepos="817:1-817:20">スコア加算</li>
<li data-sourcepos="818:1-819:0">操作猶予を生むロックディレイ</li>
</ol>
<p data-sourcepos="820:1-820:434">これらのステップを通じて、ゲーム開発における基本的な考え方や実装テクニックを学んでいただけたかと思います。特に、ゲームの状態をデータとして持ち、ゲームループの中でその状態を更新し、Canvasに再描画するというサイクルが、多くの2Dゲームの基本構造であることを理解していただけたのではないでしょうか。</p>
<p data-sourcepos="822:1-822:390">今回作成したゲームはシンプルなものですが、ここからさらにホールド機能、異なる回転法則(SRS)、ゴーストピース表示、レベルによる落下速度の変化、サウンドエフェクト、スコアランキング機能など、様々な要素を追加してゲームをより面白く、完成度高くしていくことが可能です。</p>
<p data-sourcepos="824:1-824:228">ぜひ、この記事で得た知識を元に、ご自身のアイデアを加えてオリジナルのゲーム開発に挑戦してみてください。皆様の今後の学習や制作のきっかけとなれば幸いです。</p>
</div>
<p><script>!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '305156090176370');
fbq('trackSingle', '305156090176370', 'PageView');</script><br />
<br /><a href="https://h.accesstrade.net/sp/cc?rk=0100oyf000odbd" rel="nofollow" referrerpolicy="no-referrer-when-downgrade"><img decoding="async" src="https://h.accesstrade.net/sp/rr?rk=0100oyf000odbd" alt="フラッグシティパートナーズ海外不動産投資セミナー" border="0" /></a>
<a href="https://h.accesstrade.net/sp/cc?rk=01004iw600odbd" rel="nofollow" referrerpolicy="no-referrer-when-downgrade"><img decoding="async" src="https://h.accesstrade.net/sp/rr?rk=01004iw600odbd" alt="【DMM FX】入金" border="0" /></a><br />
<br /><a href="https://qiita.com/hiranuma/items/e8e43390b540a8eb692a?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items">Source link </a></p>
<p>Views: 2</p><div class='amazon-auto-links'><div class="amazon-products-container-list amazon-unit-29349 unit-type-category" style="">
        <div class="amazon-product-container">
        <div class="amazon-auto-links-product">
    <div class="amazon-auto-links-product-image">
        <div class='amazon-product-thumbnail-container' data-href='https://www.amazon.co.jp/Bluetooth-%E3%80%90%E6%A5%B5%E8%96%84%E5%9E%8B%E3%83%A2%E3%83%87%E3%83%AB-3%E6%AE%B5%E9%9A%8EDPI%E8%AA%BF%E6%95%B4-Type-C%E5%85%85%E9%9B%BB-Macbook%E3%81%AB%E5%AF%BE%E5%BF%9C/dp/B0DZ6K625Q/ref=zg_bsnr_g_computers_d_sccl_18/356-3178928-8054451?psc=1&tag=inmobi06-22' data-large-src='https://images-fe.ssl-images-amazon.com/images/I/51seCn2Ek3L._AC_UL500_SR500,500_.jpg'><div class="amazon-product-thumbnail" style="max-width:160px;max-height:160px;width:160px">
    <a href="https://www.amazon.co.jp/Bluetooth-%E3%80%90%E6%A5%B5%E8%96%84%E5%9E%8B%E3%83%A2%E3%83%87%E3%83%AB-3%E6%AE%B5%E9%9A%8EDPI%E8%AA%BF%E6%95%B4-Type-C%E5%85%85%E9%9B%BB-Macbook%E3%81%AB%E5%AF%BE%E5%BF%9C/dp/B0DZ6K625Q/ref=zg_bsnr_g_computers_d_sccl_18/356-3178928-8054451?psc=1&tag=inmobi06-22" title="ワイヤレス Bluetooth マウス 【極薄型モデル Bluetooth 5.3&2.4GHz】 マウス 静音 3段階DPI調整(800/1200/1600) 超軽量 Type-C充電 スリープモード 高精度 小型まうす 左右対称 節電 7色LED 人間工学設計 コンパクト オフィス ゲーム Mac/Windows/Surface/PC/Macbookに対応 (ブラック): " rel="nofollow noopener" target="_blank">
        <img decoding="async" src="https://images-fe.ssl-images-amazon.com/images/I/51seCn2Ek3L._AC_UL160_SR160,160_.jpg" alt="" style="max-height:160px" />
    </a>
</div></div>
    </div>
    <div class="amazon-auto-links-product-body">
        <h5 class="amazon-product-title">
<a href="https://www.amazon.co.jp/Bluetooth-%E3%80%90%E6%A5%B5%E8%96%84%E5%9E%8B%E3%83%A2%E3%83%87%E3%83%AB-3%E6%AE%B5%E9%9A%8EDPI%E8%AA%BF%E6%95%B4-Type-C%E5%85%85%E9%9B%BB-Macbook%E3%81%AB%E5%AF%BE%E5%BF%9C/dp/B0DZ6K625Q/ref=zg_bsnr_g_computers_d_sccl_18/356-3178928-8054451?psc=1&tag=inmobi06-22" title="ワイヤレス Bluetooth マウス 【極薄型モデル Bluetooth 5.3&2.4GHz】 マウス 静音 3段階DPI調整(800/1200/1600) 超軽量 Type-C充電 スリープモード 高精度 小型まうす 左右対称 節電 7色LED 人間工学設計 コンパクト オフィス ゲーム Mac/Windows/Surface/PC/Macbookに対応 (ブラック): " rel="nofollow noopener" target="_blank">ワイヤレス Bluetooth マウス 【極薄型モデル Bluetooth 5.3&2.4GHz】 マウス 静音 3段階DPI調整(800/1200/1600) 超軽量 Type-C充電 スリープモード 高精度 小型まうす 左右対称 節電 7色LED 人間工学設計 コンパクト オフィス ゲーム Mac/Windows/Surface/PC/Macbookに対応 (ブラック)</a>
</h5>
        <div class='amazon-customer-rating-stars'><div class='crIFrameNumCustReviews' data-rating='50' data-review-count='44' data-review-url='https://www.amazon.co.jp/product-reviews/B0DZ6K625Q?tag=inmobi06-22'><span class='crAvgStars'><span class='review-stars'><a href='https://www.amazon.co.jp/product-reviews/B0DZ6K625Q?tag=inmobi06-22' target='_blank' rel='nofollow noopener'><svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 160 32' enable-background='new 0 0 160 32'><title>5星中5.0(44)
¥1,599 (2025年5月4日 13:08 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -

Most Popular