javascript
JavaScript学习笔记——编写战舰游戏 Part2:JavaScript部分
編程思路
- 核心:跟隨游戲的步驟邏輯,思考其中所需的功能,并盡量將各部分功能分離,可以使編程思路更清晰、代碼易讀性更強,也方便調試
- 先寫大框架,有需要的功能直接調用(雖然未編寫),交給后面的代碼實現細節功能
摘要:知識要點
-
每個戰艦為ship對象,包含兩個數組locations和hits,保存一艘戰艦的位置和被擊中部位
-
若有多個ship對象,將它們保存在同一個數組ships中(數組元素為對象)
-
玩家提交輸入后,通過遍歷每艘戰艦的位置,判定是否擊中
-
根據是否擊中,使用對應網格的<td>元素的setAttribute()將其設置為之前在CSS中規定好的類(類的background已被設置為戰艦或MISS圖像),網頁將實時地根據其class(類)的不同改變其背景圖像
-
消息顯示區為<div>元素,innerHTML對應其文本
-
輸入框為<input>元素(type為"text"),value對輸入框的內容,placeholder可預設其文本,將value置為""可清空文本
-
開火按鈕為<input>元素(type為"button"),value對應按鈕上的文本
-
"button"型<input>元素的onclick事件:檢測到點擊事件時觸發
-
[優化:回車自動提交表單]"text"型<input>元素的onkeypress事件:(在輸入框中)檢測到按鍵事件時觸發
-
[優化:回車自動提交表單]事件處理程序:判斷當前按下的按鍵是否為回車,如果是,自動調用fireButton.click()實現按鈕點擊操作
設計方案
將這款游戲設計為幾個較為獨立的對象(每個對象有其主要職責,從而便于獨立創建和測試游戲的各個部分),并利用DOM與用戶交互。這種設計讓問題理解起來容易得多。
- 這樣做的優點在于:可以相對獨立的編寫代碼,并分別測試每個對象,確保對象履行其職責(每個對象的測試代碼可能需要單獨編寫,僅用于測試該對象的功能)
總共有3個要設計并實現的對象:model、view和controller
- model將存儲游戲的狀態,如每艘戰艦的位置以及哪個部位被擊中;
- view負責在model存儲的狀態改變時更新界面;
- controller將各個部分整合以實現游戲邏輯:將用戶輸入交給model以更新狀態、判斷游戲是否結束
1.view對象
- view對象用于顯示當前的游戲狀態
- 需要displayMessage方法:在左上角顯示hit/miss/you sank my battleship信息
- 需要displayHit方法:在某指定網格內顯示戰艦圖像
- 需要displayMiss方法:在某指定網格內顯示MISS圖像
具體實現方案
displayMessage方法:傳入要顯示的信息msg,通過getElementById獲取用于顯示消息的<div>元素,用innerHTML設置其文本為msg
displayHit方法:傳入指定網格<td>的id,通過getElementById獲取相應的<td>元素,并將其class屬性設置為hit類,即可將網格背景設置為戰艦圖像(利用了之前定義的hit類,其background屬性為戰艦圖像)
displayMiss方法:同理
var view = {displayMessage: function(msg) {var messageArea = document.getElementById("messageArea");messageArea.innerHTML = msg;},displayHit: function(location) {var cell = document.getElementById(location);cell.setAttribute("class", "hit");},displayMiss: function(location) {var cell = document.getElementById(location);cell.setAttribute("class", "miss");} };2.model對象
model對象用于存儲游戲的狀態、判斷是否擊中以完成狀態轉換、狀態改變時通知view
- 需要一些屬性:保存網格大小、總戰艦數量、戰艦長度、被擊沉的戰艦數
- 每個戰艦為ship對象,包含兩個數組locations和hitis,保存一艘戰艦的位置和被擊中部位
- 若有多個ship對象,將它們保存在同一個數組ships中(數組元素為對象)
- 需要fire方法:向戰艦開火時給出開火位置,判斷是否擊中、擊沉,并更新狀態
- 需要isSunk方法:返回戰艦是否被擊沉
- 注意model對象所保存的狀態改變時(如擊中),要通知view。因為view模型并不會主動的檢查狀態的變化
- 需要generateShip方法:隨機生成戰艦
核心邏輯:如何檢測擊中
遍歷每艘船,比較船的位置和開火的位置
var model = {boardSize: 7,numShips: 3,shipLength: 3,shipsSunk: 0,// hard-coded values for ship locations(used for test) /*ships: [{ locations: ["06", "16", "26"], hits: ["", "", ""] },{ locations: ["24", "34", "44"], hits: ["", "", ""] },{ locations: ["10", "11", "12"], hits: ["", "", ""] }], */fire: function(guess) {for (var i = 0; i < this.numShips; i++) {var ship = this.ships[i];var index = ship.locations.indexOf(guess);//猜測的位置上有戰艦if (ship.hits[index] === "hit") {//Already hitreturn true;} else if (index >= 0) {//HITif (this.isSunk(ship)) {//SUNK}return true;}}//MISSreturn false;},//... };注意,查看是否命中戰艦時,代碼為
var ship = this.ships[i];
var locations = ship.locations;
var index = locations.indexOf(guess);
這里使用串接(Chaining)將對象引用連接,避免創建臨時變量,也減少了代碼量
串接后的代碼為
var ship = this.ships[i];
var index = ship.locations.indexOf(guess);
此處ship沒有串接,以免串接鏈條過長(閱讀理解困難)
3.controller對象
controller對象用于整合各部分:處理用戶輸入,記錄猜測次數、讓model根據當前猜測更新自己、判斷游戲是否結束
- 需要guesses屬性:記錄猜測次數
- 需要processGuess方法:傳入用戶猜測,對其處理(如"A3"轉換為"03"),將結果交給model,檢測游戲是否結束
怎樣獲取玩家輸入:事件處理程序
事件處理程序能將HTML中的表單<form>元素與游戲關聯
- 點擊Fire!按鈕時(單擊事件),調用事件處理程序
- 事件處理程序(回調函數)事先編寫好,用于獲取輸入框的內容并交給controller
單擊事件與事件處理程序
window.onload = init; function init() {//網頁加載完畢后,再開始接收輸入并開始游戲// place the ships on the game boardmodel.generateShipLocations();// Fire! button onclick handlervar fireButton = document.getElementById("fireButton");fireButton.onclick = handleFireButton; }事件處理程序用于獲取輸入框的內容并交給controller
function handleFireButton() {var guessInput = document.getElementById("guessInput");var guess = guessInput.value.toUpperCase();//獲取輸入框的內容controller.processGuess(guess);//輸入框的內容交給controllerguessInput.value = "";//重置輸入框,玩家無需手動刪除上一個輸入 }優化:希望按下回車也能觸發事件
-
HTML中<input>元素的onkeypress事件:(在輸入框中)檢測到按鍵事件時,就觸發對應的事件處理程序,可將所需的按鍵處理程序賦給onkeypress
-
事件處理程序:判斷當前按下的按鍵是否為回車,如果是,自動調用fireButton.click()實現按鈕點擊操作
這里使用了guessInput元素的onkeypress事件
- guessInput.onkeypress = handleKeyPress;
- 瀏覽器向事件處理程序handleKeyPress傳遞一個事件對象,其中包含有關用戶按下了哪個鍵的信息
4.隨機生成戰艦
- 每個戰艦為ship對象,包含兩個數組locations和hits,保存一艘戰艦的位置和被擊中部位
- 若有多個ship對象,將它們保存在同一個數組ships中(數組元素為對象)
思路:
- 大循環:要生成幾艘戰艦,就環循幾次
- 大循環內部:
每次在游戲板上隨機生成一艘戰艦(可能于已有戰艦重疊)
生成后判斷是否重疊:若重疊則重新隨機生成;若不重疊將新戰艦加入ships數組
注意,這里的[生成-判斷-若重疊,重新生成],此邏輯適合用do while循環
實現方案:
- generateShips:主方法,它創建model對象中的ships數組且保證無重疊(戰艦數由model對象的屬性numShips指定)
- generateAShipRandomly:這個方法隨機在游戲板上生成一個戰艦對象ship(包含兩個數組locations和hits)[生成戰艦可能與已有的重疊]
的位置可能與其他戰艦重疊,也可能不重疊。 - collision:傳入ship對象,并判斷它是否與已有的戰艦重疊
ps. 生成新戰艦時無需顧及hits的生成:檢測是否擊中,看的是hits數組中對應元素是否為"hit"(因為被擊中時才設置hits數組中相應索引的值為"hit"),一開始默認hits數組為空即可
generateShips: function() {var locations;for (var i = 0; i < this.numShips; i++) {do {locations = this.generateAShipRandomly();} while (this.collision(locations));this.ships[i].locations = locations;}console.log("Ships array: ");console.log(this.ships);},generateAShipRandomly: function() {var direction = Math.floor(Math.random() * 2);var row, col;if (direction === 1) { // horizontalrow = Math.floor(Math.random() * this.boardSize);col = Math.floor(Math.random() * (this.boardSize - this.shipLength + 1));} else { // verticalrow = Math.floor(Math.random() * (this.boardSize - this.shipLength + 1));col = Math.floor(Math.random() * this.boardSize);}var newShipLocations = [];for (var i = 0; i < this.shipLength; i++) {if (direction === 1) {newShipLocations.push(row + "" + (col + i));} else {newShipLocations.push((row + i) + "" + col);}}return newShipLocations;},collision: function(locations) {for (var i = 0; i < this.numShips; i++) {var ship = this.ships[i];for (var j = 0; j < locations.length; j++) {if (ship.locations.indexOf(locations[j]) >= 0) {return true;}}}return false;}};總結
以上是生活随笔為你收集整理的JavaScript学习笔记——编写战舰游戏 Part2:JavaScript部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab 直流-直流变换器毕业论文,
- 下一篇: 刀魔王带你了解雕刻刀模的生产流程!