こんにちはRockinWoolです。最近は頭痛も落ち着いていろいろなことに打ち込めるようになってきました。人生山あり谷ありですが、今は山なんだなあと感じています。
さて、今日からはPythonを使ったブラウザアプリを作って行こうと思います。まずはアプリの構成ですが、ChatGPTに質問したら以下のような回答が帰ってきました。

なるほどなるほど。それでは今回はReact.jsを使用してフロントエンドを作って、バックエンドはFlaskという構成でやっていこうと思います。

node.jsのインストール

React.jsのインストールをするためには、前提としてnodejsのインストールが必要とのことでした。なので、nodejsを入れていこうと思います。今回の環境はUbuntu20.04です。まずは以下のコマンドを実行します。

sudo apt update
sudo apt install nodejs npm
sudo npm -g install n
sudo n stable
sudo apt purge nodejs npm
sudo apt autoremove

ここまで実行すればnodeコマンドやnpmコマンドが実行できるようになると別のサイトには書いてあったのですが、自分の環境では以下のエラーが出ました。

$ node --version
bash: /usr/bin/node: そのようなファイルやディレクトリはありません

第一感では、このエラーはPATHの通っていないところにnodeがインストールされた結果発生しているのでは無いかと考えられました。なので、地道にPATHが充分か調査してみます。

$ which node
/usr/local/bin/node

このコマンドでnodeが存在していることは確定しました。次はnodeのinstallに使ったnの位置です。

n which stable
/usr/local/n/versions/node/22.12.0/bin/node

こちらにもnodeがいることがわかります。ただし、こちらにある方が本物です。というのもsudo apt install nodejsでインストールできるnodeはversion10と非常に古いのでnコマンド経由で22.12.0をインストールするようにしたのが最初のコマンド群でした。ということで、こちらをPATHに追加していきます。以下のコマンドを.bashrcとターミナルの両方に打ち込んでPATHを登録します。

export PATH="/usr/local/n/versions/node/22.12.0/bin/node:$PATH"

プロジェクトの作成

React.jsを使ったアプリの作成は以下のコマンドで実行します。

npx create-react-app maze_game --force

ここで–forceオプションをつけるのはreactのバージョンによってはうまく行かない依存関係が発生してしまうためです。このアプリの名前はmaze_gameとしました。迷路ゲームを作ろうと思っているので。
さて、ここで`cd maze_game`してnpm startでアプリを起動!のつもりがまだ依存が解決していないとかで怒られました。私の場合は以下のコマンドで解決できました。

npm install ajv@6 ajv-keywords@3

迷路部分の実装

ここから先はあまり詳しくはないので、いろいろ調査しながら作りました。
まずnpm startを実行した際の挙動としては

  1. react-scripts startが実行
  2. src/index.jsの読み込み
  3. index.jsがApp.jsをインポート

というステップを踏んでいます。したがってApp.jsに具体的にフロントエンドで実装したいことを書いていきます。ただし、直接App.jsに書くとsrcディレクトリの中が汚れそうだったのでsrc/componentsディレクトリを作って、その中にMaze.jsという本体を書いていくことにしました。したがってApp.jsとMaze.jsの中身はこんな感じになります。

App.js

import React from 'react';
import Maze from './components/Maze';

function App() {
  return (
    <div className="App">
      <h1>Maze Game</h1>
      <Maze />
    </div>
  );
}

export default App;

Maze.js

import React, { useState } from 'react';
import './Maze.css';

const initialMaze = [
  [1, 1, 1, 1, 1],
  [1, 0, 0, 0, 1],
  [1, 0, 1, 0, 1],
  [1, 0, 0, 0, 1],
  [1, 1, 1, 1, 1]
];

