Se63/typescript/zombie

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา

โค้ดเริ่มต้น

ใส่ใน jsfiddle

ส่วน HTML

<html>
  <body>
    <pre id="gameBoard"></pre>
  </body>
</html>

ส่วน Typescript (เลือก Javascript เปลี่ยนภาษาเป็น Typescript)

const boardHeight = 30;
const boardWidth = 50;

class GameBoard {
  constructor() {
    this.pX = 25;
    this.pY = 15;
    this.botCount = 5;
    this.botXs = [10,20,30,40,50];
    this.botYs = [5,10,20,15,25,12]
  }

  showBoard() {
    let boardRows = [];
    for (let i = 0; i < boardHeight; i++) {
      let rowChars = [];
      for (let j = 0; j < boardWidth; j++) {
        rowChars.push(' ');
      }
      boardRows.push(rowChars);
    }
    
    boardRows[this.pY][this.pX] = '@';
    for (let i = 0; i < this.botCount; i++) {
      boardRows[this.botYs[i]][this.botXs[i]] = 'B';
    }
    
    let boardStr = boardRows.map((row) => {
      return row.join('') + "\n";
    }).join('');
    
    let boardElement = document.getElementById('gameBoard');
    boardElement.innerHTML = boardStr;
  }
  
  moveBots() {
    for (let i=0; i < this.botCount; i++) {
      this.botXs[i]--;
    }
  }
  
  step() {
    this.moveBots();
    this.showBoard();
  }
}

gameBoard = new GameBoard();
gameBoard.showBoard();

ทำความเข้าใจกันก่อน

  • gameBoard เป็น global ทำให้เราเรียก gameBoard.step() ใน console ให้เกมทำงานทีละขั้นได้

clean showBoard กันก่อน

เมทอด showBoard นั้นค่อนข้างยาวและอ่านยาก เราจะแยกเป็น method ย่อย ๆ โดยเราจะแยกเป็น initBoardArray, boardArrayToStr กับ showBoardArray

  initBoardArray(): char[][] {
    let boardRows = [];
    for (let i = 0; i < boardHeight; i++) {
      let rowChars = [];
      for (let j = 0; j < boardWidth; j++) {
        rowChars.push(' ');
      }
      boardRows.push(rowChars);
    }
    return boardRows;
  }

  boardArrayToStr(boardRows: char[][]): string {
    return boardRows.map((row) => {
      return row.join('') + "\n";
    }).join('');
  }

  showBoardArray(boardRows: char[][]) {
    let boardElement = document.getElementById('gameBoard');
    boardElement.innerHTML = this.boardArrayToStr(boardRows);;
  }

  showBoard() {
    let boardRows = this.initBoardArray();
    
    boardRows[this.pY][this.pX] = '@';
    for (let i = 0; i < this.botCount; i++) {
      boardRows[this.botYs[i]][this.botXs[i]] = 'B';
    }
    
    this.showBoardArray(boardRows);
  }

แกะ player และ bots

เราจะทำให้โค้ดอ่านง่ายขึ้นและแก้ไขได้ง่ายขึ้น ในแนวคิดแบบ object-oriented เราจะพยายามจับกลุ่ม "ของ" ที่อยู่ด้วยกันบ่อย ๆ ให้กลายเป็นชิ้นขึ้นมา (เป็นวัตถุ) สังเกตว่าในระบบของเรามีข้อมูลที่ต้องอยู่ด้วยกันอยู่สองชุดคือ

ตำแหน่งของ bot และ

    this.botXs = [10,20,30,40,50];
    this.botYs = [5,10,20,15,25,12]

ตำแหน่งของผู้เล่น

    this.pX = 25;
    this.pY = 15;

และทั้งสองกลุ่มมีการทำงานใกล้เคียงกันเมื่อพิจารณาในส่วนของการแสดงข้อมูล เราจะสร้างคลาส Bot และ Player เพื่อเก็บตำแหน่งของ bot และ player ไว้ดังนี้ (เพิ่มไว้ก่อนคลาส GameBoard)

class Player {
  constructor(public x: number, public y: number) {}
}

class Bot {
  constructor(public x: number, public y: number) {}
}

เราจะสร้าง player ใน constructor ของ GameBoard (อีกหน่อยเราจะ inject เข้ามาได้) ส่วน bot เราจะเรียกเมทอด addBot

