使用vue+el构建表格_使用Vue 2构建井字游戏:第2部分

使用vue+el构建表格

第1部分 ( Part 1 )

Tic Tac Toe Game

In the previous tutorial, we determined the elements of the game, applied styling and added the data properties. In this tutorial, we will take up the code, where we left off.

在上一教程中,我们确定了游戏的元素,应用了样式并添加了数据属性。 在本教程中,我们将从上次讲解的代码开始。

介绍 ( Introduction )

In this part, we will finish our game, applying all the functionality to make it fully playable. We will make heavy use of the event bus we implemented in the previous part. Together, we will implement everything remaining from listening for strikes, checking a win or draw, to displaying the game status and the scoreboard. At the end, we will also give our players the ability to restart the game and display the number of matches played. So, let's get started.

在这一部分中,我们将使用所有功能使其完全可玩,从而完成游戏。 我们将大量使用上一部分中实现的事件总线。 我们将一起执行剩下的所有工作,从听罢工,检查胜利或平局到显示游戏状态和计分板。 最后,我们还将使我们的玩家能够重新启动游戏并显示比赛数。 因此,让我们开始吧。

听罢工 ( Listening For Strikes )

To let the player place a mark in any of the cells, we need to let him click on it and listen for that action. In the Cell component add the v-on:click attribute to the <td> tag. We will use the @click shorthand syntax. To the click, we respond using the strike method.

为了让玩家在任何一个单元格中放置一个标记,我们需要让他单击它并聆听该动作。 在单元组件中,将v-on:click属性添加到<td>标签。 我们将使用@click速记语法。 对于点击,我们使用点击方法进行响应。

<td class="cell" @click="strike">{{ mark }}</td>

In the strike method, we need to get the active player from its parent component that is the Grid component and use it to change the mark property. The frozen property is set to true because we don't want any player to replace an already marked cell. We also need to tell the Grid component about this change. We will fire the strike event for the Grid component to listen with the cell name prop as payload.

在Strike方法中,我们需要从其父组件(即Grid组件)中获取活动播放器,并使用它来更改mark属性。 Frozen属性设置为true,因为我们不希望任何玩家替换已经标记的单元格。 我们还需要告知Grid组件有关此更改的信息。 我们将为Grid组件触发罢工事件,以将单元格名称prop作为有效载荷进行侦听。

methods: {
    strike () {
        if (! this.frozen) {
            // gets either X or O from the Grid component
            this.mark = this.$parent.activePlayer

            this.frozen = true

            // fires an event to notify the Grid component that a mark is placed
            Event.$emit('strike', this.name)
        }
    }
}

Now, the Grid component needs to listen for this event fire and respond accordingly. According to the game flow, the Grid component now needs to fill the respective cell number with the mark in the cells object, increment the number of moves, change the game status and change the active player to the non-active player. The check for game status also checks if the game has been won or is a draw. Add the following code to the created method of the Grid component so that we are always listening to this event fire.

现在,Grid组件需要侦听此事件触发并做出相应响应。 根据游戏流程,网格组件现在需要在单元格对象中用标记填充相应的单元格编号,增加移动次数,更改游戏状态,并将活动玩家更改为非活动玩家。 检查游戏状态还会检查游戏是否赢了或平局。 将以下代码添加到Grid组件的创建方法中,以便我们始终在监听此事件。

// listens for a strike made by the user on cell
// it is called by the Cell component
Event.$on('strike', (cellNumber) => {
        // sets either X or O in the clicked cell of the cells array
        this.cells[cellNumber] = this.activePlayer

        // increments the number of moves
        this.moves++

        // stores the game status by calling the changeGameStatus method
        this.gameStatus = this.changeGameStatus()

        this.changePlayer()
})

The first two lines of code only set the data properties. The next two lines call methods. We need to create those two methods. The changePlayer() method is very simple. All it does is that it changes the player to the non-active player. If the active player is O, it changes the active player to X and if the active player is X, it changes it to O. To clean up our code, we can use a computed property to get nonActivePlayer. This computed property will handle this task and leave little for the changePlayer() method to do. The computed property will look like this:

代码的前两行仅设置数据属性。 接下来的两行调用方法。 我们需要创建这两种方法。 changePlayer()方法非常简单。 它所做的只是将播放器更改为非活动播放器。 如果活动玩家为O ,则将活动玩家更改为X ;如果活动玩家为X ,则将其更改为O。 要清理我们的代码,我们可以使用计算属性来获取nonActivePlayer 。 这个计算出的属性将处理此任务,而changePlayer()方法几乎changePlayer() 。 计算的属性将如下所示:

