【React】迷路を生成する

CubeとPlaneを用いて、迷路を生成する。

App.tsx

import React from 'react';
import { Canvas } from '@react-three/fiber';
import { PerspectiveCamera, OrbitControls } from '@react-three/drei';

import GlobalStyle from './components/GlobalStyle';
import Container from './components/Container';
import Plane from './components/Plane';
import Cube from './components/Cube';
import Maze from './components/maze';

const App = () => {
  const width: number = window.innerWidth;
  const height: number = window.innerHeight;
  
  const maze1 = new Maze(15, 15);
  maze1.set_maze_boutaoshi();
  
  return (
    <React.Fragment>
      <GlobalStyle />
      <Container>
        <Canvas>
          <PerspectiveCamera makeDefault fov={60} aspect={width/height} position={[45,45,45]} />
          <Plane />
          {
            maze1.maze.map((row: number[] | string[], y: number) => row.map((cell: number | string, x: number) => {
            if (cell === 1) {
              return <Cube key={(15*y+x).toString()} position={[x+0.5,0.5,y+0.5]} />;
            } else {
              return null;
            }}))
          }
          <axesHelper args={[35]} />
          <OrbitControls enablePan={false} enableZoom={false} enableRotate={false} />
        </Canvas>
      </Container>
    </React.Fragment>
  );
}

export default App;

GlobalStyle.tsx

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
  }
`;

export default GlobalStyle;

Container.tsx

import styled from 'styled-components';

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  background-color: black;
`;

export default Container;

Plane.tsx

import React from 'react';

const Plane: React.FC = () => {
  return (
    <mesh position={[7.5,0,7.5]} rotation={[-Math.PI/2,0,0]}>
      <planeGeometry attach="geometry" args={[15,15]} />
      <meshBasicMaterial attach="material" />
    </mesh>
  );
}
    
export default Plane;

Cube.tsx

import React from 'react';

type Props = {
  position?: [x: number, y: number, z: number];
}

const Cube: React.FC<Props> = ({ position = [0.5,0.5,0.5] }) => {
  return (
    <mesh position={position} >
      <boxGeometry attach="geometry" args={[1,1,1]} />
      <meshNormalMaterial attach="material" />
    </mesh>
  );
}
	
export default Cube;

maze.ts

import Chance from 'chance';

type maze = number[][] | string[][];
type binary = 0 | 1;

class Maze {
  PATH: number;
  WALL: number;
  width: number;
  height: number;
  maze: maze = [];
  dist: number[][] = [];
  start: number[] = [];
  goal: number[] = [];
  chance: any = new Chance();

  constructor(width: number, height: number, seed: number = 0) {
    this.PATH = 0;
    this.WALL = 1;
    this.width = width;
    this.height = height;
    if (this.width < 5 || this.height < 5) {
      return;
    }
    if (this.width%2 === 0) {
      this.width++;
    }
    if (this.height%2 === 0) {
      this.height++;
    }
    this.maze = [...Array(this.height)].map(() => Array(this.width).fill(0));
    this.dist = [...Array(this.height)].map(() => Array(this.width).fill(-1));
    this.start = [1, 1];
    this.goal = [this.width-2, this.height-2];
    this.chance = new Chance(seed);
  }

  set_outer_wall(): maze {
    for (let y = 0; y < this.height; y++) {
      for (let x = 0; x < this.width; x++) {
        if (x === 0 || y === 0 || x === this.width-1 || y === this.height-1) {
          this.maze[y][x] = this.WALL;
        }
      }
    }
    return this.maze;
  }

  set_inner_wall(): maze {
    for (let y = 2; y <= this.height-3; y+=2) {
      for (let x = 2; x <= this.width-3; x+=2) {
        this.maze[y][x] = this.WALL;
      }
    }
    return this.maze;
  }

  set_maze_boutaoshi(): maze {
    let cell_x: number, cell_y: number, direction: number;
    this.set_outer_wall();
    this.set_inner_wall();
    for (let y = 2; y <= this.height-3; y+=2) {
      for (let x = 2; x <= this.width-3; x+=2) {
        while (true) {
          cell_x = x;
          cell_y = y;
          if (y === 2) {
            direction = Math.floor(this.chance.random()*4);
          } else {
            direction = Math.floor(this.chance.random()*3);
          }
          if (direction === 0) {
            cell_x += 1;
          } else if (direction === 1) {
            cell_y += 1;
          } else if (direction === 2) {
            cell_x -= 1;
          } else if (direction === 3) {
            cell_y -= 1;
          }
          if (this.maze[cell_y][cell_x] !== this.WALL) {
            this.maze[cell_y][cell_x] = this.WALL;
            break;
          }
        }
      }
    }
    return this.maze;
  }

