Reactで作る将棋ゲーム

## はじめに

こんにちは。ココネでビリングシステムエンジニアをしていますnk60です。

自分の日々の業務は、主に会社の決済のサーバサイドの開発に携わっているのですが、ある日業務内で少しReactに触れることがありました。

ココネはゲーム会社なのですが、自分はあまりゲームの具体的な制作には携わっていません。

Reactを触った経験もほとんどなかったので、少し勉強しておこうと思いReactのチュートリアルを見てみると、内容が三目並べでした！

https://ja.reactjs.org/tutorial/tutorial.html

Reactの考え方やプログラムの書き方、三目並べを作るまでの過程などをみて面白いなと思いました。

ゲームを作ったことはほとんどないのですが、チュートリアルを参考に、自分なりに考えて将棋のゲームを作ってみました。

## プロジェクトの作成

公式チュートリアルと同様にプロジェクトを作成します。

```
npx create-react-app my-app
cd my-app
cd src

# If you're using a Mac or Linux:
rm -f *

# Or, if you're on Windows:
del *

# Then, switch back to the project folder
cd ..
```

## ソースコードの配置

作成した将棋ゲームのソースコードは以下になります。

src/index.jsがメインの画面を作る部分です。

```javascript:プロジェクトディレクトリ/src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import imgKing from "./img/玉.png";
import imgRook from "./img/飛.png";
import imgBishop from "./img/角.png";
import imgGoldGeneral from "./img/金.png";
import imgSilverGeneral from "./img/銀.png";
import imgKnight from "./img/桂.png";
import imgLance from "./img/香.png";
import imgPawn from "./img/歩.png";
import imgPromotedRook from "./img/竜.png";
import imgPromotedBishop from "./img/馬.png";
import imgPromotedSilverGeneral from "./img/成銀.png";
import imgPromotedKnight from "./img/成桂.png";
import imgPromotedLance from "./img/成香.png";
import imgPromotedPawn from "./img/と.png";
import { BoardInfo, Selection } from './components/BoardInfo';

const imgByName = {
  "玉": imgKing,
  "飛": imgRook,
  "角": imgBishop,
  "金": imgGoldGeneral,
  "銀": imgSilverGeneral,
  "桂": imgKnight,
  "香": imgLance,
  "歩": imgPawn,
  "竜": imgPromotedRook,
  "馬": imgPromotedBishop,
  "成銀": imgPromotedSilverGeneral,
  "成桂": imgPromotedKnight,
  "成香": imgPromotedLance,
  "と": imgPromotedPawn
};

function Square(props) {
  return (
    <button id={props.selectInfo} className="square" onClick={props.onClick} >
      <img id={props.piece.owner} src={imgByName[props.piece.name]} alt="" />
      <p>{(props.num >= 2) && props.num}</p>
    </button>
  );
}

class Board extends React.Component {
  renderSquare(i, j) {
    return (
      <Square
        key={j}
        piece={this.props.board[i][j]}
        selectInfo={this.props.boardSelectInfo[i][j]}
        onClick={() => this.props.onClick(i, j)}
      />
    );
  }

  render() {

    return (
      <div>
        {
          Array(9).fill(0).map((_, i) => {
            return (
              <div className="board-row" key={i}>
                {
                  Array(9).fill(0).map((_, j) => {
                    return (
                      this.renderSquare(i, j)
                    )
                  })
                }
              </div>
            )
          })
        }
      </div>
    );
  }
}

class PieceStand extends React.Component {
  renderSquare(i) {
    return (
      <Square
        key={i}
        piece={this.props.pieceStand[i]}
        num={this.props.pieceStandNum[this.props.pieceStand[i].name]}
        selectInfo={this.props.pieceStandSelectInfo[i]}
        onClick={() => this.props.onClick(i)}
      />
    );
  }

  render() {

    return (
      <div className="board-row">
        {
          Array(9).fill(0).map((_, i) => {
            return (
              this.renderSquare(i)
            )
          })
        }
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      boardInfo: new BoardInfo()
    };
  }

  canselSelection() {
    const nextBoardInfo = this.state.boardInfo;
    if (nextBoardInfo.selection.isNow) {
      nextBoardInfo.selection.isNow = false;
    } else {
      nextBoardInfo.selection = new Selection();
    }
    this.setState({
      boardInfo: nextBoardInfo
    });
  }

  boardClick(i, j) {
    this.state.boardInfo.boardClick(i, j);
  }

  pieceStandClick(piece) {
    this.state.boardInfo.pieceStandClick(piece);
  }

  render() {

    return (
      <div className="game" onClick={() => this.canselSelection()}>
        <div className="game-board">
          <PieceStand
            pieceStand={this.state.boardInfo.pieceStand["後手"]}
            pieceStandNum={this.state.boardInfo.pieceStandNum["後手"]}
            pieceStandSelectInfo={this.state.boardInfo.selection.pieceStandSelectInfo["後手"]}
            onClick={(i) => this.pieceStandClick(this.state.boardInfo.pieceStand["後手"][i])}
          />
          <br />
          <Board
            board={this.state.boardInfo.board}
            boardSelectInfo={this.state.boardInfo.selection.boardSelectInfo}
            onClick={(i, j) => this.boardClick(i, j)}
          />
          <br />
          <PieceStand
            pieceStand={this.state.boardInfo.pieceStand["先手"]}
            pieceStandNum={this.state.boardInfo.pieceStandNum["先手"]}
            pieceStandSelectInfo={this.state.boardInfo.selection.pieceStandSelectInfo["先手"]}
            onClick={(i) => this.pieceStandClick(this.state.boardInfo.pieceStand["先手"][i])}
          />
        </div>
      </div>
    );
  }
}

// ========================================

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game />);
```