computed: {
    // helper property to get the non-active player
    nonActivePlayer () {
        if (this.activePlayer === 'O') {
            return 'X'
        }

        return 'O'
    }
}

And the changePlayer() method looks like this.

而且changePlayer()方法看起来像这样。

// changes the active player to the non-active player with the help of the nonActivePlayer computed property
changePlayer () {
    this.activePlayer = this.nonActivePlayer
},

显示状态 ( Displaying the Status )

While listening for the strike event, we also changed the value of the gameStatus property using the changeGameStatus() method. We need to create it now. In this method, we need to check if the game is a win or a draw or is in progress. We will check for a win using the checkForWin() method, which we will create later. If the win condition is met, we return the gameisWon() method, which we will create later too. And to check for a draw, we make sure that the game is not yet won by any of the players and all the cells are filled. If none of them meets the conditions, we simply return 'turn', that is the default value of the gameStatus

在侦听罢工事件时,我们还使用changeGameStatus()方法更改了gameStatus属性的值。 我们需要立即创建它。 在这种方法中,我们需要检查游戏是赢局还是平局还是进行中。 我们将使用稍后将创建的checkForWin()方法检查获胜情况。 如果满足获胜条件,我们将返回gameisWon()方法,该方法也将在以后创建。 为了检查是否平局,我们确保尚未有任何玩家赢得比赛,并且所有单元格都已填满。 如果没有一个满足条件,我们只返回“ turn”,即gameStatus的默认值

// returns the game status to the gameStatus property
changeGameStatus () {
    if (this.checkForWin()) {
         return this.gameIsWon()
    // checks if the game is still not won and all cells are filled
    } else if (this.moves === 9) {
        // sets the status to draw
        return 'draw'
    }
    // sets the status to turn
    return 'turn'
}

By returning strings, we are setting the value of the gameStatus property.

通过返回字符串,我们可以设置gameStatus属性的值。

// stores the game status by calling the changeGameStatus method
this.gameStatus = this.changeGameStatus()

Depending upon the value of gameStatus, we need to change other properties too, like gameStatusMessage and gameStatusColor. We can make use of the watch feature available in Vue. The watch object listens for any change in any of the data property and helps you to perform certain actions.

根据gameStatus的值,我们还需要更改其他属性,例如gameStatusMessagegameStatusColor 。 我们可以利用Vue中可用的观看功能。 watch对象侦听任何data属性中的任何更改,并帮助您执行某些操作。

Using this, we can change the value of gameStatusMessage and gameStatusColor if there is a change in the value of gameStatus.

利用这一点,我们就可以如果在gameStatus值的变化而变化gameStatusMessagegameStatusColor的价值。

watch: {
    // watches for change in the value of gameStatus and changes the status 
    // message and color accordingly
    gameStatus () {
        if (this.gameStatus === 'win') {
            this.gameStatusColor = 'statusWin'

            this.gameStatusMessage = `${this.activePlayer} Wins !`

            return
        } else if (this.gameStatus === 'draw') {
            this.gameStatusColor = 'statusDraw'

            this.gameStatusMessage = 'Draw !'

            return
        }

        this.gameStatusMessage = `${this.activePlayer}'s turn`
    }
}

The code above is pretty straightforward. It sets the status message and color using the value of gameStatus. In this code, I am using Template Strings, that is a newly added feature to ES6. The problem here is that many browsers do not understand the new JavaScript syntax. But that is no issue since we are using the vue-loader setup. It uses Babel to compile the code for older browsers to understand.

上面的代码非常简单。 它使用gameStatus的值设置状态消息和颜色。 在这段代码中,我正在使用模板字符串,这是ES6的新增功能。 这里的问题是许多浏览器不了解新JavaScript语法。 但这不是问题,因为我们正在使用vue-loader设置。 它使用Babel编译代码以供较旧的浏览器理解。

We can display the status using these data properties to the player. Add the following <div> to the template tag of the Grid component just before the <table> tag.

我们可以使用这些数据属性向播放器显示状态。 在<table>标签之前,将以下<div>添加到Grid组件的模板标签中。

<div class="gameStatus" :class="gameStatusColor">
    {{ gameStatusMessage }}
</div>

:class is the shorthand syntax for v-bind:class. It helps to dynamically toggle classes depending upon the value of an attribute.

:classv-bind:class的简写语法。 它有助于根据属性的值动态切换类。

检查胜利 ( Checking for a Win )