  set_start_goal(start: number[], goal: number[]): maze {
    if (this.maze[start[1]][start[0]] === this.PATH) {
      this.start = start;
    }
    if (this.maze[goal[1]][goal[0]] === this.PATH) {
      this.goal = goal;
    }
    return this.maze;
  }

  set_dist_bfs(flag: boolean | binary = false): number[][] {
    let queue: number[][] = [], point: number[];
    this.dist[this.start[1]][this.start[0]] = 0;
    queue.push(this.start);
    while (queue.length > 0) {
      point = queue.shift()!;
      for (let x of [[0,-1],[1,0],[0,1],[-1,0]]) {
        if (this.maze[point[1]+x[1]][point[0]+x[0]] === 0 && this.dist[point[1]+x[1]][point[0]+x[0]] === -1) {
          this.dist[point[1]+x[1]][point[0]+x[0]] = this.dist[point[1]][point[0]] + 1;
          queue.push([point[0]+x[0],point[1]+x[1]]);
        }
        if (flag !== true && flag !== 1) {
          if (point[0]+x[0] === this.goal[0] && point[1]+x[1] === this.goal[1]) {
            queue = [];
            break;
          }
        }
      }
    }
    return this.dist;
  }

  set_dist_dfs(flag: boolean | binary = false): number[][] {
    let stack: number[][] = [], point: number[];
    this.dist[this.start[1]][this.start[0]] = 0;
    stack.push(this.start);
    while (stack.length > 0) {
      point = stack.pop()!;
      for (let x of [[0,-1],[1,0],[0,1],[-1,0]]) {
        if (this.maze[point[1]+x[1]][point[0]+x[0]] === 0 && this.dist[point[1]+x[1]][point[0]+x[0]] === -1) {
          this.dist[point[1]+x[1]][point[0]+x[0]] = this.dist[point[1]][point[0]] + 1;
          stack.push([point[0]+x[0],point[1]+x[1]]);
        }
        if (flag !== true && flag !== 1) {
          if (point[0]+x[0] === this.goal[0] && point[1]+x[1] === this.goal[1]) {
            stack = [];
            break;
          }
        }
      }
    }
    return this.dist;
  }

  set_shortest_path(): maze {
    let point: number[] = this.goal;
    const x: number[][] = [[0,-1],[1,0],[0,1],[-1,0]];
    this.maze[point[1]][point[0]] = '*';
    while (this.dist[point[1]][point[0]] > 0) {
      for (let i = 0; i < x.length; i++) {
        if (this.dist[point[1]][point[0]]-this.dist[point[1]+x[i][1]][point[0]+x[i][0]] === 1) {
          if (this.dist[point[1]][point[0]] > 0) {
            this.maze[point[1]+x[i][1]][point[0]+x[i][0]] = '*';
            point = [point[0]+x[i][0],point[1]+x[i][1]];
          }
        }
      }
    }
    return this.maze;
  }

  print_maze(): void {
    let arr: string;
    this.maze[this.start[1]][this.start[0]] = 'S';
    this.maze[this.goal[1]][this.goal[0]] = 'G';
    for (let row of this.maze) {
      arr = '';
      for (let cell of row) {
        if (cell === this.WALL) {
          arr += '#';
        } else if (cell === this.PATH) {
          arr += ' ';
        } else if (cell === 'S') {
	  arr += 'S';
	} else if (cell === 'G') {
	  arr += 'G';
	} else if (cell === '*') {
	  arr += '*';
	}
      }
      console.log(arr);
    }
  }

  print_dist(): void {
    let arr: string;
    for (let row of this.dist) {
      arr = '';
      for (let cell of row) {
        if (cell === -1) {
          if (Math.max.apply(null, Array.prototype.concat.apply([], this.dist)) === -1) {
            arr += '#';
          } else {
            for (let i = 0; i < String(Math.max.apply(null, Array.prototype.concat.apply([], this.dist))).length; i++) {
              arr += '#';
            }
          }
        } else {
          if (String(cell).length === String(Math.max.apply(null, Array.prototype.concat.apply([], this.dist))).length) {
            arr += String(cell);
          } else {
            for (let i = 0; i < String(Math.max.apply(null, Array.prototype.concat.apply([], this.dist))).length-String(cell).length; i++) {
              arr += ' ';
            }
            arr += String(cell);
          }
        }
      }
      console.log(arr);
    }
  }
}

export default Maze;

今回は、以下のように出力される。

タイトルとURLをコピーしました