01219245/javascript1/tutorial3/2

จาก Theory Wiki
รุ่นแก้ไขเมื่อ 05:01, 9 กุมภาพันธ์ 2559 โดย Jittat (คุย | มีส่วนร่วม) (→‎Using objects to encapsulate behaviors)
(ต่าง) ←รุ่นแก้ไขก่อนหน้า | รุ่นแก้ไขล่าสุด (ต่าง) | รุ่นแก้ไขถัดไป→ (ต่าง)
ไปยังการนำทาง ไปยังการค้นหา
This is the second part of JavaScript Tutorial 3 of 01219245

Your current JavaScript program in the script block probably look like this:

var numSticks = 21;

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    numSticks -= num;
    $( "#numSticks" ).text( numSticks );
}

$(function(){
    $("#pickButton").click(pickSticks);
});

Valid number of sticks

Let's add the code that check that the number of sticks the player want to take is valid, i.e., it is between 1 to 3 and is no larger than the current number of sticks. Let's assume that the input is an integer for now (i.e., that the user won't enter 1.5). If the amount is invalid, let's throw an alert for now.

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    if ( ( num >= 1 ) && ( num <= 3 ) && ( num <= numSticks ) ) {
        numSticks -= num;
        $( "#numSticks" ).text( numSticks );
    } else {
        alert( "Invalid amount.  Please try again" );
    }
}
Gitmark.png Try to see if the condition works. If it is working, commit your work.

The code is getting harder to read, so let's try to clean it up a bit. To make it clearer, we shall extract the code for validating conditions to a new function. Function pickStick and the new function is shown below:

function isValidPickNum( num, numSticks ) {
    return ( num >= 1 ) && ( num <= 3 ) && ( num <= numSticks );
}

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    if ( isValidPickNum( num, numSticks ) ) {
        numSticks -= num;
        $( "#numSticks" ).text( numSticks );
    } else {
        alert( "Invalid amount.  Please try again" );
    }
}
Gitmark.png Let's commit our work.

Badge.png 2. Show the program to the TA.

A randomized computer player

It is time to introduce the computer player. At this point we just want something that plays, so let's write a simple player that only random a number between 1 to 3. We shall write that as a function and call it in pickSticks

EXERCISE Write function computerPickAmount.

function computerPickAmount() {
    // EXERCISE: write this function
}

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    if ( isValidPickNum( num, numSticks ) ) {
        numSticks -= num;
        $( "#numSticks" ).text( numSticks );

        numSticks -= computerPickAmount();
        $( "#numSticks" ).text( numSticks );
    } else {
        alert( "Invalid amount.  Please try again" );
    }
}

Click here to see the solution when you are done.

function computerPickAmount() {
    return 1 + Math.floor( Math.random() * 3 );
}

Try it to see if the number of sticks decreases more than the number you enter. While this should indicate that computerPickAmount does something. How can we be sure that function computerPickAmount works fine? So let's add a game transcript so that you can see how the game proceeds and also to help us test the code.

Gitmark.png Before we add the transcript, commit the current work.

A transcript

Let's add another HTML element to keep the transcript. Let's use a UL element, so that we have a list. (Add this before the first script block.)

  <ul id="gameTranscript"></ul>

We shall use jQuery method append to add more items to the list. A list item should be an LI element, so we have to enclose our message with <li></li>.

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    if ( isValidPickNum( num, numSticks ) ) {
        numSticks -= num;
        $( "#numSticks" ).text( numSticks );
        $( "#gameTranscript" ).append( "<li>You pick " + num + "</li>" );

        var computerNum = computerPickAmount();
        numSticks -= computerNum;
        $( "#numSticks" ).text( numSticks );
        $( "#gameTranscript" ).append( "<li>Computer picks " + computerNum + "</li>" );
    } else {
        alert( "Invalid amount.  Please try again" );
    }
}

Now, our game should work fine, except for the end game logic.

Gitmark.png Commit your work.

Function pickSticks gets really hard to read again. So let's try to clean it.

We should try to improve readability by hiding low-level codes. The transcript code sections are our easy targets. We shall extract the code out as function addToTranscript so that, for example, both lines that deal with transcript:

        // ...
        $( "#gameTranscript" ).append( "<li>You pick " + num + "</li>" );
        // ...
        $( "#gameTranscript" ).append( "<li>Computer picks " + computerNum + "</li>" );