While writing the code to change the game status, we called the checkForWin() method and also the gameIsWon(). We need to implement them now. If you can remember, at the beginning of this tutorial, we added an array called winConditions as a data property. We are going to use that now. To check for a win, we will loop through each array in the winConditions array as the cell number and check if the three of them have the same mark that is X or O.

在编写代码以更改游戏状态时,我们调用了checkForWin()方法以及gameIsWon() 。 我们现在需要实施它们。 如果您还记得的话,在本教程开始时,我们添加了一个名为winConditions的数组作为数据属性。 我们现在将使用它。 为了检查胜利,我们将循环遍历winConditions数组中的每个数组作为单元格编号,并检查它们中的三个是否具有相同的标记XO。

// checks for possible win conditions from the data
checkForWin () {
    for (let i = 0; i < this.winConditions.length; i++) {
        // gets a single condition wc from the whole array
        let wc = this.winConditions[i]
        let cells = this.cells

        // compares 3 cell values based on the cells in the condition
        if (this.areEqual(cells[wc[0]], cells[wc[1]], cells[wc[2]])) {
            return true
        }
    }

    return false
}

This method uses an extra helper function areEqual to keep the code clean. The areEqual method looks like this:

此方法使用额外的辅助函数areEqual来保持代码干净。 areEqual方法如下所示:

// helper function for comparing cell values
areEqual () {
   var len = arguments.length;

   // loops through each value and compares them with an empty sting and 
   // for inequality
   for (var i = 1; i < len; i++){
      if (arguments[i] === '' || arguments[i] !== arguments[i-1])
         return false;
   }
   return true;
}

This method takes all arguments, loops through them, checks it against an empty string and returns false if the argument is not equal to the previous argument. If this loop passes, meaning that is does not return false, it finally returns true.

此方法接受所有参数,然后遍历它们,对空字符串进行检查,如果参数不等于前一个参数,则返回false。 如果此循环通过,则表示不返回false,最后返回true。

The gameIsWon() method still remains to be implemented. We will do four things in this method. Firstly, we will fire an event called win with the activePlayer as payload and then return a string containing win to be placed in the gameStatus property. With this event fire, we can manage the number of wins for each player in the game and display it on the scoreboard. Secondly, we set the status message. Thirdly, we fire an event to freeze the cells. And at last, return a string 'win'.

gameIsWon()方法仍然有待实现。 我们将用这种方法做四件事。 首先,我们将使用activePlayer作为有效负载触发一个名为win的事件,然后返回一个包含win的字符串,将其放置在gameStatus属性中。 借助此事件,我们可以管理游戏中每个玩家的获胜次数并将其显示在记分板上。 其次,我们设置状态消息。 第三,我们触发一个事件来冻结细胞。 最后,返回一个字符串“ win”。

gameIsWon () {
    // fires win event for the App component to change the score
    Event.$emit('win', this.activePlayer)

        // sets the game status message
        this.gameStatusMessage = `${this.activePlayer} Wins !`

        // fires an event for the Cell to freeze
        Event.$emit('freeze')

    // sets the status to win
    return 'win'
}

We need to listen for this listen and set the frozen property of the Cell component to true. Add this code to the created method of the Cell component.

我们需要侦听此侦听,并将Cell组件的Frozen属性设置为true。 将此代码添加到Cell组件的创建方法中。

Event.$on('freeze', () => this.frozen = true)

计分板 ( Scoreboard )

Game Scoreboard

The App component needs to listen for the win event that we fired in the gameIsWon method. All we need to do when listening for this event is to increment the number of wins for the respective player. The player will be available to us as it was passed as payload in the event fire. Add the following to the created method of the App component.

App组件需要侦听我们在gameIsWon方法中触发的win事件。 侦听此事件时,我们要做的就是增加各个玩家的获胜次数。 该播放器将在事件触发时作为有效负载传递给我们。 将以下内容添加到App组件的创建方法中。

created () {
  Event.$on('win', winner => this.wins[winner]++)
}

As we have the number of wins, we can display it to the player. Add this markup to the template section of the App component just above the <div> with an id of app.

当我们有获胜次数时,我们可以将其显示给玩家。 将此标记添加到ID为app<div>上方的App组件的模板部分中。

<div class="scoreBoard">
  <span>O has {{ wins.O }} wins</span>
  <h2>Score Board</h2>
  <span>X has {{ wins.X }} wins</span>
</div>

You need to enclose the whole markup in the template in a parent <div>, so that Vue does not squawk about this. We can only have a single topmost-level element in the template.

您需要将整个标记放在模板的父<div> ,以使Vue不会对此感到困惑。 模板中只能有一个顶层元素。