盤面の情報はcomponents/BoardInfo.jsにまとめています。

constructorで初期画面を生成しています。

```javascript:プロジェクトディレクトリ/src/components/BoardInfo.js
import { Piece, Blank, King, Rook, Bishop, GoldGeneral, SilverGeneral, Knight, Lance, Pawn } from './Pieces';

class BoardInfo {

    constructor() {
        this.turn = "先手";
        this.board = [[new Lance("後手"), new Knight("後手"), new SilverGeneral("後手"), new GoldGeneral("後手"), new King("後手"), new GoldGeneral("後手"), new SilverGeneral("後手"), new Knight("後手"), new Lance("後手")],
        [new Blank(), new Rook("後手"), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Bishop("後手"), new Blank()],
        [new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手"), new Pawn("後手")],
        [new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank()],
        [new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank()],
        [new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank()],
        [new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手"), new Pawn("先手")],
        [new Blank(), new Bishop("先手"), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Rook("先手"), new Blank()],
        [new Lance("先手"), new Knight("先手"), new SilverGeneral("先手"), new GoldGeneral("先手"), new King("先手"), new GoldGeneral("先手"), new SilverGeneral("先手"), new Knight("先手"), new Lance("先手")]
        ];
        this.selection = new Selection();
        this.pieceStandNum = {
            "先手": { "歩": 0, "香": 0, "桂": 0, "銀": 0, "金": 0, "角": 0, "飛": 0 },
            "後手": { "歩": 0, "香": 0, "桂": 0, "銀": 0, "金": 0, "角": 0, "飛": 0 }
        };
        this.pieceStand = {
            "先手": [new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank()],
            "後手": [new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank(), new Blank()]
        };
    }

    boardClick(i, j) {
        if (this.selection.state) {
            if (this.selection.boardSelectInfo[i][j] !== "配置可能") {
                return;
            }
            let myPiece;
            if (this.selection.pieceStandPiece.name) {
                myPiece = this.selection.pieceStandPiece;
                this.pieceStandNum[this.turn][myPiece.name] -= 1;
                this.makePieceStand();
            } else {
                myPiece = this.board[this.selection.before_i][this.selection.before_j];
                this.board[this.selection.before_i][this.selection.before_j] = new Blank();
                let yourPiece = this.board[i][j];
                if (yourPiece.name) {
                    if (yourPiece.getPiece()) {
                        yourPiece = yourPiece.getPiece();
                    }
                    this.pieceStandNum[myPiece.owner][yourPiece.name] += 1;
                    this.makePieceStand();
                }
                if (this.existCanMove(i, j, myPiece)) {
                    myPiece = this.checkPromote(myPiece, i, this.selection.before_i);
                } else {
                    myPiece = myPiece.getPromotedPiece();
                }
            }
            this.board[i][j] = myPiece;
            this.turn = this.turn === "先手" ? "後手" : "先手";
        } else {
            if (this.turn !== this.board[i][j].owner) {
                return;
            }
            this.selection.isNow = true;
            this.selection.state = true;
            this.selection.before_i = i;
            this.selection.before_j = j;
            this.selection.boardSelectInfo = JSON.parse(JSON.stringify((new Array(9)).fill((new Array(9)).fill("未選択"))));
            this.selection.pieceStandSelectInfo = {
                "先手": Array(9).fill("未選択"),
                "後手": Array(9).fill("未選択")
            };
            this.selection.boardSelectInfo[i][j] = "選択状態";
            this.checkCanPutBoard(i, j);
        }
    }

    existCanMove(i, j, piece) {
        for (let l = 0; l < piece.dx.length; l++) {
            let y = i;
            let x = j;
            y += this.turn === "先手" ? piece.dy[l] : -piece.dy[l];
            x += this.turn === "先手" ? piece.dx[l] : -piece.dx[l];
            if (0 <= y && y <= 8 && 0 <= x && x <= 8) {
                return true;
            }
        }
        return false;
    }

    checkPromote(piece, i, before_i) {
        if (!piece.getPromotedPiece()) {
            return piece;
        }
        const promoteAreaMinY = piece.owner === "先手" ? 0 : 6;
        const promoteAreaMaxY = piece.owner === "先手" ? 2 : 8;
        if ((promoteAreaMinY <= i && i <= promoteAreaMaxY) || (promoteAreaMinY <= before_i && before_i <= promoteAreaMaxY)) {
            if (window.confirm('成りますか？')) {
                return piece.getPromotedPiece()
            }
        }
        return piece;
    }

    checkCanPutBoard(i, j) {
        const piece = this.board[i][j];
        for (let l = 0; l < piece.dx.length; l++) {
            let y = i;
            let x = j;
            for (let _ = 0; _ < piece.dk[l]; _++) {
                y += this.turn === "先手" ? piece.dy[l] : -piece.dy[l];
                x += this.turn === "先手" ? piece.dx[l] : -piece.dx[l];
                if (y < 0 || y > 8 || x < 0 || x > 8 || this.board[y][x].owner === piece.owner) {
                    break;
                }
                this.selection.boardSelectInfo[y][x] = "配置可能";
                if (!this.board[y][x].owner) {
                    continue;
                }
                break;
            }
        }
    }

    pieceStandClick(piece) {
        if (this.selection.state || this.turn !== piece.owner) {
            return;
        }
        this.selection.isNow = true;
        this.selection.state = true;
        this.selection.boardSelectInfo = JSON.parse(JSON.stringify((new Array(9)).fill((new Array(9)).fill("未選択"))));
        this.selection.pieceStandPiece = piece;
        this.selection.pieceStandSelectInfo = {
            "先手": Array(9).fill("未選択"),
            "後手": Array(9).fill("未選択")
        };
        const i = this.pieceStand[piece.owner].findIndex(p => p.name === piece.name);
        this.selection.pieceStandSelectInfo[this.turn][i] = "選択状態";
        this.checkCanPutPieceStand(piece);
    }

    makePieceStand() {
        let myPieceStand = [];
        const myPieceStandNum = this.pieceStandNum[this.turn];
        for (let name in myPieceStandNum) {
            if (myPieceStandNum[name] > 0) {
                myPieceStand.push(Piece.getPieceByName(name, this.turn));
            }
        }
        while (myPieceStand.length < 9) {
            myPieceStand.push(new Blank());
        }
        this.pieceStand[this.turn] = myPieceStand;
    }

    checkCanPutPieceStand(piece) {
        let pawnColMemo = Array(9).fill(true);
        if (piece.name === "歩") {
            for (let i = 0; i < 9; i++) {
                for (let j = 0; j < 9; j++) {
                    if (this.board[i][j].name === "歩" && this.board[i][j].owner === piece.owner) {
                        pawnColMemo[j] = false;
                    }
                }
            }
        }
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                if (!this.board[i][j].owner && this.existCanMove(i, j, piece) && pawnColMemo[j]) {
                    this.selection.boardSelectInfo[i][j] = "配置可能";
                }
            }
        }
    }

}

class Selection {
    boardSelectInfo = JSON.parse(JSON.stringify((new Array(9)).fill((new Array(9)).fill(""))));
    isNow = false;
    state = false;
    before_i = null;
    before_j = null;
    pieceStandSelectInfo = {
        "先手": Array(9).fill("持駒"),
        "後手": Array(9).fill("持駒")
    };
    pieceStandPiece = new Blank();
}

export { BoardInfo, Selection };
```

