UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ Lab 13

Background

Overview

This lab is subject to modification until the scheduled start time

When we left off Lab 12, we had a method toString to print our World object as a simple ascii-art map. The general plan is to write a loop that updates the world (i.e. moving some critters around), and then prints it out again.

Implimenting and testing World.turn()

Time
25 minutes
Activity
Small groups
             it("turn",
                function () {
                    let count=0;
                    spyOn(world, 'letAct').and.callFake(function(critter,vector) {count++;});
                    world.turn();
                    expect(count).toBe(4);
                });
             it("checkDestination",
                function () {
                    expect(world.checkDestination({direction: 's'},
                                                  new life.Vector(19,1))).______________________________;
                    expect(world.checkDestination({direction: 'n'},
                                                  new life.Vector(0,0))).toBe(undefined);
                });
             it("letAct",
                function () {
                    let src=new life.Vector(19,1);
                    let dest=_____________________;
                    let bob=world.grid.get(src);
                    spyOn(bob,'act').and.returnValue({type: 'move', direction: 's'});
                    world.letAct(bob, src);
                    expect(world.grid.get(dest)).toEqual(bob);
                });

View objects

Time
25 minutes
Activity
Small groups

A view object encapsulates a particular creature and a current location.

describe("View",
         function () {
             let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});
             let position=new Vector(15,9);
             it("constructor",
                function () {
                    let view=new View(world, position);
                    expect(view.vector).toEqual(position);
                });
         });
             it("look",
                function () {
                    let view=new View(world, position);
                    expect(view.look("s"))._________;
                });
             it("findAll",
                function () {
                    let view=new View(world, position);
                    let directionNames = [ 'e', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w' ];
                    expect(view.findAll(" ")_______).toEqual(directionNames);
                });
             it("find",
              function () {
                    let view=new View(world, position);
                    expect(view.find(" ")).toBe('s');
              });

Timers

Time
30 minutes
Activity
Demo, group discussion

In this section we look at some of the simplest examples of asynchronous programming in JavaScript. This means that rather than running functions directly, we let the runtime invoke them later triggered by some event.

In order to have some crude simulation of animation, we want to clear the terminal screen between printing the map. It turns out that for most terminals, it suffices to print out the ANSI escape "ESC c" to reset the terminal.

Our first attempt at animation is

let str="";
for (let i=0; i<60; i++) {
    console.log('\033c');
    str+= "*";
    console.log(str);
}

console.log("all done!");

Something is not quite as desired here; in particular if we move the console.log outside the loop, the visual effect is the same. The obvious answer is to delay between iterations. Unlike some programming languages, JavaScript does not define a sleep builtin to pause the current thread. This is mainly because there is only one thread in JavaScript (including node.js). Instead there is built-in suport for timers. Perhaps the simplest (or at least the shortest) way to get what we want is to use setTimeout like the following example:

function loop(i,str) {
    if (i>0) {
        console.log("\033c");
        console.log(str);
        setTimeout(function() { loop(i-1, str+"*"); }, 1000);
    }
}

loop(20,"*");

console.log("all done!");

Something is not quite right here either with the printing of "all done!". Let's trace out a smaller example like loop(3,"*") on the board to try and understand what's going on.

If you don't like the recursive style of the previous example, you can use setInterval, but notice you have to explicitely cancel the timer, or it runs forever.

function animate(iterations) {
    let i=0;
    let str="";
    let timer = null;
    function frame() {
        i++;  str += "#";
        console.log('\033c');
        console.log(str);
        if (i>=iterations)
            clearInterval(timer);
    }
    timer=setInterval(frame,300);
}

animate(20);

Where should we add console.log("all done!")?

Putting the Pieces to together

Time
20 minutes
Activity
Small groups
    let life=require("./life.js");

    let plan = ["############################",
                "#      #    #      o      ##",
                "#                          #",
                "#          #####           #",
                "##         #   #    ##     #",
                "###           ##     #     #",
                "#           ###      #     #",
                "#   ####                   #",
                "#   ##       o             #",
                "# o  #         o       ### #",
                "#    #                     #",
                "############################"];

    let world = new life.World(plan, {"#": life.Wall, "o": life.BouncingCritter});

    for (let i = 0; i < 5; i++) {
        world.turn();
        console.log(world.toString());
    }

On your own

You will notice that the set of tests we left off with does not have complete coverage.

Here are some partial tests to complete coverage. Figure out which test group the belong in (by looking at the output of nyc jasmine) and add them.

The first one is relatively easy, it's an uncovered line.

    it("look outside",
       function () {
           let position=new Vector(    ,    );
           let view=new View(world, position);
           expect(view.look("s")).toBe("#");
       });

Things then get tricker, because it's specific branches of ifs that are not being tested.

    it("find nonexistent",
       function () {
           let view=new View(world, position);
           spyOn(Math, 'random').and.returnValue(0.5);
           expect(view.find("@")).toBe(   );
       });
    it("act, unclear path, find returns null",
       function () {
           let unclear = {look: function () {return "#";}, find: function () { return     }};
           expect(bob.act(unclear)).toEqual({type: "move", direction: "s"});
       });
    it("letAct, act returns null",
       function () {
           let src=new life.Vector(19,1);
           let dest=new life.Vector(19,2);
           let bob=world.grid.get(src);
           spyOn(bob,'act').and.returnValue(   );
           world.letAct(bob, src);
           expect(world.grid.get(dest)).toBe(   );
       });

If you get all of those tests to pass, that should leave you with a single branch uncovered. See if you can construct a test to cover it.