const Maze = () => {
  const [playerPos, setPlayerPos] = useState([1, 1]);

  const handleKeyDown = (e) => {
    const [x, y] = playerPos;
    let newPos = [...playerPos];

    switch (e.key) {
      case 'ArrowUp':
        if (initialMaze[x - 1][y] === 0) newPos = [x - 1, y];
        break;
      case 'ArrowDown':
        if (initialMaze[x + 1][y] === 0) newPos = [x + 1, y];
        break;
      case 'ArrowLeft':
        if (initialMaze[x][y - 1] === 0) newPos = [x, y - 1];
        break;
      case 'ArrowRight':
        if (initialMaze[x][y + 1] === 0) newPos = [x, y + 1];
        break;
      default:
        break;
    }
    setPlayerPos(newPos);
  };

  return (
    <div className="maze" tabIndex="0" onKeyDown={handleKeyDown}>
      {initialMaze.map((row, rowIndex) => (
        <div className="row" key={rowIndex}>
          {row.map((cell, colIndex) => (
            <div
              className={`cell ${
                playerPos[0] === rowIndex && playerPos[1] === colIndex
                  ? 'player'
                  : cell === 1
                  ? 'wall'
                  : 'path'
              }`}
              key={colIndex}
            ></div>
          ))}
        </div>
      ))}
    </div>
  );
};

export default Maze;

App.jsはMaze.jsを呼び出しているだけなので、今回はそんなに重要ではないです。本題となるのはMaze.jsの方で、ここには大きく分けて3つの要素が含まれています。

  • 迷宮の地図
  • キーボードを入力した際の動き
  • 迷路とプレイヤーの描画

迷宮の地図

const initialMaze = [
  [1, 1, 1, 1, 1],
  [1, 0, 0, 0, 1],
  [1, 0, 1, 0, 1],
  [1, 0, 0, 0, 1],
  [1, 1, 1, 1, 1]
];

キーボードを入力した際の動き

const Maze = () => {
  const [playerPos, setPlayerPos] = useState([1, 1]);

  const handleKeyDown = (e) => {
    const [x, y] = playerPos;
    let newPos = [...playerPos];

    switch (e.key) {
      case 'ArrowUp':
        if (initialMaze[x - 1][y] === 0) newPos = [x - 1, y];
        break;
      case 'ArrowDown':
        if (initialMaze[x + 1][y] === 0) newPos = [x + 1, y];
        break;
      case 'ArrowLeft':
        if (initialMaze[x][y - 1] === 0) newPos = [x, y - 1];
        break;
      case 'ArrowRight':
        if (initialMaze[x][y + 1] === 0) newPos = [x, y + 1];
        break;
      default:
        break;
    }
    setPlayerPos(newPos);
  };

最初は1.1の座標からスタートすることを言っています。handleKeyDownはキーボード入力を拾っています。それからplayerPosを直接更新することはせずに一回tempとしてnewPosを定義しています。let newPos = [..playerPos]の部分がそれで、playerPos=newPosとするとplayerPos[0]=2としたときにnewPos[0]も2になってしまう問題があるため、PlayerPosの中身(..playerPos)を配列[]で囲むことによって同じ配列を作っています。後は入力e.keyの種類によってx,yを更新してPlayerPosに入れています。

迷路とプレイヤーの描画

正直ここが一番他の言語と違うところだと思いました。描画処理を直接書くなんてあまりないですよね。

<div className="maze" tabIndex="0" onKeyDown={handleKeyDown}>

この部分はHtmlっぽいですが、classNameを定義して、それをtebIndex=0でキーボード入力受付可能にしています。onKeyDownはhandleKeyDownを呼び出すようにしています。これによってキーボード入力して位置が変わるという処理ができています。後は迷路の実際のデザインはMaze.cssに規定します。

Maze.css

.maze {
    display: inline-block;
    outline: none;
  }
  
  .row {
    display: flex;
  }
  
  .cell {
    width: 40px;
    height: 40px;
    border: 1px solid black;
  }
  
  .wall {
    background: black;
  }
  
  .path {
    background: white;
  }
  
  .player {
    background: blue;
  }
  

これで迷宮を描画してキーボード入力によって進むことができるブラウザアプリが作れました。

まとめ#1

ここまででフロントエンド側の実装を一通り終えることができました。
次回はバックエンド側の実装をやっていこうと思います。
強化学習で迷路を脱出するアプリとか作れたら嬉しいですよね。
それではまた次回!ここまで見てくれてありがとうございました!

Leave a Reply

Your email address will not be published. Required fields are marked *