各駒の動き方や名前、成った後の駒、成る前の駒などをcomponents/Pieces.jsでまとめています。

各駒がPieceクラスを継承しており、constructorの引数でowner(先手 or 後手)を渡します。

```javascript:プロジェクトディレクトリ/src/components/Pieces.js
class Piece {
    constructor(owner) {
        this.owner = owner;
    }
    getPiece() {
        return null;
    }
    getPromotedPiece() {
        return null;
    }
    static getPieceByName(name, owner) {
        switch (name) {
            case "飛":
                return new Rook(owner);
            case "角":
                return new Bishop(owner);
            case "金":
                return new GoldGeneral(owner);
            case "銀":
                return new SilverGeneral(owner);
            case "桂":
                return new Knight(owner);
            case "香":
                return new Lance(owner);
            case "歩":
                return new Pawn(owner);
            default:
                return null;
        }
    }
}

class Blank extends Piece {
}

class King extends Piece {
    name = "玉";
    dx = [-1, -1, -1, 0, 1, 1, 1, 0];
    dy = [-1, 0, 1, 1, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1, 1, 1];
}

class Rook extends Piece {
    name = "飛";
    dx = [-1, 0, 1, 0];
    dy = [0, 1, 0, -1];
    dk = [10, 10, 10, 10];
    getPromotedPiece() {
        return new PromotedRook(this.owner);
    }
}

class Bishop extends Piece {
    name = "角";
    dx = [-1, -1, 1, 1];
    dy = [-1, 1, 1, -1];
    dk = [10, 10, 10, 10];
    getPromotedPiece() {
        return new PromotedBishop(this.owner);
    }
}

class GoldGeneral extends Piece {
    name = "金";
    dx = [-1, -1, 0, 1, 1, 0];
    dy = [-1, 0, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1];
}

class SilverGeneral extends Piece {
    name = "銀";
    dx = [-1, -1, 1, 1, 0];
    dy = [-1, 1, 1, -1, -1];
    dk = [1, 1, 1, 1, 1];
    getPromotedPiece() {
        return new PromotedSilverGeneral(this.owner);
    }
}

class Knight extends Piece {
    name = "桂";
    dx = [-1, 1];
    dy = [-2, -2];
    dk = [1, 1];
    getPromotedPiece() {
        return new PromotedKnight(this.owner);
    }
}

class Lance extends Piece {
    name = "香";
    dx = [0];
    dy = [-1];
    dk = [10];
    getPromotedPiece() {
        return new PromotedLance(this.owner);
    }
}

class Pawn extends Piece {
    name = "歩";
    dx = [0];
    dy = [-1];
    dk = [1];
    getPromotedPiece() {
        return new PromotedPawn(this.owner);
    }
}

class PromotedRook extends Piece {
    name = "竜";
    dx = [-1, 0, 1, 0, -1, -1, 1, 1];
    dy = [0, 1, 0, -1, -1, 1, 1, -1];
    dk = [10, 10, 10, 10, 1, 1, 1, 1];
    getPiece() {
        return new Rook(this.owner);
    }
}

class PromotedBishop extends Piece {
    name = "馬";
    dx = [-1, -1, 1, 1, -1, 0, 1, 0];
    dy = [-1, 1, 1, -1, 0, 1, 0, -1];
    dk = [10, 10, 10, 10, 1, 1, 1, 1];
    getPiece() {
        return new Bishop(this.owner);
    }
}

class PromotedSilverGeneral extends Piece {
    name = "成銀";
    dx = [-1, -1, 0, 1, 1, 0];
    dy = [-1, 0, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1];
    getPiece() {
        return new SilverGeneral(this.owner);
    }
}

class PromotedKnight extends Piece {
    name = "成桂";
    dx = [-1, -1, 0, 1, 1, 0];
    dy = [-1, 0, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1];
    getPiece() {
        return new Knight(this.owner);
    }
}

class PromotedLance extends Piece {
    name = "成香";
    dx = [-1, -1, 0, 1, 1, 0];
    dy = [-1, 0, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1];
    getPiece() {
        return new Lance(this.owner);
    }
}

class PromotedPawn extends Piece {
    name = "と";
    dx = [-1, -1, 0, 1, 1, 0];
    dy = [-1, 0, 1, 0, -1, -1];
    dk = [1, 1, 1, 1, 1, 1];
    getPiece() {
        return new Pawn(this.owner);
    }
}

export { Piece, Blank, King, Rook, Bishop, GoldGeneral, SilverGeneral, Knight, Lance, Pawn};
```

