rogueafdsafsf

🧩 Syntax:
let chip = 13 , chipSize = 60;
let walls = [];
let maze = [];

class Wall {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

let wayToStr = (x,y) => (
  x === 0 && y === 0 ? '0'
  : x === 1 && y === 0 ? ">"
  : x === 0 && y === 1 ? 'v'
  : x === -1 && y === 0 ? '<'
  : x === 0 && y === -1 ? '^'
  : '?'
);

let turnLeft = (x,y) => (
  x === 1 && y === 0 ? [0, -1]
  : x === 0 && y === -1 ? [-1, 0]
  : x === -1 && y === 0 ? [0, 1]
  : x === 0 && y === 1 ? [1, 0]
  : [0, 0]
);

let leftOne = (x,y) => {
  if (x === 1 && y === 0) return [0, -1];
  if (x === 0 && y === 1) return [1, 0];
  if (x === -1 && y === 0) return [0, 1];
  if (x === 0 && y === -1) return [-1, 0];
};

class Level {
  constructor() {
    this.tiles = [];    
    this.lenX = 0;
    this.lenY = 0;
  }
  tileAt(x, y) {
    if (x<0 || x>=this.lenX || y<0 || y>=this.lenY) return 1;
    return this.tiles[y][x];
  }
  setTile(x, y, value) {
    this.tiles[y][x] = value;
  }
  clear(value) {
    for(let y=0; y<this.lenY; y++) {
      for(let x=0; x<this.lenX; x++) {
        this.setTile(x, y, value);
      }
    }
  }
}

let Kind = {
  None: 0,
  Player: 1,
  Enemy: 2,
  Sword: 3,
}

let Behaviour = {
  None: 0,
  Lefthand: 1,
  Stalker: 2,
}

class Actor {
  constructor(kind, x, y, image, behaviour) {
    this.kind = kind;
    this.behaviour = behaviour;
    this.x = x;
    this.y = y;
    this.wx = 1;
    this.wy = 0;
    this.image = image;
    this.items = [];
  }
}

class Camera {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
}
////////////////////////////////////////////////////////
class ComMove {
  /**
   * @param {Actor} actor 移動させたいアクター
   * @param {number} dx 何マス移動するか
   * @param {number} dy 何マス移動するか
   */
  constructor(actor, dx, dy) {
    let t = this;
    t.actor = actor;
    t.dx = dx;
    t.dy = dy;
    t.beginX = -1;
    t.beginY = -1;
    t.endX = -1;
    t.endY = -1;
    /** 実行したフレーム数 */
    t.f = 0;
  }
  /**
   * コマンドを1フレーム実行する
   */
  exec() {
    let t = this;
    if (t.done) return t.done; //終了しているコマンドは実行しない
    t.f++;
    if (t.f === 1) {
      // 開始地点と終了地点の座標を計算
      t.beginX = t.actor.x;
      t.beginY = t.actor.y;
      t.endX = t.actor.x + t.dx;
      t.endY = t.actor.y + t.dy;
    }
    if (game.level.tileAt(t.endX, t.endY) === 1) {
      t.f = 20;
      return t.done;
    }
    // ↑で計算した座標の間を移動する
    t.actor.x = t.beginX + t.f*t.dx/20;
    t.actor.y = t.beginY + t.f*t.dy/20;

    return t.done;
  }
  /**
   * @returns {boolean} コマンドが終了していればtrue, 実行中ならfalse
   */
  get done() {
    return this.f >= 20;
  }
}
////////////////////////////////////////////////////////
class Game {
  constructor() {
    this.level = new Level();
    this.player = null;
    this.actors = [];
    this.camera = new Camera(0,0);
    this.commands = [];
    this.dirMapToPlayer = null;
  }
}

let game;

// 幅優先探索でdirMapを計算する
// 使用例 スタート地点を 4,2 ゴールを 4,5 としたい場合は
// calcDirMap(4, 2, game.level)
let calcDirMap = (startX, startY, level) => {
  let dirMap = new Level();
  
  // デフォルトの大きさではなく、chipかけるchipにする
  dirMap.tiles = new Array(chip).fill(0).map(_ => new Array(chip).fill(0));
  dirMap.lenX = chip;
  dirMap.lenY = chip;
  dirMap.clear([0,0]);
  
  //---- 幅優先探索
  let posList = [[startX,startY]];
  
  while(posList.length >= 1) {
    let pos = posList.shift();
    let nextPosList = [
      [pos[0]+1, pos[1]],
      [pos[0], pos[1]+1],
      [pos[0]-1, pos[1]],
      [pos[0], pos[1]-1]
    ];
    for(let nextPos of nextPosList) {
      let dirAtNextPos = dirMap.tileAt(...nextPos);
      if (dirAtNextPos[0] !== 0 || dirAtNextPos[1] !== 0) continue;
      
      let tileNumAtNextPos = level.tileAt(...nextPos);
      if (tileNumAtNextPos !== 0) continue;
        
      let vec = [pos[0]-nextPos[0], pos[1]-nextPos[1]];
      dirMap.setTile(...nextPos, vec);
      posList.push(nextPos);
    }
  }
  
  // dirMapをコンソールに出してみる(デバッグ用)
  let str = '';
  for(let y=0; y<dirMap.lenY; y++) {
    str += dirMap.tiles[y].map(vec => wayToStr(...vec)).join(' ');
    str += '\n';
  }
  console.log(str);
  
  return dirMap;
};
////////////////////////////////////////////////////////
function setup() {
  //二次元配列初期化
  for (let y = 0; y < chip; y++) {
    walls[y] = [];
    maze[y] = [];
    for (let x = 0; x < chip; x++) {
      walls[y][x] = 0;
      maze[y][x] = 0;
    }
  }
  //棒倒し法迷路設定
  for (let b = 0; b < chip; b++) {
    for (let a = 0; a < chip; a++) {
      if(b === 0 || b === chip - 1 || a === 0 || a === chip - 1){
        maze[b][a] = 1;
      }else{
        if(b % 2 === 0 && a % 2 === 0){
          maze[b][a] = 1;
          let c;
          b === 2 ? c = Math.floor(random(4))
          : c = Math.floor(random(3))
          c === 0 ? maze[b][a-1] = 1 //left
          : c === 1 ? maze[b][a+1] = 1 //right
          : c === 2 ? maze[b+1][a] = 1 //down
          : maze[b-1][a] = 1 //up
          }
        }
      }      
    }
  //壁クラス設定
  for (let q = 0; q < chip; q++) {
    for (let p = 0; p < chip; p++) {
      walls[q][p] = new Wall(p * chipSize, q * chipSize);
    }
  } 

  game = new Game();
  
  let player = new Actor(Kind.Player, 1,1,'🧙‍♀️', Behaviour.None);
  let enemy1 = new Actor(Kind.Enemy, 5,5,'👻', Behaviour.Lefthand);
  let enemy2 = new Actor(Kind.Enemy, 11,11,'🎃', Behaviour.Stalker);
  let sword = new Actor(Kind.Sword, 3,3, '🗡️', Behaviour.None);
  game.player = player;
  game.actors = [player, enemy1, enemy2, sword];
  
  // 生成した迷路を適用
  game.level.tiles = maze;
  game.level.lenX = chip;
  game.level.lenY = chip;
  
  createCanvas(800, 600);
}

function draw() {
  let w = chipSize;
  
////////////////////////////////////////////////////////
  
  // 剣を拾う判定
  //TODO: 毎フレームやる必要はないので、あとで直す
  if (game.commands.length === 0) {
    let items = game.actors
      .filter(act => act.kind === Kind.Sword)
      .filter(act => act.x === game.player.x && act.y === game.player.y);
    if (items.length === 1) {
      let item = items[0];
      game.player.items.push(Kind.Sword);
      item.kind = Kind.None;
    }
  }
  
  // プレイヤーの入力を受け付ける
  if (keyIsPressed && game.commands.length === 0) {
    let dxy = {37:[-1,0], 38:[0,-1], 39:[1,0], 40:[0,1]}[keyCode];
    if (dxy !== undefined) {
      game.player.wx = dxy[0];
      game.player.wy = dxy[1];
      game.commands.push(new ComMove(game.player, dxy[0], dxy[1]));
      
      game.dirMapToPlayer = calcDirMap(game.player.x, game.player.y, game.level);

      // 敵を動かす
      for(let enemy1 of game.actors.filter(act => act.kind === Kind.Enemy)) {

        if (enemy1.behaviour === Behaviour.Lefthand) {
          // 左手の方向
          let left = leftOne(enemy1.wx, enemy1.wy);
          // 左手にあるタイル番号(0は通路、1は壁)
          let leftTileNum = game.level.tileAt(enemy1.x+left[0], enemy1.y+left[1]);

          // 進行方向の左手に壁がないとき、反時計回りに向きをかえる
          if (leftTileNum === 0) {
            let leftWay = turnLeft(enemy1.wx, enemy1.wy);
            enemy1.wx = leftWay[0];
            enemy1.wy = leftWay[1];
          }

          let frontTileNum = game.level.tileAt(enemy1.x+enemy1.wx, enemy1.y+enemy1.wy);

          // 進行方向に壁があるとき、時計回りに向きをかえる
          if (frontTileNum === 1) {
            // turnRight関数がないので、3回turnLeftして代用
            let rightWay = [enemy1.wx, enemy1.wy];
            for(let i=0; i<3; i++) {
              rightWay = turnLeft(...rightWay);
            }
            enemy1.wx = rightWay[0];
            enemy1.wy = rightWay[1];
          }
        }
        
        if (enemy1.behaviour === Behaviour.Stalker) {
          // 敵を方向マップに応じて(幅優先探索の結果に応じて)動かす
          let dirAtEnemyPos = game.dirMapToPlayer.tileAt(enemy1.x, enemy1.y);
          enemy1.wx = dirAtEnemyPos[0];
          enemy1.wy = dirAtEnemyPos[1];
        }
        
        // 敵の行き先
        let destX = enemy1.x + enemy1.wx;
        let destY = enemy1.y + enemy1.wy;
        
        // プレイヤーの行き先(衝突を考慮する)
        let playerX;
        let playerY;
        // プレイヤーの行き先(衝突を考慮しない)
        let playerDestX = game.player.x + game.player.wx;
        let playerDestY = game.player.y + game.player.wy;
        
        if (game.level.tileAt(playerDestX, playerDestY) === 1) {
          // 壁にぶつかりそうなら元の座標
          playerX = game.player.x;
          playerY = game.player.y;
        }else{
          // 壁にぶつからない場合は、行き先の座標
          playerX = playerDestX;
          playerY = playerDestY;
        }

        if (destX === playerX && destY === playerY) {
          // プレイヤーと衝突してしまいそう
          // 何もしない
        }else{
          // 衝突しなさそう
          game.commands.push(new ComMove(enemy1, enemy1.wx, enemy1.wy));  
        }
        
        // game.commands.push(new ComMove(enemy1, enemy1.wx, enemy1.wy));
      }
    }
  }

  // コマンドをすべて1フレーム分実行する
  for(let c of game.commands) {
    c.exec();
  }
  // 実行し終わったコマンドを消す
  game.commands = game.commands.filter(c => !c.done);
////////////////////////////////////////////////////////
  
  let p = game.player;
  let c = game.camera;
  c.x = p.x - 9/2;
  c.y = p.y - 9/2;
  let cx = w * c.x;
  let cy = w * c.y;
  
  background(0);
  
  textAlign(LEFT, TOP);
  textSize(w * 7/8);
  for(let y=0; y<game.level.lenY; y++){
    for(let x=0; x<game.level.lenX; x++){
      let t = game.level.tileAt(x,y);
      if(t === 1){
        text('🧱', w*x-cx, w*y-cy);
      } 
    }
  }
  
  for(let a of game.actors) {
    if (a.kind === Kind.None) continue;
    text(a.image, w*a.x-cx, w*a.y-cy);
    
    if (a.kind === Kind.Sword) continue;
    fill(255);
    text(wayToStr(a.wx, a.wy), w*a.x-cx, w*a.y-cy);
  }
  
  //----- UI描画
  fill('gray');
  rect(600, 0, 200, 600);
  //TODO: game.player.items の配列をみて、所持アイテムを描画
}

/*--------------------------------------------------
12/04

衝突判定(作成中)
迷路自動作成ロジック(作成中)

--------------------------------------------------
11/20

幅優先探索
敵を方向マップに応じて(幅優先探索の結果に応じて)動かす

--------------------------------------------------
11/13 dirMapをコンソールに出してみる(デバッグ用)

以下をコンソールへ入力

calcDirMap(4, 2, 4, 5, game.level)

--------------------------------------------------
11/06 enemyの動作アルゴリズム

進行方向の左手に壁がないとき、反時計回りに向きをかえる
進行方向に壁があるとき、時計回りに向きをかえる

反時計回りは、関数turnLeft
時計回りは、関数turnLeftを3回繰り返す

--------------------------------------------------*/