Coffescript Callbacks & Functions

2019-08-30 01:03发布

问题:

I have this function and I am trying to use two large functions made up of smaller ones and one while loops. One for when it's the players turn, one when it's the opponent's turn, and a while loop when the player has won. When I run it in the browser, the buttons do not do anything. What is wrong?

$attackButtonClick = (opponent) ->
    if playersTurn and not player.Win
        player.attack(opponent)
        if opponent.currentHealth <= 0
            player.Win = true;
            allOff()
            postBattle(opponent)
        else 
            playersTurn = false;
            opponentTurn(opponent)

$defendButtonClick = (opponent) ->
    if playersTurn and not player.Win
        player.defend()
        appendToBattleOutputBox "<p>Your defense has been doubled for one turn.</p>"
        if opponent.currentHealth <= 0
            player.Win = true;
            allOff()
            return postBattle(opponent)
        else 
            playersTurn = false;
            opponentTurn(opponent)

$useItemButtonClick = (opponent) ->
    if playersTurn and not player.Win
        if $useItemSelection.html().length > 15
            itemBeingUsed = $("select[name='useItemSelection'] option:selected").text()
            switch itemBeingUsed
                when "Book of Spells"
                    player.Items.bookOfSpells.use()
                    if player.Items.bookOfSpells.effect is "burn" or player.Items.bookOfSpells.effect is "poison"
                        appendToBattleOutputBox "<p>You have #{player.Items.bookOfSpells.effect}ed #{opponent.Name}.</p>"
                        if player.Items.bookOfSpells.effect is "burn"
                            opponent.Burned = true;
                        else
                            opponent.Poisoned = true;
                    else
                        appendToBattleOutputBox "<p>You have frozen #{opponent.Name}.</p>"
                        opponent.Frozen = true;
                    player.Items.bookOfSpells.used = true;
                when "Shield Charm"
                    if (player.Items.shieldCharm.used is false)
                        player.Items.shieldCharm.use()
                        appendToBattleOutputBox "<p>You will block the next attack with you shield charm.</p>"
                        player.Items.shieldCharm.used = true;
                when "Normal Potion"
                    player.Items.normalPotion.use()
            if opponent.currentHealth <= 0
                player.Win = true;
                allOff()
                postBattle(opponent)
            else 
                playersTurn = false;
                opponentTurn(opponent)
        else 
            noUsableItemP = $("<p id='noUsableItemP'>You have no usable items. Select another command.</p>")
            $battleCommandPromptDiv.empty()
            $battleCommandPromptDiv.append(noUsableItemP)

battle = (opponent) ->
    console.log(p)
    appendToBattleOutputBox "<p>You make the first move!</p>"
    battleInProgress = yes;
    playersTurn = true;
    playerTurn = (opponent) ->
        $attackButton.click -> $attackButtonClick(opponent)

        $defendButton.click -> $defendButtonClick(opponent)

        $useItemButton.click -> $useItemButtonClick(opponent)

        status.Poison("opponent", opponent) if opponent.Poisoned
        status.Burn("opponent", opponent) if opponent.Burned
        status.Freeze("opponent", opponent) if opponent.Frozen
        opponent.undefend() if opponent.defenseDoubled or opponent.defenseTripled
        refresh(opponent)

    opponentTurn = (opponent) ->
        if not playersTurn and not player.Win
            if rndmNumber(10) > 5
                thisTurnAttack = opponent.attack(opponent.Attack, opponent.Luck)
                player.currentHealth -= thisTurnAttack
                refresh(opponent)
                if thisTurnAttack is 0 then appendToBattleOutputBox "<p class='right'>You have blocked the attack.</p>" else appendToBattleOutputBox "<p class='right'>#{thisTurnAttack} damage has been inflicted upon you.</p>"
            else 
                opponent.defend()
                appendToBattleOutputBox "<p class='right'>#{opponent.Name} has doubled his defense for one turn.</p>"
            player.undefend() if player.defenseDoubled or player.defenseTripled
            player.Poisoned = true if (opponent.Type is "Snake" and rndmNumber(10) > 7)
            player.Burned = true if (opponent.Name is "Salamander" and rndmNumber(10) > 3) or opponent.Name is "Fire Dragon"
            player.Frozen = true if (opponent.Name is "Penguin" and rndmNumber(10) > 5) or (opponent.Name is "Wolf" and rndmNumber(10) > 6) or (opponent.Name is "Polar Bear" and rndmNumber(10) > 4)
            status.Poison("player") if player.Poisoned
            status.Burn("player") if player.Burned
            status.Freeze("player") if player.Frozen
            if player.currentHealth <= 0 then postBattle(opponent) else playerTurn(opponent)
            playersTurn = true;

    playerTurn(opponent)

    while (player.Win is true)
        player.Win is false;
        postBattle(opponent)