index.cssで見た目を将棋盤に整えます。

後手の駒か、持ち駒か、選択状態にあるか、配置可能なマス目かなどで色を変えています。

```CSS:プロジェクトディレクトリ/src/index.css
body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.square {
  position: relative;
  background: #FFCC00;
  border: 1px solid #999;
  float: left;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  width: 34px;
}

.square p {
  margin-top: 1px;
  color: yellow;
  position: absolute;
  top: 0;
  left: 0;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

#未選択.square {
  background: rgba(0, 0, 0, .5);
}

#選択状態.square {
  background: red;
}

#配置可能.square {
  background: yellow;
}

#持駒.square {
  background: darkgoldenrod;
}

img {
  width: 28px;
}

img#後手 {
  transform: scale(-1, -1);
}
```

## ソース配置

srcフォルダ内にcomponentsディレクトリとimgディレクトリが含まれ、imgディレクトリに各駒の画像を入れています。

この画像はフリー素材の駒の画像をサイズが同じに成るように揃え、背景を透明にしたものです。

<img width="120" alt="スクリーンショット 2022-12-24 17.46.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/702c575a-1b72-72f2-ff4b-28a975082038.png">

![玉.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/30d44367-cd1c-9497-3c05-4d858f5860a7.png)![飛.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/9bc2bf1a-975e-d86a-36cd-47873e06d05c.png)![角.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/0b5933c3-e9ef-d98d-cde2-ca4a64b579be.png)![金.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/9c28a23d-43b8-0482-3916-9db67d09b850.png)![銀.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/6f1a82ef-9b1d-a247-38ea-162121b0e378.png)![桂.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/5f86b29d-76f3-6280-c989-ab51e722a779.png)![香.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/5a6bc4cc-c782-1cfd-7a41-749fb966cbf2.png)
![歩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/4095ea0a-1473-462a-53ca-2673fd055936.png)![竜.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/0638a0f7-71fc-97ee-3aad-07939cac3d07.png)![馬.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/31adf13e-17d9-10e6-6e81-9f0f56c86204.png)![成銀.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/3993f5ae-fc6c-f119-a02e-93fca500dd65.png)![成桂.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/2a9dd052-2d4c-a233-fa50-19f15a25ac26.png)![成香.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/35b4fa43-48d1-2dba-611e-1d9d2f0873e2.png)![と.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/57fa9fc8-2062-1b9a-f7ac-3ab0d1756ff2.png)

