迷路のサイズと Cube の大きさに合わせて迷路の床の大きさを調整する。
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 cube_size = 2;
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 size={cube_size} />
{
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={[cube_size*(x+0.5),cube_size*0.5,cube_size*(y+0.5)]} size={cube_size} />;
} 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';
type Props = {
size?: number;
}
const Plane: React.FC<Props> = ({ size = 1 }) => {
return (
<mesh position={[size*7.5,0,size*7.5]} rotation={[-Math.PI/2,0,0]}>
<planeGeometry attach="geometry" args={[size*15,size*15]} />
<meshBasicMaterial attach="material" />
</mesh>
);
}
export default Plane;
Cube.tsx
import React from 'react';
type Props = {
position?: [x: number, y: number, z: number];
size?: number;
}
const Cube: React.FC<Props> = ({ position = [0.5,0.5,0.5], size = 1 }) => {
return (
<mesh position={position} >
<boxGeometry attach="geometry" args={[size,size,size]} />
<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;
今回は、以下のように出力される。