4
\$\begingroup\$

There is the implementation of flood game on my GitHub.

The app.coffee is the main part of project:

class Square
  colors = [
    'orange',
    'green',
    'red',
    'blue',
    'violet',
    'yellow',
  ]
  constructor: (@game, @node)->
    @node.on 'click', =>
      @game.flood(@color)

  reset: ->
    @controlled = false
    color = colors[Math.floor(Math.random() * colors.length)]
    @setColor(color)

  setColor: (color)->
    @node.removeClass(@color)
    @color = color
    @node.addClass(@color)


class Game
  DIMENSION = 14
  SIZE = DIMENSION * DIMENSION
  constructor: ($tbody)->
    @limit = 25
    @grid = []
    for i in [0...DIMENSION]
      $tr = $ '<tr/>'
      $tbody.append $tr
      @grid.push []
      for j in [0...DIMENSION]
        $td = $('<td/>').html('&nbsp;')
        $tr.append $td
        @grid[i].push(new Square(@, $td))
    @top = @grid[0][0]
    @reset()

  reset: ->
    for i in [0...DIMENSION]
      for j in [0...DIMENSION]
        @grid[i][j].reset()
    @top = @grid[0][0]
    @top.controlled = true
    @flood(@top.color)
    @setTurnCounter(0)

  setTurnCounter: (counter)->
    $counter = $ '#count'
    @turn = counter
    $counter.toggleClass 'bad', @turn > @limit
    $('#counter-used').text(@turn)


  flood: (color)->
    if @top.color is color
      return
    @setTurnCounter(@turn + 1)
    @_flood(0, 0, color, [])
    if @hasWon()
      setTimeout(
        =>
          @decreaseLimit()
          @reset()
        2000
      )
  decreaseLimit: ()->
    @expected--



  _flood: (i, j, color, checked)->
    if i < 0 or j < 0 or i == DIMENSION or j == DIMENSION
      return
    cur = @grid[i][j]
    if cur.controlled or cur.color is color
      if cur in checked
        return
      cur.setColor(color)
      cur.controlled = true
      checked.push cur
      @_flood(i-1, j, color, checked)
      @_flood(i+1, j, color, checked)
      @_flood(i, j-1, color, checked)
      @_flood(i, j+1, color, checked)

  hasWon: ->
    for i in [0...DIMENSION]
      for j in [0...DIMENSION]
        if not @grid[i][j].controlled
          return false
    return true

(($, Game)->
  tbody = $('tbody')
  game = new Game(tbody)
  btnRestart = $('#btn-restart')
  btnRestart.on 'click', (e)->
    e.preventDefault()
    game.reset()

)(jQuery, Game)

I build assets with Grunt and use grunt-serve as a simple HTTP server.

P.S.: I use styles from another implementation of this game but based on Knockout.js.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Overall, it's not bad. I only have some minor observations on the code itself:

  • You're setting, and re-setting @top in both the constructor and reset.

  • SIZE isn't used for anything.

  • Why does reset call flood? As far as I can tell, there's no need at all.

  • Doesn't seem like decreaseLimit has a point.

  • You could postfix a couple of if/unless statements and cut down on indentation.

  • in _flood the if i < 0 or j < 0 or i == DIMENSION or j == DIMENSION check could be written as:

    return unless 0 <= i < DIMENSION and 0 <= j < DIMENSION
    

    or you could simply skip it in favor of trying the coordinates and checking afterward:

    square = @grid[i]?[j]
    return unless square?
    
  • When looping through the @grid (in reset and hasWon) there's no need to use a [0...DIMENSION] range. You can just use regular CoffeeScript for...in loops:

    for row in @grid
      for square in row
        # do something with square...
    
  • The IIFE at the end could be written more idiomatically using the do keyword

    do ($ = jQuery, Game) ->
      ...
    

Structurally, I have a few notes:

  • You inject both the Game object and a td element into Square, so Square doesn't really have that much to do. In fact, the first thing it does is set an event listener on the element that the game object created, and set it to call back to the game object. It just seems a roundabout way to set things up.
    I'd probably move the td creation to Square.

  • You pass a tbody element to the Game constructor, which is nice. But Game also directly accesses #count and #counter-user. So it's not really decoupled from the markup at all.

  • Somewhat similarly: You pass in jQuery as $ in the IIFE that kicks things off. That's a perfectly good pattern. However, Game just uses $ "raw". So you're sort of half-way following the pattern.

Here's a (simplified) refactored version, just for fun.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks! It's a really nice answer. But: square = @grid[i]?[j] return unless square? looks like magic for me. Is it really nice to use those magic features of a language? \$\endgroup\$ Commented Oct 20, 2014 at 19:35
  • \$\begingroup\$ @kharandziuk ? "absorbs" null/undefined. If you just write @grid[-1][-2] (or some other invalid coordinates) you'll get an error, because @grid[-1] is undefined, and you can't say undefined[-2]. But the ? will protect you from that. In plain JS, it could be written as square = this.grid[i] ? this.grid[i][j] : null followed by if(!square) return \$\endgroup\$
    – Flambino
    Commented Oct 20, 2014 at 19:45
  • \$\begingroup\$ @kharandziuk And yes, it's very nice - when it makes sense. For instance, let's say you want to print a Person object with an optional lastName property, and you want to make the last name uppercase. Then you could do person.lastName?.toUpperCase(). It's just a shorthand for first checking, and then using a property. Check the docs under "The existential operator": "The accessor variant of the existential operator ?. can be used to soak up null references in a chain of properties" \$\endgroup\$
    – Flambino
    Commented Oct 20, 2014 at 19:51
  • \$\begingroup\$ Maybe, I don't understand something: grid[i]? # check that i-th element exists and returns bool and grid[i]?[j] # takes j-th element of bool How does it work? \$\endgroup\$ Commented Oct 20, 2014 at 20:05
  • \$\begingroup\$ @kharandziuk The semantics aren't the same. If you just use foo?, yes, you get a boolean. But, as I quoted from the docs in my earlier comment, the ?. (or in this case ?[]) operator is a variant of that. Something like foo?.bar should be read as "if foo? is true (i.e. foo exists), then access foo's bar property" \$\endgroup\$
    – Flambino
    Commented Oct 20, 2014 at 20:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.