## 完成後の動作

上記ソースコードを配置すると以下のように動作します。

![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342507/0f26b02e-3963-48df-656e-54bb8f7d27bd.gif)

## 移動可能なマス目の表示

将棋のゲームを作ろうと思った際に最初にぶつかる壁は、駒の種類と移動のパターンの多さだと思います。

歩は前に一回進めますが、角は斜めにずっと進めます。

```javascript:プロジェクトディレクトリ/src/components/Pieces.js - class Pawn
class Pawn extends Piece {
    name = "歩";
    dx = [0];
    dy = [-1];
    dk = [1];
    getPromotedPiece() {
        return new PromotedPawn(this.owner);
    }
}
```

```javascript:プロジェクトディレクトリ/src/components/Pieces.js - class Bishop
class Bishop extends Piece {
    name = "角";
    dx = [-1, -1, 1, 1];
    dy = [-1, 1, 1, -1];
    dk = [10, 10, 10, 10];
    getPromotedPiece() {
        return new PromotedBishop(this.owner);
    }
}
```

dxの長さ分for文を回し、現在の座標からdx, dyぶん進みそれをdk回繰り返します。

(二次元配列と同じなので、yは下向きが正)

駒がそれ以上進めない条件は以下です。

1.盤の外に出たらそれ以上進めない。
2.自分の駒があるときそのマスには進めず、それ以上進めない。
3.相手の駒がある時、相手の駒をとりそのマスに進める。それ以降は進めない。

