Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

JavaScript Object-Oriented JavaScript: Challenge Making the Game Interactive playToken() Method Solution

Nothing works and there is console errors

Whenever I press the left arrow key, the console throws this error "Uncaught TypeError: this.activePlayer.activeToken.moveLeft is not a function at Game.handleKeyDown (Game.js:35) at HTMLDocument.<anonymous> (app.js:10)" and whenever I press the right arrow key, the console throws this error "Uncaught TypeError: this.activePlayer.activeToken.moveRight is not a function at Game.handleKeyDown (Game.js:37) at HTMLDocument.<anonymous> (app.js:10)" Here is my code for doing the movement: Token.js:

/**
* Moves html token one column to left
*/
moveLeft () {
    if (this.columnLocation > 0) {
        this.htmlToken.style.left = this.offsetLeft - 76;
        this.columnLocation--;
    }
}
/**
* Moves html token one column to left
*/
moveRight (columns) {
    if (this.columnLocation < columns - 1) {
        this.htmlToken.style.left = this.offsetLeft + 76;
        this.columnLocation++;
    }
}
drop (target, reset) {
    $(this.htmlToken).animate({
        top: (target.y * target.diameter)
    }, 750, 'easeOutBounce', reset);
    this.dropped = true;
}

Game.js:

handleKeyDown (e) {
    if (this.ready) {
        if (e.key === "ArrowLeft") {
            this.activePlayer.activeToken.moveLeft();
        } else if (e.key === "ArrowRight") {
            this.activePlayer.activeToken.moveRight(this.board.columns);
        } else if (e.key === "ArrowDown") {

        }
    }
}
playToken () {
    let spaces = this.board.spaces;
    let activeToken = this.activePlayer.activeToken;
    let targetColumn = spaces[activeToken.columnLocation];
    let targetSpace = null;
    for (let space of targetColumn) {
        if (space.token === null) {
            targetSpace = space;
        }
    }

    if (targetSpace !== null) {
        game.ready = false;
        activeToken.drop(targetSpace);
    }
}

Could someone tell me what is wrong? Thanks.

5 Answers

Torben Korb
seal-mask
PLUS
.a{fill-rule:evenodd;}techdegree seal-36
Torben Korb
Front End Web Development Techdegree Graduate 90,071 Points

There is a repository with all steps from the videos. Best is to download and compare for the exact step you're working on.

I would assume that you get an error once you click the "start game" button, because you call in game.start() the method to drawHtmlToken on the activeToken on the activePlayer and this methods doesn't exist. So maybe your code has some silent errors before and it never reaches that state.

Spaces should be rendered but as long there is no drawHtmlToken method called there should also be no token rendered to the screen. Maybe you have some versioned files with different codes? Can you share a screenshot of the board?

Nevermind, I figured it out, thanks for your help though.

Torben Korb
seal-mask
PLUS
.a{fill-rule:evenodd;}techdegree seal-36
Torben Korb
Front End Web Development Techdegree Graduate 90,071 Points

Hi babyoscar,

all of this functions should be methods on some class. This should be the Game class living in Game.js.

In your examples you've put all functions in some global scope but it should be inside the Game class to make it work:

class Game {
    moveLeft() { ... }
    moveRight(columns) { ... }
    ... and so on ...
}

Hope this helps. Happy coding!

Those are actually in the Game class, but I just took the methods for moving left and moving right into here. The full code for Game.js is below:

class Game {
    constructor() {
        this.board = new Board();
        this.players = this.createPlayers();
        this.ready = false;
    }

    /** 
     * Creates two player objects
     * @return  {array}    An array of two player objects.
     */
    createPlayers() {
        const players = [new Player('Player 1', 1, '#e15258', true),
                         new Player('Player 2', 2, '#e59a13')];
        return players;
    }
    /*
     * Gets game ready for play
    */
    startGame() {
        this.board.drawHTMLBoard();
        this.activePlayer.activeToken.drawHTMLToken();
        this.ready = true;
    }
    get activePlayer () {
        return this.players.find(players => players.active);
    }
    /**
    * Branches code, depending on what key player presses
    * @param   {Object}    e - Keydown event object
    */
    handleKeyDown (e) {
        if (this.ready) {
            if (e.key === "ArrowLeft") {
                this.activePlayer.activeToken.moveLeft();
            } else if (e.key === "ArrowRight") {
                this.activePlayer.activeToken.moveRight(this.board.columns);
            } else if (e.key === "ArrowDown") {

            }
        }
    }
    playToken () {
        let spaces = this.board.spaces;
        let activeToken = this.activePlayer.activeToken;
        let targetColumn = spaces[activeToken.columnLocation];
        let targetSpace = null;
        for (let space of targetColumn) {
            if (space.token === null) {
                targetSpace = space;
            }
        }

        if (targetSpace !== null) {
            game.ready = false;
            activeToken.drop(targetSpace);
        }
    }
}
Torben Korb
seal-mask
PLUS
.a{fill-rule:evenodd;}techdegree seal-36
Torben Korb
Front End Web Development Techdegree Graduate 90,071 Points

