Created
March 15, 2023 16:48
-
-
Save tosik/dfe15fb58d3db34c7ce190ccea49aa94 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const canvas = document.getElementById('game'); | |
const ctx = canvas.getContext('2d'); | |
const tileSize = 10; | |
const numRows = canvas.height / tileSize; | |
const numCols = canvas.width / tileSize; | |
const numEnemies = 5; // お好みの敵の数に設定 | |
const rows = 10; | |
const cols = 10; | |
const cellSize = 32; | |
let grid; | |
function createGrid() { | |
grid = new Array(rows); | |
for (let i = 0; i < rows; i++) { | |
grid[i] = new Array(cols); | |
for (let j = 0; j < cols; j++) { | |
// セルのタイプを表す数値を設定します。例えば、0 は空き、1 は壁など | |
// ここでは、すべてのセルを空き(0)に設定していますが、必要に応じて異なる値を設定できます。 | |
grid[i][j] = 0; | |
} | |
} | |
} | |
// グリッドの初期化 | |
createGrid(); | |
class Player { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
move(dx, dy) { | |
this.x += dx; | |
this.y += dy; | |
} | |
draw() { | |
ctx.fillStyle = 'blue'; | |
ctx.fillRect(this.x * tileSize, this.y * tileSize, tileSize, tileSize); | |
} | |
} | |
const player = new Player(Math.floor(numCols / 2), Math.floor(numRows / 2)); | |
function draw() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
player.draw(); | |
} | |
function handleKeyPress(e) { | |
const key = e.key; | |
let moved = false; | |
if (key === "ArrowUp") { | |
moved = tryMovePlayer(0, -1); | |
} else if (key === "ArrowDown") { | |
moved = tryMovePlayer(0, 1); | |
} else if (key === "ArrowLeft") { | |
moved = tryMovePlayer(-1, 0); | |
} else if (key === "ArrowRight") { | |
moved = tryMovePlayer(1, 0); | |
} | |
if (moved) { | |
updateEnemies(grid); // ここで敵を更新 | |
} | |
} | |
function tryMovePlayer(dx, dy) { | |
const newX = player.x + dx; | |
const newY = player.y + dy; | |
if (canMoveTo(newX, newY)) { | |
player.x = newX; | |
player.y = newY; | |
return true; | |
} | |
return false; | |
} | |
class Cell { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
this.visited = false; | |
this.walls = { | |
top: true, | |
bottom: true, | |
left: true, | |
right: true | |
}; | |
} | |
draw() { | |
ctx.strokeStyle = 'white'; | |
if (this.walls.top) { | |
ctx.beginPath(); | |
ctx.moveTo(this.x * tileSize, this.y * tileSize); | |
ctx.lineTo((this.x + 1) * tileSize, this.y * tileSize); | |
ctx.stroke(); | |
} | |
if (this.walls.bottom) { | |
ctx.beginPath(); | |
ctx.moveTo(this.x * tileSize, (this.y + 1) * tileSize); | |
ctx.lineTo((this.x + 1) * tileSize, (this.y + 1) * tileSize); | |
ctx.stroke(); | |
} | |
if (this.walls.left) { | |
ctx.beginPath(); | |
ctx.moveTo(this.x * tileSize, this.y * tileSize); | |
ctx.lineTo(this.x * tileSize, (this.y + 1) * tileSize); | |
ctx.stroke(); | |
} | |
if (this.walls.right) { | |
ctx.beginPath(); | |
ctx.moveTo((this.x + 1) * tileSize, this.y * tileSize); | |
ctx.lineTo((this.x + 1) * tileSize, (this.y + 1) * tileSize); | |
ctx.stroke(); | |
} | |
} | |
} | |
function createMaze() { | |
const maze = new Array(numRows); | |
for (let i = 0; i < numRows; i++) { | |
maze[i] = new Array(numCols); | |
for (let j = 0; j < numCols; j++) { | |
maze[i][j] = new Cell(j, i); | |
} | |
} | |
return maze; | |
} | |
function removeWall(cell1, cell2) { | |
const dx = cell1.x - cell2.x; | |
const dy = cell1.y - cell2.y; | |
if (dx === 1) { | |
cell1.walls.left = false; | |
cell2.walls.right = false; | |
} else if (dx === -1) { | |
cell1.walls.right = false; | |
cell2.walls.left = false; | |
} | |
if (dy === 1) { | |
cell1.walls.top = false; | |
cell2.walls.bottom = false; | |
} else if (dy === -1) { | |
cell1.walls.bottom = false; | |
cell2.walls.top = false; | |
} | |
} | |
function generateMaze(maze, currentCell) { | |
currentCell.visited = true; | |
while (true) { | |
const neighbors = []; | |
const top = maze[currentCell.y - 1]?.[currentCell.x]; | |
const bottom = maze[currentCell.y + 1]?.[currentCell.x]; | |
const left = maze[currentCell.y]?.[currentCell.x - 1]; | |
const right = maze[currentCell.y]?.[currentCell.x + 1]; | |
if (top && !top.visited) { | |
neighbors.push(top); | |
} | |
if (bottom && !bottom.visited) { | |
neighbors.push(bottom); | |
} | |
if (left && !left.visited) { | |
neighbors.push(left); | |
} | |
if (right && !right.visited) { | |
neighbors.push(right); | |
} | |
if (neighbors.length === 0) { | |
break; | |
} | |
const nextCell = neighbors[Math.floor(Math.random() * neighbors.length)]; | |
removeWall(currentCell, nextCell); | |
generateMaze(maze, nextCell); | |
} | |
} | |
function drawMaze(maze) { | |
for (let y = 0; y < numRows; y++) { | |
for (let x = 0; x < numCols; x++) { | |
if (maze[y][x] === 1) { | |
ctx.fillStyle = 'black'; | |
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize); | |
} | |
} | |
} | |
} | |
let maze = createMaze(); | |
generateMaze(maze, maze[0][0]); | |
function canMoveTo(x, y) { | |
if (x < 0 || x >= numCols || y < 0 || y >= numRows) { | |
return false; | |
} | |
return dungeon[y][x] === 0; | |
} | |
player.move = function (dx, dy) { | |
const newX = this.x + dx; | |
const newY = this.y + dy; | |
if (canMoveTo(newX, newY)) { | |
this.x = newX; | |
this.y = newY; | |
} | |
}; | |
class MinHeap { | |
constructor() { | |
this.heap = [null]; | |
} | |
insert(node) { | |
this.heap.push(node); | |
let currentIndex = this.heap.length - 1; | |
while ( | |
currentIndex > 1 && | |
this.heap[Math.floor(currentIndex / 2)].f > this.heap[currentIndex].f | |
) { | |
[this.heap[Math.floor(currentIndex / 2)], this.heap[currentIndex]] = [ | |
this.heap[currentIndex], | |
this.heap[Math.floor(currentIndex / 2)], | |
]; | |
currentIndex = Math.floor(currentIndex / 2); | |
} | |
} | |
remove() { | |
if (this.heap.length === 1) return null; | |
if (this.heap.length === 2) return this.heap.pop(); | |
const min = this.heap[1]; | |
this.heap[1] = this.heap.pop(); | |
let currentIndex = 1; | |
let leftChildIndex = currentIndex * 2; | |
let rightChildIndex = currentIndex * 2 + 1; | |
while ( | |
(this.heap[leftChildIndex] && | |
this.heap[currentIndex].f > this.heap[leftChildIndex].f) || | |
(this.heap[rightChildIndex] && | |
this.heap[currentIndex].f > this.heap[rightChildIndex].f) | |
) { | |
let smallerChildIndex = | |
this.heap[rightChildIndex] && | |
this.heap[leftChildIndex].f > this.heap[rightChildIndex].f | |
? rightChildIndex | |
: leftChildIndex; | |
[this.heap[currentIndex], this.heap[smallerChildIndex]] = [ | |
this.heap[smallerChildIndex], | |
this.heap[currentIndex], | |
]; | |
currentIndex = smallerChildIndex; | |
leftChildIndex = currentIndex * 2; | |
rightChildIndex = currentIndex * 2 + 1; | |
} | |
return min; | |
} | |
} | |
function coordToString(coord) { | |
return `${coord.x},${coord.y}`; | |
} | |
function heuristic(a, b) { | |
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y); | |
} | |
class PriorityQueue { | |
constructor() { | |
this.elements = []; | |
} | |
insert(item) { | |
this.elements.push(item); | |
this.elements.sort((a, b) => a.f - b.f); | |
} | |
pop() { | |
return this.elements.shift(); | |
} | |
isEmpty() { | |
return this.elements.length === 0; | |
} | |
} | |
function getNeighbors(node, grid) { | |
const neighbors = []; | |
const { x, y } = node; | |
if (x > 0) neighbors.push(grid[y][x - 1]); | |
if (x < grid[0].length - 1) neighbors.push(grid[y][x + 1]); | |
if (y > 0) neighbors.push(grid[y - 1][x]); | |
if (y < grid.length - 1) neighbors.push(grid[y + 1][x]); | |
return neighbors; | |
} | |
function aStarSearch(startNode, endNode, grid) { | |
const openSet = new PriorityQueue(); | |
openSet.insert({ ...startNode, g: 0, f: heuristic(startNode, endNode) }); | |
console.log("Inserted start node:", { ...startNode, g: 0, f: heuristic(startNode, endNode) }); | |
const cameFrom = {}; | |
const gScore = {}; | |
gScore[coordToString(startNode)] = 0; | |
while (openSet.length > 0) { | |
const currentNode = openSet.shift(); | |
console.log("Current node:", currentNode); | |
console.log("Open set:", openSet); | |
if (coordToString(currentNode) === coordToString(endNode)) { | |
const path = reconstructPath(cameFrom, current); | |
console.log("Path found:", path); | |
return path; | |
} | |
const neighbors = getNeighbors(currentNode, grid); | |
console.log("Neighbors:", neighbors); | |
for (const neighbor of neighbors) { | |
const neighborKey = coordToString(neighbor); | |
const tentativeGScore = gScore[coordToString(current)] + 1; | |
if (!(neighborKey in gScore) || tentativeGScore < gScore[neighborKey]) { | |
cameFrom[neighborKey] = current; | |
gScore[neighborKey] = tentativeGScore; | |
openSet.insert({ | |
...neighbor, | |
g: tentativeGScore, | |
f: tentativeGScore + heuristic(neighbor, endNode), | |
}); | |
} | |
} | |
} | |
return null; | |
} | |
class Enemy { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
this.path = []; | |
} | |
draw() { | |
ctx.fillStyle = 'red'; | |
ctx.fillRect(this.x * tileSize, this.y * tileSize, tileSize, tileSize); | |
} | |
update(grid, player) { | |
console.log("Updating enemy position"); | |
const start = { x: this.x, y: this.y }; | |
const goal = { x: player.x, y: player.y }; | |
const path = aStarSearch(start, goal, grid); | |
if (path && path.length > 1) { | |
this.x = path[1].x; | |
this.y = path[1].y; | |
} | |
} | |
} | |
const ROOM_MIN_SIZE = 3; | |
const ROOM_MAX_SIZE = 9; | |
const MAX_ROOMS = 15; | |
function createDungeon() { | |
const dungeon = new Array(numRows); | |
for (let i = 0; i < numRows; i++) { | |
dungeon[i] = new Array(numCols).fill(1); | |
} | |
return dungeon; | |
} | |
function createRoom() { | |
const width = Math.floor(Math.random() * (ROOM_MAX_SIZE - ROOM_MIN_SIZE + 1)) + ROOM_MIN_SIZE; | |
const height = Math.floor(Math.random() * (ROOM_MAX_SIZE - ROOM_MIN_SIZE + 1)) + ROOM_MIN_SIZE; | |
const x = Math.floor(Math.random() * (numCols - width - 1)) + 1; | |
const y = Math.floor(Math.random() * (numRows - height - 1)) + 1; | |
return { x, y, width, height }; | |
} | |
function isRoomOverlap(roomA, roomB) { | |
return ( | |
roomA.x < roomB.x + roomB.width + 1 && | |
roomA.x + roomA.width + 1 > roomB.x && | |
roomA.y < roomB.y + roomB.height + 1 && | |
roomA.y + roomA.height + 1 > roomB.y | |
); | |
} | |
function carveRoom(dungeon, room) { | |
for (let y = room.y; y < room.y + room.height; y++) { | |
for (let x = room.x; x < room.x + room.width; x++) { | |
dungeon[y][x] = 0; | |
} | |
} | |
} | |
function carveTunnel(dungeon, startPoint, endPoint) { | |
const [x1, y1] = startPoint; | |
const [x2, y2] = endPoint; | |
let x = x1; | |
let y = y1; | |
while (x !== x2) { | |
x += Math.sign(x2 - x); | |
dungeon[y][x] = 0; | |
} | |
while (y !== y2) { | |
y += Math.sign(y2 - y); | |
dungeon[y][x] = 0; | |
} | |
} | |
function generateDungeon(dungeon) { | |
const rooms = []; | |
for (let i = 0; i < MAX_ROOMS; i++) { | |
const newRoom = createRoom(); | |
let overlap = false; | |
for (const room of rooms) { | |
if (isRoomOverlap(newRoom, room)) { | |
overlap = true; | |
break; | |
} | |
} | |
if (!overlap) { | |
carveRoom(dungeon, newRoom); | |
rooms.push(newRoom); | |
} | |
} | |
for (let i = 1; i < rooms.length; i++) { | |
const prevRoomCenter = { | |
x: Math.floor(rooms[i - 1].x + rooms[i - 1].width / 2), | |
y: Math.floor(rooms[i - 1].y + rooms[i - 1].height / 2), | |
}; | |
const currentRoomCenter = { | |
x: Math.floor(rooms[i].x + rooms[i].width / 2), | |
y: Math.floor(rooms[i].y + rooms[i].height / 2), | |
}; | |
if (Math.random() < 0.5) { | |
carveTunnel(dungeon, [prevRoomCenter.x, prevRoomCenter.y], [currentRoomCenter.x, currentRoomCenter.y]); | |
} else { | |
carveTunnel(dungeon, [currentRoomCenter.x, currentRoomCenter.y], [prevRoomCenter.x, prevRoomCenter.y]); | |
} | |
} | |
return rooms; | |
} | |
const dungeon = createDungeon(); | |
const rooms = generateDungeon(dungeon); | |
// 初期プレイヤー位置を最初の部屋の中央に設定 | |
player.x = Math.floor(rooms[0].x + rooms[0].width / 2); | |
player.y = Math.floor(rooms[0].y + rooms[0].height / 2); | |
maze = dungeon; | |
function draw() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
drawMaze(maze); | |
drawEnemies(); // 敵を描画する関数を追加 | |
player.draw(); | |
} | |
function spawnEnemies(rooms) { | |
const enemies = []; | |
for (let i = 0; i < numEnemies; i++) { | |
const randomRoomIndex = Math.floor(Math.random() * rooms.length); | |
const randomRoom = rooms[randomRoomIndex]; | |
const x = Math.floor(randomRoom.x + Math.random() * randomRoom.width); | |
const y = Math.floor(randomRoom.y + Math.random() * randomRoom.height); | |
enemies.push(new Enemy(x, y)); | |
} | |
return enemies; | |
} | |
// 敵を生成 | |
let enemies = spawnEnemies(rooms); | |
function drawEnemies() { | |
enemies.forEach(enemy => enemy.draw()); | |
} | |
function updateEnemies(grid) { | |
enemies.forEach(enemy => enemy.update(grid, player)); | |
} | |
function moveEnemyTowardPlayer(enemy) { | |
const dx = Math.sign(player.x - enemy.x); | |
const dy = Math.sign(player.y - enemy.y); | |
const newX = enemy.x + dx; | |
const newY = enemy.y + dy; | |
if (canMoveTo(newX, newY)) { | |
enemy.x = newX; | |
enemy.y = newY; | |
} | |
} | |
function gameLoop() { | |
draw(); | |
requestAnimationFrame(gameLoop); | |
} | |
document.removeEventListener('keydown', handleKeyPress); | |
document.addEventListener('keydown', (event) => { | |
handleKeyPress(event); | |
draw(); | |
}); | |
gameLoop(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment