Se63/typescript/zombie
เนื้อหา
โค้ดเริ่มต้น
ใส่ใน 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