Hi babyoscar, better you post also full code for Token.js and even Player.js to see better ... or just create a snapshot if working with Workspaces.

Token.js:

class Token {
    constructor (index, owner) {
        this.owner = owner;
        this.id = `token-${index}-${owner.id}`;
        this.dropped = false;
        this.columnLocation = 0;
    }
    get offsetLeft () {
        return this.htmlToken.offsetLeft;
    }
    /**
    * Moves html token one column to left
    */
    moveLeft () {
        if (this.columnLocation > 0) {
            this.htmlToken.style.left = this.offsetLeft - 76;
            this.columnLocation--;
        }
    }
    /**
    * Moves html token one column to left
    */
    moveRight (columns) {
        if (this.columnLocation < columns - 1) {
            this.htmlToken.style.left = this.offsetLeft + 76;
            this.columnLocation++;
        }
    }
    drop (target, reset) {
        $(this.htmlToken).animate({
            top: (target.y * target.diameter)
        }, 750, 'easeOutBounce', reset);
        this.dropped = true;
    }
}

Player.js:

class Player {
    constructor(name, id, color, active = false) {
        this.name = name;
        this.id = id;
        this.color = color;
        this.active = active;
        this.tokens = this.createTokens(21);
    }

   /**
     * Creates token objects for player
     * @param   {integer}   num - Number of token objects to be created
     * @return  {array}     tokens - an arary of new token objects
     */
    createTokens(num) {
        const tokens = [];

        for (let i = 0; i < num; i++) {
            let token = new Token(i, this);
            tokens.push(token);
        }

        return tokens;
    }
    get unusedTokens () {
        return this.tokens.filter(token => !token.dropped);
    }
    get activeToken () {
        return this.unusedTokens[0];
    }
}
Torben Korb
seal-mask
PLUS
.a{fill-rule:evenodd;}techdegree seal-36
Torben Korb
Front End Web Development Techdegree Graduate 90,071 Points

Ok, you're referencing this.htmlToken in Token.js but this one is never defined. There should be a getter with a binding to the corresponding DOM element.

Also you're missing the drawHtmlToken method in Token.js from a video before to create that DOM element and append it to the current DOM.

  1. Okay, but I don't know what to do then because the solution has the same code for that part.
  2. Okay, but why would the token still show up then?
Torben Korb
seal-mask
PLUS
.a{fill-rule:evenodd;}techdegree seal-36
Torben Korb
Front End Web Development Techdegree Graduate 90,071 Points

Best is if you share your full app ... can you make it available in GitHub or in Workspaces?

You wrote you shifted around a couple of methods, so maybe you created the drawHtmlToken somewhere else ... and if so you would need to reference it in this place.

Are there any other console errors before you click arrow keys? Because it says "this.activePlayer.activeToken.moveLeft is not a function" it could be that Token throws error while initializing or somewhere in between.

I'm using notepad++ so I can't take a snapshot, so here is all of my files: Board.js

class Board {
    constructor() {
        this.rows = 6;
        this.columns = 7;
        this.spaces = this.createSpaces();
    }

    /** 
     * Generates 2D array of spaces. 
     * @return  {array}     An array of space objects
     */
    createSpaces() {
        const spaces = [];

        for (let x = 0; x < this.columns; x++) {
            const col = [];

            for (let y = 0; y < this.rows; y++) {
                const space = new Space(x, y);
                col.push(space);
            }

            spaces.push(col);
        }

        return spaces;
    }
    drawHTMLBoard() {
        for (let column of this.spaces) {
            for (let space of column) { 
                space.drawSVGSpace();
            }
        }
    }
}