แก้ constructor และเพิ่ม addBot ดังนี้

class GameBoard {
  bots: Bot[];
  
  constructor() {
    this.player = new Player(25,15);
    this.botCount = 0;
    this.bots = [];
  }

  addBot(b: Bot) {
    this.bots.push(b);
    this.botCount++;
  }

  // ...
}

จากนั้นแก้ส่วน showBoard

  showBoard() {
    // ...
    
    boardRows[this.player.y][this.player.x] = '@';
    for (let i = 0; i < this.botCount; i++) {
      boardRows[this.bots[i].y][this.bots[i].x] = 'B';
    }
    
    // ...
  }

เพิ่ม bot เข้าไปหลังจากสร้าง GameBoard

gameBoard = new GameBoard();

gameBoard.addBot(new Bot(10,5));
gameBoard.addBot(new Bot(20,10));
gameBoard.addBot(new Bot(30,20));
gameBoard.addBot(new Bot(40,15));
gameBoard.addBot(new Bot(50,12));


สังเกตว่าส่วน showBoard โค้ดยังดูซ้ำ ๆ กันอยู่ ขั้นตอนไปเราจะสร้าง abstraction ไปอีกชั้นหนึ่ง เพื่อให้ครอบทั้ง player และ bot

GamePiece

สร้างคลาส GamePiece

class GamePiece {
  constructor(public x: number, public y: number) {}

  getX(): number { return this.x; }
  getY(): number { return this.y; }
  getPieceChar(): string { return '*'; }
}

ปรับ Player และ Bot

class Player extends GamePiece {
  getPieceChar(): string { return '@'; }
}

class Bot extends GamePiece {
  getPieceChar(): string { return 'B'; }
}

แกะ method drawPiece

  drawPiece(boardRows: char[][], piece: GamePiece) {
    boardRows[piece.getY()][piece.getX()] = piece.getPieceChar();
  }

แล้ววาดของลงไปด้วยโค้ดเดียวกัน (สร้างอาร์เรย์ elements ขึ้นมาก่อน โดยรวม this.bots และ this.player

    let elements = [...this.bots, this.player];

    let board = this;
    elements.forEach((piece) => {
      board.drawPiece(boardRows, piece);
    });

หมายเหตุ: ในฟังก์ชันใน forEach เราใช้ this แทน gameBoard ไม่ได้ เพราะว่าจะมีการตีความเป็นอย่างอื่น เลยต้องสร้างตัวแปรพิเศษมาเก็บค่าไว้

move bots

เราทิ้งโค้ดเมทอด moveBots ไว้ เป็นโค้ดที่เรียกแล้วจะมีปัญหา (เพราะว่ายังใช้ botXs อยู่เลย) เราจะเขียนเมท็อดในการเดินไว้ในคลาส Bot เลย ดังนี้

class Bot extends GamePiece {
  getPieceChar(): string { return 'B'; }
  
  move() {
    this.x--;
  }
}

จากนั้นไปแก้เมทอด moveBot ใน GameBoard ให้เรียกใช้เมทอดดังกล่าว

  moveBots() {
    for (let i=0; i < this.botCount; i++) {
      this.bots[i].move();
    }
  }

จริง ๆ zombie จะเดินเข้าหา player ดังนั้นจริง ๆ แล้ว การ move ควรจะต้องขึ้นกับตำแหน่งของ player ด้วย เราจะแก้เมทอด moveBots ให้เป็นดังนี้

  moveBots() {
    for (let i=0; i < this.botCount; i++) {
      this.bots[i].moveTo(this.player);
    }
  }

ให้เปลี่ยนชื่อเมทอด move เป็น moveTo (ปรับให้รับ player ด้วย)

งานของคุณ: ให้แก้ให้ bot วิ่งเข้าหา player โดยวิ่งได้ 4 ทิศเท่านั้น แก้ฟังก์ชัน moveTo ในคลาส Bot

class Bot extends GamePiece {
  // ...
  
  moveTo(p: Player) {
    // *** YOUR JOB HERE ***
  }
}

พิเศษ: SuperBot

งานพิเศษ: ให้เพิ่มคลาส SuperBot ที่เดินหาผู้ใช้ได้โดยเดินได้ 8 ทิศ (เดินแทยงได้ด้วย) แล้วเปลี่ยนบาง bot เป็น SuperBot

interface

Bots รูปแบบอื่น ๆ