重新开始游戏 ( Restarting the Game )

Restart button

Almost everything is done now, but our players can have only one match per browser load. That is not fair. Let's add a restart button so that our players can play as many matches as they want. Add the following button in the template tag of the App component just after the <grid> tag.

现在几乎所有工作都已完成,但是我们的播放器每次加载浏览器只能进行一次匹配。 这不公平。 让我们添加一个重启按钮,以便我们的玩家可以玩任意数量的比赛。 在<grid>标签之后,在App组件的模板标签中添加以下按钮。

<button class="restart" @click="restart">Restart</button>

This button listens for clicks and calls the restart method on each click. We need to add the restart method for this button to work. The things that we need to do in this method are to clear the mark property of all the cells and reset all the data of the Grid component. This way, we will have a fresh new game to play.

此按钮监听点击,并在每次点击时调用重新启动方法。 我们需要添加重新启动方法,此按钮才能工作。 在此方法中,我们需要做的事情是清除所有单元格的mark属性并重置Grid组件的所有数据。 这样,我们将玩一个全新的新游戏。

restart () {
  Event.$emit('clearCell')

  Event.$emit('gridReset')

  this.matches++
}

Here, we are calling two methods and incrementing the number of matches. The first event fire is for the Cell component to clear all the cells. And the second event fire is for the Grid component to reset its data. We need to listen for both the event fires.

在这里,我们调用了两个方法并增加了匹配数。 第一个事件触发是Cell组件清除所有单元格。 第二个事件触发是Grid组件重置其数据。 我们需要听两个事件触发。

The first one is very simple. In the Cell component, we will set the mark property to an empty string so that it can be replaced with a mark by any player. We also toggle the frozen property to false so that our player can place a mark. Add this code to the created method of the Cell component.

第一个很简单。 在“单元”组件中,我们将mark属性设置为一个空字符串,以便任何玩家都可以将其替换为mark。 我们还将冻结的属性切换为false,以便我们的播放器可以放置标记。 将此代码添加到Cell组件的创建方法中。

Event.$on('clearCell', () => {
    this.mark = ''

    this.frozen = false
})

For the second event fire, the Grid component will reset all its data property. To do so, we will make use of the Object.assign() function. It accepts the target object as the first argument and the source object as the second argument. Add this code to the created method of the Grid component.

对于第二个事件,Grid组件将重置其所有数据属性。 为此,我们将使用Object.assign()函数。 它接受目标对象作为第一个参数,源对象作为第二个参数。 将此代码添加到Grid组件的创建方法中。

// listens for a restart button press
// the data of the component is reinitialized
// it is called by the App component
Event.$on('gridReset', () => {
    Object.assign(this.$data, this.$options.data())
})

The $data returns the whole data object and $options.data() return the initial state of the data as set in the object. So, in this code, the initial data is set to the data object, which in other words, resets the data of the component.

$data返回整个数据对象,而$options.data()返回对象中设置的数据的初始状态。 因此,在此代码中,初始数据被设置为数据对象,换言之,将组件的数据复位。

比赛次数 ( Number of Matches )

Title and matches count

At last, we can display the number of the match being played by the player. Add this <h2> tag below the <h1> in the App component.

最后,我们可以显示玩家正在玩的比赛的号码。 将此<h2>标签添加到App组件中的<h1>

<h2>Match #{{ matches + 1 }}</h2>

We add 1 in it because we want to display the number of the match being played at the time and not the number of matches played before the game that is being played.

我们在其中添加1是因为我们想显示当时正在进行的比赛的数量,而不是正在玩的游戏之前的比赛的数量。

结论 ( Conclusion )

The final version of the game

Along these tutorials, we learned many important concepts required for making frontend apps. We learned about bootstrapping a Vue app with vue-cli and vue-loader. We also learned about Eventing and made an event bus. We also made use of computed properties, the watch method.

通过这些教程,我们学习了制作前端应用程序所需的许多重要概念。 我们了解了如何使用vue-cli和vue-loader引导Vue应用程序。 我们还了解了Eventing,并制作了事件总线。 我们还利用了计算属性,即watch方法。

That's it for this 2 part series. I hope you enjoyed making and playing this game with me and yourself. If you have any question, you may ask in the comments below.

这是两部分系列的内容。 我希望您喜欢和我以及您自己一起制作和玩这个游戏。 如有任何疑问,可以在下面的评论中提问。

翻译自: https://scotch.io/tutorials/building-a-tic-tac-toe-game-with-vue-2-part-2

使用vue+el构建表格

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值