Оcenka-BEL.com - безплатни онлайн тестове, пробни изпити, видео уроци и помощ при писане на задания от литературно и хуманитарно естество

Practical CoffeeScript, Part II

Публикувано: 2017-09-11 17:04:43

Резултат с изображение за dimoff web consultant

 

 

Practical CoffeeScript, Part II

Introduction

In this part of the Practical CoffeeScript series we are going to create a Tic-tac-toe game 
It will consist of ~120 lines of CoffeeScript and some HTML/CSS

Tic-Tac-Toe Game

In our index page, we load Bootstrap, jQuery and a custom webfont we will be using. 
The div with classes "col-md-4 text-center col-md-offset-4 alert alert-info alerts" would be used to show who won or display that the game was a tie. 
The form would be used to collect the names of the players - we catch its submission with an event listener and prevent actual submission 

 will be used to show how many wins and loses each player has so far, as well as who is playing X and who is playing with O. There would also be some default messages stored there. 
 is where the actual game would be shown with JavaScript.

 

HTML

index.html


Tic-Tac-Toe

×

Player 1

 

Player 2

 

 

`

Welcome to Tic-Tac-Toe.

Defeat your friends and show no mercy

CSS

#messages h1 {
    font-size: 1.8em;
}
* {
    font-family: 'Chewy', cursive;
}

#messages p:first-letter {
    font-size: 1.3em;
}

#messages p {
    color: #7f7f7e;
}

.tic {
    width: 30%;
    height: 120px;
    background-color: cornsilk;
    border: 1px groove crimson;
}

.tic:nth-of-type(3n + 1) {
    clear: both;
}

.tic {
    cursor: pointer;
    float: left;
    font-size: 68px;
    text-align: center;

}

.tic.x {
    color: crimson;
}

.tic.o {
    color: #3997ff ;
}

#messages {
    display: none;
}

.alerts {
    font-size: 24px;
    text-align: center;
}

We apply our webfont to all elements, force the tic-tac-toe boxes to appear on a new line on each 4th box, make the tic boxes look clickable and apply some other default styles.

CoffeeScript

$ ->
  Tic =
    data:
      turns: 0
      x: {}
      o: {}
      gameOver: false

When the DOM is ready we initialize the Tic object and set its data property to store some initial values. x and o will be objects containing properties for the number of diagonal, horizontal and vertical ticks for X and O, turns would store the turns made in order for us to determine whose player’s turn is next.

initialize: ->
      $("form").hide 'slow'
      $("#tic").html("")
      $("

").appendTo("#tic") for tic in [0..8] @.addListeners() @.assignRoles()

In the initialize method of the Tic object we hide the form, reset the game board and add all 9 boxes to it. Afterwards, we add the event listeners to the DOM and call assignRoles to determine who is playing X and who is playing O.

addListeners: ->
      $(".tic").click ->

        if Tic.data.gameOver is no and not $(@).text().length
          if Tic.data.turns % 2 is 0 then $(@).html("X").addClass("x moved")
          else if Tic.data.turns % 2 isnt 0 then $(@).html("O").addClass("o moved")
          Tic.data.turns++
          Tic.checkEnd()
        if $(".moved").length >= 9
          Tic.addToScore("none")

In the addListeners method, we set up an event handler for click on a box where we check if the game is not over and if there is no text in the box we determine whether we have to place a X or an O in the box based on the turns variable.Finally, we place the X or O and add a corresponding class to the box.

We increment the turns variable, check if the game needs to end and check if the game is a tie after we check if it has ended.


    checkEnd : ->
      @.data.x = {}
      @.data.o = {}

      diagonals = [[0,4,8], [2,4,6]]
      for diagonal in diagonals
         for col in diagonal
           @.checkField(col, 'diagonal')
         @.checkWin()
         @.emptyStorageVar('diagonal')
      for row in [0..2]

        start = row * 3
        end = (row * 3) + 2
        middle = (row * 3) + 1

        #vertical check
        @.checkField(start, 'start')
        @.checkField(middle, 'middle')
        @.checkField(end, 'end')
        @.checkWin()
        for column in [start..end]
        # horizontal check
          @.checkField(column, 'horizontal')

        @.checkWin()
        @.emptyStorageVar('horizontal')

In the checkEnd method, we empty out x and o (because we will be doing a new check). Check the diagonal, vertical and horizontal fields for 3 or more ticks of the same type.

checkField: (field, storageVar) ->
      if $(".tic").eq(field).hasClass("x")
        if @.data.x[storageVar]? then @.data.x[storageVar]++ else @.data.x[storageVar] = 1
      else if $(".tic").eq(field).hasClass("o")
        if @.data.o[storageVar]? then @.data.o[storageVar]++ else @.data.o[storageVar] = 1

The checkField method checks if a given field contains an x or an o and adds 1 to the storageVar if so (storageVar could be vertical, horizontal or diagonal)

emptyStorageVar: (storageVar) ->
      @.data.x[storageVar] = null
      @.data.o[storageVar] = null

The emptyStorageVar method just takes a storageVar and empties it both for x and o.

 checkWin: ->

      for key,value of @.data.x
        if value >= 3
          localStorage.x++; @.addAlert "X wins"
          @.data.gameOver = true
          @.addToScore("X")
      for key,value of @.data.o
        if value >= 3
          localStorage.o++; @.addAlert "O wins"
          @.data.gameOver = true
          @.addToScore("O")

The checkWin method checks each value of the x and o objects (horizontal, vertical and diagonal properties) whether it is equal to 3 (3 ticks in the same direction) and if so adds adds that game to the scores of the playing users.

 addToScore: (winningParty) ->
      @.data =
        turns: 0
        x: {}
        o: {}
        gameOver: false
      $("#messages").html ""
      if winningParty is "none"
        @.addAlert "The game was a tie."
        Tic.initialize()
        return false

      if playerData.rolep1[winningParty]? then ++playerData.p1stats.wins else ++playerData.p1stats.loses
      if playerData.rolep2[winningParty]? then ++playerData.p2stats.wins else ++playerData.p2stats.loses

      localStorage[playerData.player1] = JSON.stringify playerData.p1stats
      localStorage[playerData.player2] = JSON.stringify playerData.p2stats
      Tic.initialize()

The addToScore method takes as an argument the party that won (“X” or “O”), resets the data about the game and the messages, adds the current wins/loses of the playing users and resets the game. If nobody won, sets out an alert that the game was tie and restarts the game without adding or changing the scores of the players in localStorage or in playerData.

addAlert: (msg) ->
      $("p.gameAlert").fadeOut('slow').remove()
      $(".alerts").append "

#{msg}

" $(".alerts").show "slow" $("body").animate( scrollTop: $(".alerts").offset().top , 'slow' )

The addAlert method just appends the message inside a paragraph to the .alerts and scrolls to that alerts class.

The last method of the Tic object is assignRoles:

assignRoles: ->
      roles = ["X","O"]
      randomRole = roles[Math.floor(Math.random() * roles.length)]
      if randomRole is "X" then randomRole2 = "O" else randomRole2 = "X"
      playerData.rolep1 = {}
      playerData.rolep2 = {}
      playerData.rolep1[randomRole] = true
      playerData.rolep2[randomRole2] = true
      player1 = "

#{playerData.player1} is playing #{randomRole}

" player2 = "

#{playerData.player2} is playing #{randomRole2}

" $("#messages").append("

X starts first!

").append(player1).append player2 $("#messages").append("

#{playerData.player1} has #{playerData.p1stats.wins} wins and #{playerData.p1stats.loses} loses

") $("#messages").append("

#{playerData.player2} has #{playerData.p2stats.wins} wins and #{playerData.p2stats.loses} loses

") $("#messages").show 'slow'

In it, we generate a random role and display who is playing x and who is playing o. We also display their statistics so far.

Finally, we add 2 event listeners outside of the Tic object. When the form is submitted we save the players data and load their statistics from localStorage. Afterwards, we start the game.

 $("form").on "submit", (evt) ->
    evt.preventDefault()
    playerData.player1 = $("input[name='pl-1']").val()
    playerData.player2 = $("input[name='pl-2']").val()
    playerData.p1stats = localStorage[playerData.player1] || wins: 0, loses: 0
    if typeof playerData.p1stats is "string" then playerData.p1stats = JSON.parse playerData.p1stats
    playerData.p2stats = localStorage[playerData.player2] || wins: 0, loses: 0
    if typeof playerData.p2stats is "string" then playerData.p2stats = JSON.parse playerData.p2stats
    Tic.initialize()
  $(".close").click ->
    $(@).parents(".alerts").hide 'slow'

Conclusion

I hope you see how CoffeeScript can seriously reduce your development time while letting you focus on the logic of your application. Feel free to enhance the Tic Tac Toe game and make it production-ready to further practice your freshly acquired CoffeeScript skills. We actually left some parts of the logic, such as @.addToScore("X") verbose so the app could be slimmed down further.