これらを用いて、移動可能なマスは以下のように表せます。

```javascript:プロジェクトディレクトリ/src/components/BoardInfo.js - checkCanPutBoard(i, j)
checkCanPutBoard(i, j) {
    const piece = this.board[i][j];
    for (let l = 0; l < piece.dx.length; l++) {
        let y = i;
        let x = j;
        for (let _ = 0; _ < piece.dk[l]; _++) {
            y += this.turn === "先手" ? piece.dy[l] : -piece.dy[l];
            x += this.turn === "先手" ? piece.dx[l] : -piece.dx[l];
            if (y < 0 || y > 8 || x < 0 || x > 8 || this.board[y][x].owner === piece.owner) {
                break;
            }
            this.selection.boardSelectInfo[y][x] = "配置可能";
            if (!this.board[y][x].owner) {
                continue;
            }
            break;
        }
    }
}
```

## 全体的な処理の流れ

このゲームには3つのイベント処理が入ってます。

1.canselSelection
2.pieceStandClick
3.boardClick

```javascript:プロジェクトディレクトリ/src/index.js - class Game - render()
render() {
  return (
    <div className="game" onClick={() => this.canselSelection()}>
      <div className="game-board">
        <PieceStand
          pieceStand={this.state.boardInfo.pieceStand["後手"]}
          pieceStandNum={this.state.boardInfo.pieceStandNum["後手"]}
          pieceStandSelectInfo={this.state.boardInfo.selection.pieceStandSelectInfo["後手"]}
          onClick={(i) => this.pieceStandClick(this.state.boardInfo.pieceStand["後手"][i])}
        />
        <br />
        <Board
          board={this.state.boardInfo.board}
          boardSelectInfo={this.state.boardInfo.selection.boardSelectInfo}
          onClick={(i, j) => this.boardClick(i, j)}
        />
        <br />
        <PieceStand
          pieceStand={this.state.boardInfo.pieceStand["先手"]}
          pieceStandNum={this.state.boardInfo.pieceStandNum["先手"]}
          pieceStandSelectInfo={this.state.boardInfo.selection.pieceStandSelectInfo["先手"]}
          onClick={(i) => this.pieceStandClick(this.state.boardInfo.pieceStand["先手"][i])}
        />
      </div>
    </div>
  );
}
```

ReactのonClickイベントは、小要素から親要素に伝播していきます。

一番親要素のclassName="game"のcanselSelectionは、盤面をクリックした時も、持ち駒をクリックした時も、またそれ以外の場所をクリックした時も呼ばれることになります。

```javascript:プロジェクトディレクトリ/src/index.js - class Game
canselSelection() {
  const nextBoardInfo = this.state.boardInfo;
  if (nextBoardInfo.selection.isNow) {
    nextBoardInfo.selection.isNow = false;
  } else {
    nextBoardInfo.selection = new Selection();
  }
  this.setState({
    boardInfo: nextBoardInfo
  });
}
boardClick(i, j) {
  this.state.boardInfo.boardClick(i, j);
}
pieceStandClick(piece) {
  this.state.boardInfo.pieceStandClick(piece);
}
```

これは、盤面の駒、または持ち駒の駒を選択したときに盤面をグレーにしていますが、次どこかをクリックした時、必ず選択した状態を初期化し、色を元の状態に戻すためです。(nextBoardInfo.selection = new Selection();)

boardClick(盤面をクリック)、pieceStandClick(持ち駒をクリック)した時は、this.state.boardInfoの中身が次の状態に書き換えられますが、これはまだstateにsetされていません。直後に呼ばれるcanselSelectionのsetStateによってstate内のboardInfoの情報がnextBoardInfoの情報に書き換えられます。

## 最後に

ここまで読んでいただきありがとうございました。

Reactで作る将棋ゲームについて書かせていただきましたが、細かいルールなどに関しては実装できていない部分も多いと思います。

Reactチュートリアルと同じように棋譜を残すようにしたり、詰みの判定を入れるなど、まだできることは多くあると思います。

自分もReactの知識はまだまだ浅い部分があるので、改善できるところも多くあるかもしれないです。

少しでも興味を持っていただいた部分や、参考になった部分があったと思っていただけなら幸いです。

今日はクリスマスですがいかがお過ごしでしょうか:santa_tone1:

来年も良いお年を!