P.S. disregard the semicolons after true and false. I know they aren't supposed to be there.


UPDATE

Now my problem is that when I try calling opponentTurn it is not defined. I have tried looking up a way to hoist a function in Coffeescript, but have found nothing. Is there a way to hoist the function or just another way to order the code?

$attackButtonClick = (opponent) ->
    console.log(playersTurn)
    if playersTurn and not player.Win
        player.attack(opponent)
        if opponent.currentHealth <= 0
            player.Win = true;
            allOff()
            postBattle(opponent)
        else 
            playersTurn = false;
            opponentTurn(opponent)

$defendButtonClick = (opponent) ->
    if playersTurn and not player.Win
        player.defend()
        appendToBattleOutputBox "<p>Your defense has been doubled for one turn.</p>"
        if opponent.currentHealth <= 0
            player.Win = true;
            allOff()
            return postBattle(opponent)
        else 
            playersTurn = false;
            opponentTurn(opponent)

$useItemButtonClick = (opponent) ->
    if playersTurn and not player.Win
        if $useItemSelection.html().length > 15
            itemBeingUsed = $("select[name='useItemSelection'] option:selected").text()
            switch itemBeingUsed
                when "Book of Spells"
                    player.Items.bookOfSpells.use()
                    if player.Items.bookOfSpells.effect is "burn" or player.Items.bookOfSpells.effect is "poison"
                        appendToBattleOutputBox "<p>You have #{player.Items.bookOfSpells.effect}ed #{opponent.Name}.</p>"
                        if player.Items.bookOfSpells.effect is "burn"
                            opponent.Burned = true;
                        else
                            opponent.Poisoned = true;
                    else
                        appendToBattleOutputBox "<p>You have frozen #{opponent.Name}.</p>"
                        opponent.Frozen = true;
                    player.Items.bookOfSpells.used = true;
                when "Shield Charm"
                    if (player.Items.shieldCharm.used is false)
                        player.Items.shieldCharm.use()
                        appendToBattleOutputBox "<p>You will block the next attack with you shield charm.</p>"
                        player.Items.shieldCharm.used = true;
                when "Normal Potion"
                    player.Items.normalPotion.use()
            if opponent.currentHealth <= 0
                player.Win = true;
                allOff()
                postBattle(opponent)
            else 
                playersTurn = false;
                opponentTurn(opponent)
        else 
            noUsableItemP = $("<p id='noUsableItemP'>You have no usable items. Select another command.</p>")
            $battleCommandPromptDiv.empty()
            $battleCommandPromptDiv.append(noUsableItemP)