Game.js

class Game {
    constructor() {
        this.board = new Board();
        this.players = this.createPlayers();
        this.ready = false;
    }

    /** 
     * Creates two player objects
     * @return  {array}    An array of two player objects.
     */
    createPlayers() {
        const players = [new Player('Player 1', 1, '#e15258', true),
                         new Player('Player 2', 2, '#e59a13')];
        return players;
    }
    /*
     * Gets game ready for play
    */
    startGame() {
        this.board.drawHTMLBoard();
        this.activePlayer.activeToken.drawHTMLToken();
        this.ready = true;
    }
    get activePlayer () {
        return this.players.find(players => players.active);
    }
    /**
    * Branches code, depending on what key player presses
    * @param   {Object}    e - Keydown event object
    */
    handleKeyDown (e) {
        if (this.ready) {
            if (e.key === "ArrowLeft") {
                this.activePlayer.activeToken.moveLeft();
            } else if (e.key === "ArrowRight") {
                this.activePlayer.activeToken.moveRight(this.board.columns);
            } else if (e.key === "ArrowDown") {

            }
        }
    }
    playToken () {
        let spaces = this.board.spaces;
        let activeToken = this.activePlayer.activeToken;
        let targetColumn = spaces[activeToken.columnLocation];
        let targetSpace = null;
        for (let space of targetColumn) {
            if (space.token === null) {
                targetSpace = space;
            }
        }

        if (targetSpace !== null) {
            game.ready = false;
            activeToken.drop(targetSpace);
        }
    }
}

Token.js

class Token {
    constructor (index, owner) {
        this.owner = owner;
        this.id = `token-${index}-${owner.id}`;
        this.dropped = false;
        this.columnLocation = 0;
    }
    get offsetLeft () {
        return this.htmlToken.offsetLeft;
    }
    /**
    * Moves html token one column to left
    */
    moveLeft () {
        if (this.columnLocation > 0) {
            this.htmlToken.style.left = this.offsetLeft - 76;
            this.columnLocation--;
        }
    }
    /**
    * Moves html token one column to left
    */
    moveRight (columns) {
        if (this.columnLocation < columns - 1) {
            this.htmlToken.style.left = this.offsetLeft + 76;
            this.columnLocation++;
        }
    }
    drop (target, reset) {
        $(this.htmlToken).animate({
            top: (target.y * target.diameter)
        }, 750, 'easeOutBounce', reset);
        this.dropped = true;
    }
}

Player.js

class Player {
    constructor(name, id, color, active = false) {
        this.name = name;
        this.id = id;
        this.color = color;
        this.active = active;
        this.tokens = this.createTokens(21);
    }

   /**
     * Creates token objects for player
     * @param   {integer}   num - Number of token objects to be created
     * @return  {array}     tokens - an arary of new token objects
     */
    createTokens(num) {
        const tokens = [];

        for (let i = 0; i < num; i++) {
            let token = new Token(i, this);
            tokens.push(token);
        }

        return tokens;
    }
    get unusedTokens () {
        return this.tokens.filter(token => !token.dropped);
    }
    get activeToken () {
        return this.unusedTokens[0];
    }
}

Space.js

class Space {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.id = `space-${x}-${y}`;
        this.token = null;
        this.diameter = 76;
        this.radius = this.diameter/2;
    }
    drawSVGSpace() {
        const svgSpace = document.createElementNS("http://www.w3.org/2000/svg", "circle");
        svgSpace.setAttributeNS(null, "id", this.id);
        svgSpace.setAttributeNS(null, "cx", (this.x * this.diameter) + this.radius);
        svgSpace.setAttributeNS(null, "cy", (this.y * this.diameter) + this.radius);
        svgSpace.setAttributeNS(null, "r", this.radius - 8);
        svgSpace.setAttributeNS(null, "fill", "black");
        svgSpace.setAttributeNS(null, "stroke", "none");
        document.getElementById("mask").appendChild(svgSpace);  
    }
}

app.js

const game = new Game();
const start = document.getElementById('begin-game')
start.addEventListener("click", () => {
    game.startGame();
    start.style.display = 'none';
    document.getElementById('play-area').style.opacity = '1';
})

document.addEventListener('keydown', (event) => {
    game.handleKeyDown(event);
})

Also, to your second question, no, there is only errors if I press either the left or right arrow key.