become

        // ...
        addToTranscript( "You pick " + num );
        // ...
        addToTranscript( "Computer picks " + computerNum );

EXERCISE: Write function addToTranscript.

Click here to see the solution when you are done.

function addToTranscript( msg ) {
    $( "#gameTranscript" ).append( "<li>" + msg + "</li>" );
}
Gitmark.png It's time to commit your work.

Badge.png 3. Show the program to the TA.

Using objects to encapsulate behaviors

When you have a piece of data and you perform some operation on it many times, it is usually a sign that you should create an object.

If you look at our function pickSticks, you might notice that we change the number of sticks and every time we need to update the span element to reflect that change. Let's upgrade variable numSticks and these functions to an object.

A good name goes a long way in making code readable, so let's find a good name for this object. We can start from fairly bad names: numSticks (same name) or s (what?). We can use something like gameState or gameTable, but it seems a little too general. Since we want this object to capture the set of sticks, we may use sticks, stickPile, or gameSticks. Naming is hard, but at some point we have to choose. Let's use gameSticks for now.

Clearly, we only have one method for gameSticks, however, we might add methods like reset later. Here's the code for the object and the updated pickSticks.

var gameSticks = {
    remainingSticks: 21,

    remove: function( n ) {
        this.remainingSticks -= n;
        $( "#numSticks" ).text( this.remainingSticks );
    }
};    

function pickSticks() {
    var num = parseInt( $( "#pickNum" ).val() );
    if ( isValidPickNum( num, gameSticks.remainingSticks ) ) {
        gameSticks.remove( num );
        addToTranscript( "You pick " + num );

        var computerNum = computerPickAmount();
        gameSticks.remove( computerNum );
        addToTranscript( "Computer picks " + computerNum );
    } else {
        alert( "Invalid amount.  Please try again" );
    }
}
Gitmark.png Try to see if the code works, then commit the changes.

Note that we pass gameSticks.remainingSticks to isValidPickNum to check. Should we move isValidPickNum into the object? What do you think?

Click to see discussions.

It is OK to put isValidPickNum into object gameSticks if we think that the rules for the game (i.e., that you can choose from 1 - 3) should be handle by that object. Technically, it will make the call to isValidePickNum a lot nicer because we do not have to look inside gameSticks. However, I do not want to combine too many things into an object, so let's leave it as it is for now.

EXERCISE: Since we have gameSticks as an object, let's try to improve its display. Let's show the sticks as sticks (not just a number), i.e., 21 sticks should look like this:

||||||||||||||||||||

Add a method updateHTML that changes the content of the span element to show the current number of sticks. Modify method remove and other part of the code accordingly.

Gitmark.png Does our game look better? Let's commit now.

Badge.png 4. Show the program to the TA.

Exercise: Get the game mechanic done

There are a few things left:

  • Our computer player may pick too many sticks (e.g., when the number of remaining sticks is less than 3).
  • Game never stops even when someone wins.
  • We should be able to restart the game.

Let's finish this small project by implementing these features. Don't try to implement all features at once, try to do that one by one and commit the changes every time you get a unit of work done. Note that for the first feature, you will need to pass gameSticks.remainingSticks to function computerPickAmount.

Gitmark.png Don't forget to commit when you get a unit of work done.

Badge.png 5. Show the working game to the TA.

Exercise: Smart computer player

The last part is to make the computer player smart. There is a strategy that can ensure the player will win. Note that whatever choice one player make, another can make sure to complement the move so that the total number of sticks decrease by 4. Therefore, if a player can pick sticks so that after her move the remaining number of sticks is multiple of 4, then she can keep maintaining that fact and eventually she should be able to reduce that to 0; thus, winning the game.

However, the first player has this winning strategy, as she can pick 1 to reduce from 21 to 20. But we can make our computer player to take the opportunity to win when it has a chance, otherwise, it just plays randomly.

Implement this strategy in computerPickAmount and play with the game. Try to give it a chance to win and see if it can beat you.

Gitmark.png Wow! we are done. Don't forget to commit.

Badge.png 6. You have a great game. Show it to the TA.