battle = (opponent) ->
    appendToBattleOutputBox "<p>You make the first move!</p>"
    battleInProgress = yes;
    playerTurn = (opponent) ->
        $attackButton.click -> $attackButtonClick(opponent)

        $defendButton.click -> $defendButtonClick(opponent)

        $useItemButton.click -> $useItemButtonClick(opponent)

        status.Poison("opponent", opponent) if opponent.Poisoned
        status.Burn("opponent", opponent) if opponent.Burned
        status.Freeze("opponent", opponent) if opponent.Frozen
        opponent.undefend() if opponent.defenseDoubled or opponent.defenseTripled
        refresh(opponent)

    opponentTurn = (opponent) ->
        if not playersTurn and not player.Win
            if rndmNumber(10) > 5
                thisTurnAttack = opponent.attack(opponent.Attack, opponent.Luck)
                player.currentHealth -= thisTurnAttack
                refresh(opponent)
                if thisTurnAttack is 0 then appendToBattleOutputBox "<p class='right'>You have blocked the attack.</p>" else appendToBattleOutputBox "<p class='right'>#{thisTurnAttack} damage has been inflicted upon you.</p>"
            else 
                opponent.defend()
                appendToBattleOutputBox "<p class='right'>#{opponent.Name} has doubled his defense for one turn.</p>"
            player.undefend() if player.defenseDoubled or player.defenseTripled
            player.Poisoned = true if (opponent.Type is "Snake" and rndmNumber(10) > 7)
            player.Burned = true if (opponent.Name is "Salamander" and rndmNumber(10) > 3) or opponent.Name is "Fire Dragon"
            player.Frozen = true if (opponent.Name is "Penguin" and rndmNumber(10) > 5) or (opponent.Name is "Wolf" and rndmNumber(10) > 6) or (opponent.Name is "Polar Bear" and rndmNumber(10) > 4)
            status.Poison("player") if player.Poisoned
            status.Burn("player") if player.Burned
            status.Freeze("player") if player.Frozen
            if player.currentHealth <= 0 then postBattle(opponent) else playerTurn(opponent)
            playersTurn = true;

    playerTurn(opponent)

    while (player.Win is true)
        player.Win is false;
        postBattle(opponent)

P.S. disregard the semicolons after true and false. I know they aren't supposed to be there.

回答1:

There's no easy way to explain this. You've fundamentally misunderstood how Javascript (and by extension Coffeescript) works.

Javascript is an asynchronous language which doesn't block on io. When you do things like this

while (playerTurn is true and player.Win is false)
    $attackButton.click ->
        player.attack(opponent)
        ...

You're entering a loop which registers a callback for a click event and then runs the loop again. This is happening, hundreds, probably thousands of times because of the nature of your while loop. That's why your browser has crashed.

A good way to check this kind of thing out is to use debugging statements with console.log, this way you'll be able to see how often different sections of your code are being entered by looking at the number of printed outputs in your console.

You need to restructure the logic of your game, so that rather than doing while loops and waiting for blocking conditions to happen; you have a set of functions that call each other when events take place.

What I would suggest is something like this:

attack = ->
  # conditions go here instead
  if playerTurn and not player.Win
    player.attack(opponent)
    if opponent.currentHealth <= 0
      player.Win = true
      allOff()
      # return statements aren't needed inside callback functions
      postBattle(opponent)
    else 
      playerTurn = false;

# not inside a while loop
$attackButton.click attack

You can use this model for any 'event' in your game e.g. a button click or a changed condition etc.


UPDATE

Coffeescript doesn't support function hoisting, so you've only really got two options here. Try to untangle your code so that you can write it out in fashion that doesn't require using functions that haven't yet been defined, or as I prefer to do, use Livescript.

Livescript has a lot of compatibility with Coffeescript and it brings a lot of useful features to the table with a similar syntax. One of the features is function hoisting. Your hoisted function would be declared like this:

function opponentTurn (opponent)
  ...
# rather than
opponentTurn = (opponent) ->
  ... 

Which will respectively compile to

function opponentTurn(opponent){
  ...
}

var opponentTurn;
opponentTurn = function(opponent){
  ...
};

I couldn't recommend Livescript enough as an alternative to Coffeescript.

http://livescript.net/blog/ten-reasons-to-switch-from-coffeescript.html