失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > JavaScript学习笔记——编写战舰游戏 Part2:JavaScript部分

JavaScript学习笔记——编写战舰游戏 Part2:JavaScript部分

时间:2020-10-20 12:18:01

相关推荐

JavaScript学习笔记——编写战舰游戏 Part2:JavaScript部分

编程思路

核心:跟随游戏的步骤逻辑,思考其中所需的功能,并尽量将各部分功能分离,可以使编程思路更清晰、代码易读性更强,也方便调试先写大框架,有需要的功能直接调用(虽然未编写),交给后面的代码实现细节功能初始化:随机生成战舰的位置(这一功能最后再实现,先使用硬编码指定战舰位置(即:指定固定的位置),优先编写和调试后面的核心功能获取玩家输入:用controller对象实现。将输入的"A3"转换为"03",判定是否击中交给后面的对象model处理;且通过model中击沉的战舰数量判断游戏是否结束;处理击中和未击中:用model对象实现,接收到"03"这样的参数后,判断是否击中、是否重复击中同一位置、战舰是被击沉;击中/未击中的图像显示交给后面的对象view实现;图形化的游戏显示:用view对象实现,传入击中/未击中的单元格坐标,在对应位置显示战舰或MISS图像。

摘要:知识要点

每个战舰为ship对象,包含两个数组locationshits,保存一艘战舰的位置和被击中部位

若有多个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对象,包含两个数组locationshitis,保存一艘战舰的位置和被击中部位若有多个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,检测游戏是否结束

var controller = {guesses: 0,processGuess: function(guess) {var location = parseGuess(guess);//"A3"转换为"03"的辅助函数if (location) {//有效输入this.guesses++;var hit = model.fire(location);if (hit && model.shipsSunk === model.numShips) {view.displayMessage("You sank all my battleships, in " + this.guesses + " guesses");}}}}

怎样获取玩家输入:事件处理程序

事件处理程序能将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传递一个事件对象,其中包含有关用户按下了哪个键的信息

function init() {//网页加载完毕后,再开始接收输入并开始游戏//...var guessInput = document.getElementById("guessInput");guessInput.onkeypress = handleKeyPress;//...}function handleKeyPress(e) {//包含有关用户按下了哪个键的信息var fireButton = document.getElementById("fireButton");// in IE9 and earlier, the event object doesn't get passed// to the event handler correctly, so we use window.event instead.e = e || window.event;if (e.keyCode === 13) {fireButton.click();return false;//返回false,让表单不做其他的提交等操作}}

4.随机生成战舰

每个战舰为ship对象,包含两个数组locationshits,保存一艘战舰的位置和被击中部位若有多个ship对象,将它们保存在同一个数组ships中(数组元素为对象

思路:

大循环:要生成几艘战舰,就环循几次大循环内部:

每次在游戏板上随机生成一艘战舰(可能于已有战舰重叠)

生成后判断是否重叠:若重叠则重新随机生成;若不重叠将新战舰加入ships数组

注意,这里的[生成-判断-若重叠,重新生成],此逻辑适合用do while循环

实现方案:

generateShips:主方法,它创建model对象中的ships数组且保证无重叠(战舰数由model对象的属性numShips指定)generateAShipRandomly:这个方法随机在游戏板上生成一个战舰对象ship(包含两个数组locationshits)[生成战舰可能与已有的重叠]

的位置可能与其他战舰重叠,也可能不重叠。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部分》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。