Tamagotchi-style web app

This is a demo of a ‘literate git’ presentation. See the project page on GitHub for more details.

1 Introduction to web programming

Done some Scratch? Got a lot of useful ideas from that? Let's see how to take similar ideas and create something which runs on the web. We will work towards a Tamagotchi-style game.

In some ways programming on the web is very similar to Scratch --- Scratch has sprites with costumes and scripts. The web-app will have parts that describe what the page looks like (like costumes), and parts which say how it behaves (like scripts).

2 Build basic structure

Our web app will be made of three pieces, each controlling one aspect of the app:

  • An HTML file — the hyper text markup language gives the content of the page, in terms of what text, sections, headings, areas for graphics, and so on there are.
  • A CSS file — the cascading style sheet describes what we want the content to look like.
  • A JS file — the JavaScript is the programming language understood by web browsers, and lets us describe what behaviour we want the web app to have.

We'll set up a basic web app which does very little, but allows us to make sure these three pieces are working correctly together.

code.js

1
$(document).ready(function()
2
{
3
    // Your code JavaScript goes here.
4
});

page.html

1
<html><head>
2
    <title>Tamagotchi</title>
3
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
4
    <script src="code.js"></script>
5
    <link rel="stylesheet" type="text/css" href="style.css"></link>
6
  </head>
7
8
  <body>
9
    <h1>Look after your Tamagotchi!</h1>
10
11
    <canvas id="game" width="480" height="480"></canvas>
12
  </body>
13
</html>

style.css

1
body { background-color: #eee; margin: 24px; }
2
h1 { color: red; }
3
#game { background-color: #ffd; }

2.1 Add bare skeleton of HTML/CSS/JS structure

The web app is made of three parts. The content here is a bare skeleton to get things started. If you're using a web-based system like JSBin, these three pieces are provided for you.

code.js

1
$(document).ready(function()
2
{
3
    // Your code JavaScript goes here.
4
});

page.html

1
<html><head>
2
    <title>Tamagotchi</title>
3
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
4
    <script src="code.js"></script>
5
    <link rel="stylesheet" type="text/css" href="style.css"></link>
6
  </head>
7
8
  <body>
9
    <!-- Your web page HTML goes here. -->
10
  </body>
11
</html>

style.css

1
/* Your web styling CSS goes here. */

2.2 Add header

Notice how HTML uses 'tags' to draw elements, and how elements have matching start and end tags. You could experiment with changing <h1> to <h2> or <h3> or <h4> and see what happens. Or try adding some more text in a paragraph element:

<p>
  ... text goes here ...
</p>

page.html

6 6
  </head>
7 7
8 8
  <body>
9
    <!-- Your web page HTML goes here. -->
9
    <h1>Look after your Tamagotchi!</h1>
10 10
  </body>
11 11
</html>

2.3 Styling the page

You can change how your page content looks using another web language called CSS (which is short for Cascading Style Sheets). It's a series of rules, and each rule says what parts of the web page it applies to, and what to do with those parts. Here, we choose all <h1> pieces, and make them red.

Other styles to experiment with include:

  • font-size
  • color
  • min-height
  • background
  • border
  • margin
  • padding

You can also style the whole page by selecting the <body> in CSS.

style.css

1
/* Your web styling CSS goes here. */
1
h1 { color: red; }

2.4 Add a canvas to draw the Tamagotchi on

The web also allows you to create pictures and well as text. This is done by creating a canvas for the drawing to happen on. The <canvas> element here has a predefined, fixed size on the screen.

But so far we're not drawing anything.

page.html

7 7
8 8
  <body>
9 9
    <h1>Look after your Tamagotchi!</h1>
10
11
    <canvas id="game" width="480" height="480"></canvas>
10 12
  </body>
11 13
</html>

2.5 Make overall background light grey

White is a bit harsh for the background to the whole game. We can add styling to the whole to soften it slightly. This way of describing a colour uses 'Red Green Blue' values.

style.css

1
body { background-color: #eee; }
1 2
h1 { color: red; }

2.6 Make canvas background champagne

We can style the canvas so we can see it better. The #game selector in the CSS lets us talk about the canvas element which has the id="game" attribute in the HTML. This 'identifier' ties them together.

style.css

1 1
body { background-color: #eee; }
2 2
h1 { color: red; }
3
#game { background-color: #ffd; }

2.7 Leave a bit more margin round page

Everything is right up against the edge of the browser window. By adding a 'margin' to the body, we can leave some room.

style.css

1
body { background-color: #eee; }
1
body { background-color: #eee; margin: 24px; }
2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }

3 Draw the egg and make it hatch

The first step is to show the Tamagotchi's egg when the player loads our web app. Then, when the player clicks a button, the egg will hatch into the actual Tamagotchi.

To do this, we'll need to add the button to the HTML, and then write some JavaScript code to do the drawing, and connect them together.

code.js

1 1
$(document).ready(function()
2 2
{
3
    // Your code JavaScript goes here.
3
    var canvas = document.getElementById('game');
4
    var ctx = canvas.getContext('2d');
5
6
    ctx.fillStyle = 'green';
7
    ctx.beginPath();
8
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
9
    ctx.fill();
10
11
    function draw_alien()
12
    {
13
        ctx.clearRect(0, 0, 480, 480);
14
        ctx.fillStyle = 'blue';
15
16
        ctx.beginPath();
17
        ctx.arc(240, 110, 80, 0.0, 2.0 * Math.PI, false);
18
        ctx.fill();
19
20
        ctx.beginPath();
21
        ctx.arc(240, 300, 150, 0.0, 2.0 * Math.PI, false);
22
        ctx.fill();
23
    }
24
25
    $('#hatch').click(draw_alien);
4 26
});

page.html

9 9
    <h1>Look after your Tamagotchi!</h1>
10 10
11 11
    <canvas id="game" width="480" height="480"></canvas>
12
13
    <p>Click here to make it hatch: <button id="hatch">Hatch</button></p>
12 14
  </body>
13 15
</html>

3.1 Get a '2D drawing context' for the canvas

Our drawing is going to be in 2D. You can do 3D drawing, but it's quite a lot more complicated. We find the canvas using the name (id) we gave it, and then ask the canvas for its '2D' drawing 'context'. It's not hugely important right now exactly what a 'context' is, but it's what lets us draw on the page.

The document.getElementById() finds the game element, which is the canvas we created in the HTML earlier.

The dot (.) between the canvas and the getContext is a bit like choosing the getContext option from a right-click menu of things you can do with or to canvas. The () are for making it actually do the set of instructions (function) getContext.

code.js

1 1
$(document).ready(function()
2 2
{
3
    // Your code JavaScript goes here.
3
    var canvas = document.getElementById('game');
4
    var ctx = canvas.getContext('2d');
4 5
});

3.2 Draw Tamagotchi 'egg'

The egg is a simple circle, but we need to know about 'paths' to draw one. You set up a path that an imaginary pen is going to take round the canvas, and then, when it's done, say whether to: draw the outline (with a 'stroke' of the pen) or 'fill' it in. We want a coloured-in circle so will 'fill' it; you can choose a colour, or something more complicated (gradient or pattern) --- we'll stick with a plain colour.

To talk about position on the canvas, everything is done in terms of x and y coordinates --- starting in the top-left corner, how far across should you go (x), and then how far down (y). Annoyingly, this is different to how Scratch does it, where you start in the middle and then go across and up. Scratch's way is how mathematicians do things; most computer graphics is done the way the HTML canvas does it. This is for 'historical reasons'.

code.js

2 2
{
3 3
    var canvas = document.getElementById('game');
4 4
    var ctx = canvas.getContext('2d');
5
6
    ctx.fillStyle = 'green';
7
    ctx.beginPath();
8
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
9
    ctx.fill();
5 10
});

3.3 Add 'Hatch' button and instructions

We can add a button which will start the game using the HTML <button> element. But so far, nothing happens when you click on it, because we haven't connected it to any JavaScript code.

Add styling to the button if you like by adding #hatch {...} to the CSS. The identifier for the button is hatch.

page.html

9 9
    <h1>Look after your Tamagotchi!</h1>
10 10
11 11
    <canvas id="game" width="480" height="480"></canvas>
12
13
    <p>Click here to make it hatch: <button id="hatch">Hatch</button></p>
12 14
  </body>
13 15
</html>

3.4 Say we want to draw the alien when button clicked

We declare that when the button (which we describe by giving its id) is clicked, we want the function (set of instructions) draw_alien to happen. But we haven't yet said what draw_alien means, so it'll look like nothing happens when we click the button. If you bring up the 'developer tools' for your browser, you can see an error: Uncaught ReferenceError: draw_alien is not defined which means you're trying to refer to something but you haven't defined it. We'll do that next.

The current situation is like an imaginary situation in Scratch where you've got a script which says 'when this sprite clicked, jump-up-and-down', but you haven't defined the custom 'jump-up-and-down' block. (This situation is imaginary because Scratch, sensibly, won't let you do this.)

code.js

7 7
    ctx.beginPath();
8 8
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
9 9
    ctx.fill();
10
11
    $('#hatch').click(draw_alien);
10 12
});

3.5 draw_alien(): Add

To actually draw the alien, we'll just use two overlapping blue circles. To say where the circles go, we use coordinates as before. Now it should actually draw the Tamagotchi when you click the 'hatch' button.

code.js

8 8
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
9 9
    ctx.fill();
10 10
11
    function draw_alien()
12
    {
13
        ctx.clearRect(0, 0, 480, 480);
14
        ctx.fillStyle = 'blue';
15
16
        ctx.beginPath();
17
        ctx.arc(240, 110, 80, 0.0, 2.0 * Math.PI, false);
18
        ctx.fill();
19
20
        ctx.beginPath();
21
        ctx.arc(240, 300, 150, 0.0, 2.0 * Math.PI, false);
22
        ctx.fill();
23
    }
24
11 25
    $('#hatch').click(draw_alien);
12 26
});

4 Fade out 'hatch' instructions when game starts

This is fine but the instructions remain once the Tamagotchi has hatched, and that doesn't make sense because you can't hatch the Tamagotchi more than once. To fix this, we want the instructions to go away at the same time as the alien appears.

code.js

22 22
        ctx.fill();
23 23
    }
24 24
25
    $('#hatch').click(draw_alien);
25
    $('#hatch').click(draw_alien_fade_instructions);
26
27
    function fade_out_hatch_instructions()
28
    {
29
        $('#hatch-instructions').fadeOut();
30
    }
31
32
    function draw_alien_fade_instructions()
33
    {
34
        draw_alien();
35
        fade_out_hatch_instructions();
36
    }
26 37
});

page.html

10 10
11 11
    <canvas id="game" width="480" height="480"></canvas>
12 12
13
    <p>Click here to make it hatch: <button id="hatch">Hatch</button></p>
13
    <p id="hatch-instructions">Click here to make it hatch:
14
      <button id="hatch">Hatch</button></p>
14 15
  </body>
15 16
</html>

4.1 Give an 'id' to the 'Hatch' instructions

To be able to get rid of the instructions paragraph, we need to be able to identify it, so give it an id.

page.html

10 10
11 11
    <canvas id="game" width="480" height="480"></canvas>
12 12
13
    <p>Click here to make it hatch: <button id="hatch">Hatch</button></p>
13
    <p id="hatch-instructions">Click here to make it hatch: <button id="hatch">Hatch</button></p>
14 14
  </body>
15 15
</html>

4.2 (Reformat)

It's worth making sure your code is readable, and part of that is not having lines which are too long. There isn't a hard and fast rule for what counts as 'too long', so choose something which works for your development set-up.

page.html

10 10
11 11
    <canvas id="game" width="480" height="480"></canvas>
12 12
13
    <p id="hatch-instructions">Click here to make it hatch: <button id="hatch">Hatch</button></p>
13
    <p id="hatch-instructions">Click here to make it hatch:
14
      <button id="hatch">Hatch</button></p>
14 15
  </body>
15 16
</html>

4.3 fade_out_hatch_instructions(): Add

A new function saying how to make the 'instructions' paragraph fade out. The fadeOut() function is part of the jQuery library we're using and takes care of the animation, timing, etc. Again, nothing happens yet because all we've done is say what fade_out_hatch_instructions means. So far, we haven't told it to do those instructions.

code.js

23 23
    }
24 24
25 25
    $('#hatch').click(draw_alien);
26
27
    function fade_out_hatch_instructions()
28
    {
29
        $('#hatch-instructions').fadeOut();
30
    }
26 31
});

4.4 draw_alien_fade_instructions(): Add

We are defining a lot of functions, but it makes your code easier to read. It might seem that the only thing that matters is that your program works, but it's also very important that somebody reading your code can understand it. Often this is you, a month later.

code.js

28 28
    {
29 29
        $('#hatch-instructions').fadeOut();
30 30
    }
31
32
    function draw_alien_fade_instructions()
33
    {
34
        draw_alien();
35
        fade_out_hatch_instructions();
36
    }
31 37
});

4.5 On 'Hatch', fade instructions too

When the button is clicked, we want the new set of instructions to be done.

code.js

22 22
        ctx.fill();
23 23
    }
24 24
25
    $('#hatch').click(draw_alien);
25
    $('#hatch').click(draw_alien_fade_instructions);
26 26
27 27
    function fade_out_hatch_instructions()
28 28
    {

5 Define function to draw a circle

Take a step back and notice that we're drawing circles three times, and repeating the same slightly fiddly set of instructions each time. When you notice this, it's a good idea to collect those instructions together and give them a name, i.e., define a new function. We've done this before, but this function has something new compared to the ones we've defined before — it's not enough to just say 'draw a circle', you need to know where and how big. The function has extra information it needs before it can do its job. The 'caller' has to supply those extra pieces of information.

That's why our previous functions have () — there's nothing between the () because there is no extra information required.

Once we have this handy way of saying 'draw a circle here and make it this big', we use it to make the rest of our code easier to read.

Note that having made these changes, the app behaves the same, but it is easier to read and understand. This is known as code refactoring.

code.js

3 3
    var canvas = document.getElementById('game');
4 4
    var ctx = canvas.getContext('2d');
5 5
6
    function draw_circle(x, y, r)
7
    {
8
        ctx.beginPath();
9
        ctx.arc(x, y, r, 0.0, 2.0 * Math.PI, false);
10
        ctx.fill();
11
    }
12
6 13
    ctx.fillStyle = 'green';
7
    ctx.beginPath();
8
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
9
    ctx.fill();
14
    draw_circle(240, 240, 180);
10 15
11 16
    function draw_alien()
12 17
    {
13 18
        ctx.clearRect(0, 0, 480, 480);
14 19
        ctx.fillStyle = 'blue';
15 20
16
        ctx.beginPath();
17
        ctx.arc(240, 110, 80, 0.0, 2.0 * Math.PI, false);
18
        ctx.fill();
19
20
        ctx.beginPath();
21
        ctx.arc(240, 300, 150, 0.0, 2.0 * Math.PI, false);
22
        ctx.fill();
21
        draw_circle(240, 110, 80);
22
        draw_circle(240, 300, 150);
23 23
    }
24 24
25 25
    $('#hatch').click(draw_alien_fade_instructions);

5.1 draw_circle(): Add

This function takes three arguments, two to say where the circle should be, and one to say how big it should be. So far we aren't using the function, though, only defining it.

code.js

3 3
    var canvas = document.getElementById('game');
4 4
    var ctx = canvas.getContext('2d');
5 5
6
    function draw_circle(x, y, r)
7
    {
8
        ctx.beginPath();
9
        ctx.arc(x, y, r, 0.0, 2.0 * Math.PI, false);
10
        ctx.fill();
11
    }
12
6 13
    ctx.fillStyle = 'green';
7 14
    ctx.beginPath();
8 15
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);

5.2 Use draw_circle() to draw the egg

To a human reader, this code now makes it clearer what we're doing.

code.js

11 11
    }
12 12
13 13
    ctx.fillStyle = 'green';
14
    ctx.beginPath();
15
    ctx.arc(240, 240, 180, 0.0, 2.0 * Math.PI, false);
16
    ctx.fill();
14
    draw_circle(240, 240, 180);
17 15
18 16
    function draw_alien()
19 17
    {

5.3 Use draw_circle() to draw the hatched Tamagotchi

The gain is bigger when we're drawing the actual alien; it's much clearer for a human reader to see that it's made up of two circles.

code.js

18 18
        ctx.clearRect(0, 0, 480, 480);
19 19
        ctx.fillStyle = 'blue';
20 20
21
        ctx.beginPath();
22
        ctx.arc(240, 110, 80, 0.0, 2.0 * Math.PI, false);
23
        ctx.fill();
24
25
        ctx.beginPath();
26
        ctx.arc(240, 300, 150, 0.0, 2.0 * Math.PI, false);
27
        ctx.fill();
21
        draw_circle(240, 110, 80);
22
        draw_circle(240, 300, 150);
28 23
    }
29 24
30 25
    $('#hatch').click(draw_alien_fade_instructions);

6 Show how happy, healthy, hungry Tamagotchi is

We can now start adding the features which will make a playable game. The player will need to make sure the Tamagotchi stays healthy, happy, and not too hungry. The first step is to give the player some displays which show the current state of the Tamagotchi.

code.js

26 26
27 27
    function fade_out_hatch_instructions()
28 28
    {
29
        $('#hatch-instructions').fadeOut();
29
        $('#hatch-instructions').fadeOut(400, fade_in_levels_display);
30
    }
31
32
    function fade_in_levels_display()
33
    {
34
        $('#levels').fadeIn();
30 35
    }
31 36
32 37
    function draw_alien_fade_instructions()

page.html

12 12
13 13
    <p id="hatch-instructions">Click here to make it hatch:
14 14
      <button id="hatch">Hatch</button></p>
15
16
    <div id="levels">
17
      <p>Health: 100</p>
18
      <p>Happiness: 100</p>
19
      <p>Hungriness: 0</p>
20
    </div>
15 21
  </body>
16 22
</html>

style.css

1 1
body { background-color: #eee; margin: 24px; }
2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }
4
#levels { display: none }

6.1 Add health, happiness, hungriness level displays

Add three <p> elements to the html, to show the Tamagotchi's current levels of health, happiness, and hungriness. It starts off fully healthy and happy, and not at all hungry.

This isn't quite right, because these displays are always visible, even at the very start when only the egg is on the screen. The displays should only appear when the Tamagotchi has hatched; we'll fix that next.

page.html

12 12
13 13
    <p id="hatch-instructions">Click here to make it hatch:
14 14
      <button id="hatch">Hatch</button></p>
15
16
    <p>Health: 100</p>
17
    <p>Happiness: 100</p>
18
    <p>Hungriness: 0</p>
15 19
  </body>
16 20
</html>

6.2 Collect the levels displays into one 'div'

To be able to talk about the displays all as one collection, we group them into a <div> with an id.

page.html

13 13
    <p id="hatch-instructions">Click here to make it hatch:
14 14
      <button id="hatch">Hatch</button></p>
15 15
16
    <p>Health: 100</p>
17
    <p>Happiness: 100</p>
18
    <p>Hungriness: 0</p>
16
    <div id="levels">
17
      <p>Health: 100</p>
18
      <p>Happiness: 100</p>
19
      <p>Hungriness: 0</p>
20
    </div>
19 21
  </body>
20 22
</html>

6.3 Hide the levels displays on start-up

We've been using CSS to control the appearance of various parts of our web-app. One way it allows us to do this it to say that some parts shouldn't appear at all, by saying display: none. Then, within the program, we can make them appear at the right time.

style.css

1 1
body { background-color: #eee; margin: 24px; }
2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }
4
#levels { display: none }

6.4 fade_in_levels_display(): Add

In a very similar way to how we defined a function to fade out the hatch instructions, we can define a function to fade in the displays of health, etc. We refer to the group of displays by the id 'levels' of the <div>.

code.js

29 29
        $('#hatch-instructions').fadeOut();
30 30
    }
31 31
32
    function fade_in_levels_display()
33
    {
34
        $('#levels').fadeIn();
35
    }
36
32 37
    function draw_alien_fade_instructions()
33 38
    {
34 39
        draw_alien();

6.5 Fade in levels display after 'hatch' faded out

The fadeOut function from jQuery can optionally take more arguments (extra pieces of information). The first is how long to make the animation last, and the second is what to do when the animation has finished. Here we add arguments to make the animation last 400ms, and afterwards, call our new fade_in_levels_display function.

Note that we do not put () after fade_in_levels_display. We want to tell fadeOut what to do next, not follow the fade_in_levels_display instructions right away.

code.js

26 26
27 27
    function fade_out_hatch_instructions()
28 28
    {
29
        $('#hatch-instructions').fadeOut();
29
        $('#hatch-instructions').fadeOut(400, fade_in_levels_display);
30 30
    }
31 31
32 32
    function fade_in_levels_display()

7 Make Tamagotchi get hungrier (basic idea)

To make a start on the game mechanics, we'll set up the basic idea of keeping track of the Tamagotchi's changing levels of hunger. We add a variable in the JavaScript, to keep track of how hungry it is, and write a function to update both it and the display. Temporarily, to make sure everything is working, we make the Tamagotchi get 5 steps hungrier after two seconds.

code.js

3 3
    var canvas = document.getElementById('game');
4 4
    var ctx = canvas.getContext('2d');
5 5
6
    var hungriness = 0;
7
8
    function make_alien_hungrier()
9
    {
10
        hungriness += 5;
11
        $('#hungriness').html(hungriness);
12
    }
13
6 14
    function draw_circle(x, y, r)
7 15
    {
8 16
        ctx.beginPath();

20 28
21 29
        draw_circle(240, 110, 80);
22 30
        draw_circle(240, 300, 150);
31
32
        window.setTimeout(make_alien_hungrier, 2000);
23 33
    }
24 34
25 35
    $('#hatch').click(draw_alien_fade_instructions);

page.html

14 14
      <button id="hatch">Hatch</button></p>
15 15
16 16
    <div id="levels">
17
      <p>Health: 100</p>
18
      <p>Happiness: 100</p>
19
      <p>Hungriness: 0</p>
17
      <p>Health: <span id="health">100</span></p>
18
      <p>Happiness: <span id="happiness">100</span></p>
19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21 21
  </body>
22 22
</html>

7.1 Add 'id's to individual level displays

To be able to update the displays, we need to be able to refer to the number part of each one. For this we use an HTML <span> element. This is a bit like a <div> except it doesn't cause a break in the paragraph.

page.html

14 14
      <button id="hatch">Hatch</button></p>
15 15
16 16
    <div id="levels">
17
      <p>Health: 100</p>
18
      <p>Happiness: 100</p>
19
      <p>Hungriness: 0</p>
17
      <p>Health: <span id="health">100</span></p>
18
      <p>Happiness: <span id="happiness">100</span></p>
19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21 21
  </body>
22 22
</html>

7.2 Define variable 'hungriness'

To keep track of the Tamagotchi's hunger levels within the program, we need a variable. When it first hatches, it starts off not at all hungry, so the initial value is zero.

code.js

3 3
    var canvas = document.getElementById('game');
4 4
    var ctx = canvas.getContext('2d');
5 5
6
    var hungriness = 0;
7
6 8
    function draw_circle(x, y, r)
7 9
    {
8 10
        ctx.beginPath();

7.3 make_alien_hungrier(): Add

As time goes by in the game, the Tamagotchi will get hungrier. We'll create a function which does one this. There are two things which we need to do:

  • Update the program's idea of how hungry it is — this means increasing the hungriness variable.
  • Update the web page to show the player the new value — this means putting the new number in as the HTML of the <span> called hungriness.

code.js

5 5
6 6
    var hungriness = 0;
7 7
8
    function make_alien_hungrier()
9
    {
10
        hungriness += 5;
11
        $('#hungriness').html(hungriness);
12
    }
13
8 14
    function draw_circle(x, y, r)
9 15
    {
10 16
        ctx.beginPath();

7.4 Temp: Make alien hungrier 2" after hatching

Just to check that this is all working, we'll set up a timer to make the Tamagotchi get hungrier two seconds after it hatches. We'll get rid of this once we're happy it's all working correctly. At this point, if you hatch the Tamagotchi, you should then see the hungriness go up from 0 to 5 after two seconds.

code.js

28 28
29 29
        draw_circle(240, 110, 80);
30 30
        draw_circle(240, 300, 150);
31
32
        window.setTimeout(make_alien_hungrier, 2000);
31 33
    }
32 34
33 35
    $('#hatch').click(draw_alien_fade_instructions);

8 Make Tamagotchi get hungrier (properly)

Now we know the basic idea works, we'll do this properly. The Tamagotchi will get hungrier every two seconds, until its hungriness reaches 100. At that point, the player has lost the game, and we display a suitable message.

code.js

5 5
6 6
    var hungriness = 0;
7 7
8
    function make_alien_hungrier()
8
    function alien_is_alive()
9 9
    {
10
        hungriness += 5;
11
        $('#hungriness').html(hungriness);
10
        return (hungriness < 100);
12 11
    }
13 12
14 13
    function draw_circle(x, y, r)

28 27
29 28
        draw_circle(240, 110, 80);
30 29
        draw_circle(240, 300, 150);
31
32
        window.setTimeout(make_alien_hungrier, 2000);
33 30
    }
34 31
35 32
    $('#hatch').click(draw_alien_fade_instructions);

48 45
    {
49 46
        draw_alien();
50 47
        fade_out_hatch_instructions();
48
        window.setTimeout(time_goes_by, 2000);
49
    }
50
51
    function make_alien_hungrier()
52
    {
53
        hungriness += 5;
54
        $('#hungriness').html(hungriness);
55
    }
56
57
    function time_goes_by()
58
    {
59
        make_alien_hungrier();
60
61
        if (alien_is_alive())
62
        {
63
            window.setTimeout(time_goes_by, 2000);
64
        }
65
        else
66
        {
67
            $('#messages').html('Oh no!  Your Tamagotchi died!');
68
        }
51 69
    }
52 70
});

page.html

18 18
      <p>Happiness: <span id="happiness">100</span></p>
19 19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21
22
    <p id="messages"></p>
21 23
  </body>
22 24
</html>

8.1 Remove temporary hungriness increase

Now we know the idea works, we will work on making the Tamagotchi get continually hungrier as time goes on.

code.js

28 28
29 29
        draw_circle(240, 110, 80);
30 30
        draw_circle(240, 300, 150);
31
32
        window.setTimeout(make_alien_hungrier, 2000);
33 31
    }
34 32
35 33
    $('#hatch').click(draw_alien_fade_instructions);

8.2 make_alien_hungrier(): Move definition lower

We can see we'll need functions dealing with the Tamagotchi's health and happiness too. It makes more sense to put functions dealing with the Tamagotchi's state of being together.

code.js

5 5
6 6
    var hungriness = 0;
7 7
8
    function make_alien_hungrier()
9
    {
10
        hungriness += 5;
11
        $('#hungriness').html(hungriness);
12
    }
13
14 8
    function draw_circle(x, y, r)
15 9
    {
16 10
        ctx.beginPath();

47 41
        draw_alien();
48 42
        fade_out_hatch_instructions();
49 43
    }
44
45
    function make_alien_hungrier()
46
    {
47
        hungriness += 5;
48
        $('#hungriness').html(hungriness);
49
    }
50 50
});

8.3 time_goes_by(): Add

For now, the only thing that will happen as time goes by is that the Tamagotchi gets hungrier. As we develop the game further, it might also catch a disease (lose health), or become sad (if not played with enough).

But for now, nothing actually happens, because we haven't said that we want time_goes_by() to take place; we've only defined it.

code.js

47 47
        hungriness += 5;
48 48
        $('#hungriness').html(hungriness);
49 49
    }
50
51
    function time_goes_by()
52
    {
53
        make_alien_hungrier();
54
    }
50 55
});

8.4 Make Tamagotchi hungrier 2" after hatching

This is a more natural place to set up this timer.

code.js

40 40
    {
41 41
        draw_alien();
42 42
        fade_out_hatch_instructions();
43
        window.setTimeout(time_goes_by, 2000);
43 44
    }
44 45
45 46
    function make_alien_hungrier()

8.5 Make time keep going by

The setTimeout function sets up a once-off timer. We want game time to keep ticking, so we request a new timer each time.

code.js

52 52
    function time_goes_by()
53 53
    {
54 54
        make_alien_hungrier();
55
56
        window.setTimeout(time_goes_by, 2000);
55 57
    }
56 58
});

8.6 alien_is_alive(): Add

We need to know if the Tamagotchi has got so hungry that it dies of starvation. To check this, we define a new function. This function is different to the ones we've writtem so far, because it gives back a piece of information to the caller. This is known as 'returning' the information. Here, the information is whether the Tamagotchi is still alive, and JavaScript has the idea of a Boolean value for this — a value which is either true or false. In our case, whether the Tamagotchi is alive is worked out by asking 'is its hungriness less than 100?'.

Nothing uses this function yet, though.

code.js

5 5
6 6
    var hungriness = 0;
7 7
8
    function alien_is_alive()
9
    {
10
        return (hungriness < 100);
11
    }
12
8 13
    function draw_circle(x, y, r)
9 14
    {
10 15
        ctx.beginPath();

8.7 If the alien is not alive, stop time

To make the game stop once the Tamagotchi has become too hungry, we only set up another timer if it's still alive.

code.js

58 58
    {
59 59
        make_alien_hungrier();
60 60
61
        window.setTimeout(time_goes_by, 2000);
61
        if (alien_is_alive())
62
        {
63
            window.setTimeout(time_goes_by, 2000);
64
        }
62 65
    }
63 66
});

8.8 Add 'messages' paragraph to HTML

At the start of the game, this <p> is empty. We will use it to show messages from the JavaScript code.

page.html

18 18
      <p>Happiness: <span id="happiness">100</span></p>
19 19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21
22
    <p id="messages"></p>
21 23
  </body>
22 24
</html>

8.9 Show sad message if Tamagotchi dies

We currently check whether the Tamagotchi is still alive, and keep the game going if so. Now we add some code to give the behaviour if the Tamagotchi is not still alive — we show a sad message using our messages paragraph.

code.js

62 62
        {
63 63
            window.setTimeout(time_goes_by, 2000);
64 64
        }
65
        else
66
        {
67
            $('#messages').html('Oh no!  Your Tamagotchi died!');
68
        }
65 69
    }
66 70
});

9 Give player a way to feed the Tamagotchi

So far the game is not very fair, because all that happens is the Tamagotchi gets hungrier and hungrier until it dies. We'll add a button the player can click to feed it some bread, which reduces its hungriness levels.

code.js

33 33
34 34
    function fade_out_hatch_instructions()
35 35
    {
36
        $('#hatch-instructions').fadeOut(400, fade_in_levels_display);
36
        $('#hatch-instructions').fadeOut(400, fade_in_playing_sections);
37 37
    }
38 38
39
    function fade_in_levels_display()
39
    function fade_in_playing_sections()
40 40
    {
41 41
        $('#levels').fadeIn();
42
        $('#action-buttons').fadeIn();
42 43
    }
43 44
44 45
    function draw_alien_fade_instructions()

54 55
        $('#hungriness').html(hungriness);
55 56
    }
56 57
58
    function feed_alien_some_bread()
59
    {
60
        hungriness -= 20;
61
        if (hungriness < 0)
62
        {
63
            hungriness = 0;
64
        }
65
66
        $('#hungriness').html(hungriness);
67
    }
68
69
    $('#feed-bread').click(feed_alien_some_bread);
70
57 71
    function time_goes_by()
58 72
    {
59 73
        make_alien_hungrier();

page.html

19 19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21 21
22
    <p id="action-buttons">
23
      <button id="feed-bread">Bread</button>
24
    </p>
25
22 26
    <p id="messages"></p>
23 27
  </body>
24 28
</html>

style.css

2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }
4 4
#levels { display: none }
5
#action-buttons { display: none;
6
                  width: 480px; background-color: #ddf; text-align: center; }
7
#action-buttons button { margin: 24px; }

9.1 Add button to give bread to Tamagotchi

We'll add a way for the player to feed the Tamagotchi. Looking ahead a bit, we'll need to add more than one button, so we'll add a <p> to hold them all, and add it below the levels displays

Then, we'll use the HTML <button> element to create the actual button.

This doesn't look right yet, because it should be hidden at the start of the game. We'll fix that next.

page.html

19 19
      <p>Hungriness: <span id="hungriness">0</span></p>
20 20
    </div>
21 21
22
    <p id="action-buttons">
23
      <button id="feed-bread">Bread</button>
24
    </p>
25
22 26
    <p id="messages"></p>
23 27
  </body>
24 28
</html>

9.2 Hide action-buttons bar at start-up

Using the same idea as for the levels display, we don't want to display the action buttons at the start of the game.

style.css

2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }
4 4
#levels { display: none }
5
#action-buttons { display: none; }

9.3 Fade in action buttons with levels display

We add a second call to fadeIn, for the action-buttons element. The two fade-in animations happen at the same time, which is appropriate. (If we wanted one to fade in and then the other, we'd add extra arguments to the first fadeIn call, like we did to make the displays fade in after the hatch instructions had finished fading out.)

code.js

39 39
    function fade_in_levels_display()
40 40
    {
41 41
        $('#levels').fadeIn();
42
        $('#action-buttons').fadeIn();
42 43
    }
43 44
44 45
    function draw_alien_fade_instructions()

9.4 Rename fade_in_levels_display()

Another small piece of refactoring: Rename this function to fade_in_playing_sections(). This is a better summary of what it does, and makes the code easier to read and understand.

code.js

33 33
34 34
    function fade_out_hatch_instructions()
35 35
    {
36
        $('#hatch-instructions').fadeOut(400, fade_in_levels_display);
36
        $('#hatch-instructions').fadeOut(400, fade_in_playing_sections);
37 37
    }
38 38
39
    function fade_in_levels_display()
39
    function fade_in_playing_sections()
40 40
    {
41 41
        $('#levels').fadeIn();
42 42
        $('#action-buttons').fadeIn();

9.5 Style the action-buttons bar

The default appearance of the button is a bit drab, so add a small bit of styling to fix this.

style.css

2 2
h1 { color: red; }
3 3
#game { background-color: #ffd; }
4 4
#levels { display: none }
5
#action-buttons { display: none; }
5
#action-buttons { display: none;
6
                  width: 480px; background-color: #ddf; text-align: center; }

9.6 Add some room round the actual action button

The strip of action buttons would look better with some room at the top and bottom of the actual button. Add this with a margin. The CSS specifier means 'any button within the action-buttons element, to make sure we don't accidentally affect other buttons.

style.css

4 4
#levels { display: none }
5 5
#action-buttons { display: none;
6 6
                  width: 480px; background-color: #ddf; text-align: center; }
7
#action-buttons button { margin: 24px; }

9.7 feed_alien_some_bread(): Add

When the player feeds the Tamagotchi some bread, the hungriness should go down. Shortly we will connect this function to the 'Bread' button.

code.js

55 55
        $('#hungriness').html(hungriness);
56 56
    }
57 57
58
    function feed_alien_some_bread()
59
    {
60
        hungriness -= 20;
61
        $('#hungriness').html(hungriness);
62
    }
63
58 64
    function time_goes_by()
59 65
    {
60 66
        make_alien_hungrier();

9.8 Trigger feed_alien_some_bread() on button click

Connect the 'bread' button (identified by its id) to the new function we just wrote.

code.js

61 61
        $('#hungriness').html(hungriness);
62 62
    }
63 63
64
    $('#feed-bread').click(feed_alien_some_bread);
65
64 66
    function time_goes_by()
65 67
    {
66 68
        make_alien_hungrier();

9.9 Make sure hungriness doesn't become negative

There's a bug! You can make the Tamagotchi very UN-hungry by repeatedly feeding it bread. The hungriness can become very negative, which doesn't make sense. We'll fix this now.

After reducing the hungriness, we check whether we've reduced it so far that it's become less than zero. If so, change it to be zero.

code.js

58 58
    function feed_alien_some_bread()
59 59
    {
60 60
        hungriness -= 20;
61
        if (hungriness < 0)
62
        {
63
            hungriness = 0;
64
        }
65
61 66
        $('#hungriness').html(hungriness);
62 67
    }
63 68

10 Allow choice of feeding bread or sweets

So far the player can only feed bread to the Tamagotchi. We will give the player the choice of feeding sweets instead, by adding another button and writing a new function. We can then notice that the bread-feeding function and the sweets-feeding function are very similar, so we can refactor by writing a general function to feed either food.

code.js

55 55
        $('#hungriness').html(hungriness);
56 56
    }
57 57
58
    function feed_alien_some_bread()
58
    function feed_alien(hungriness_reduction)
59 59
    {
60
        hungriness -= 20;
60
        hungriness -= hungriness_reduction;
61 61
        if (hungriness < 0)
62 62
        {
63 63
            hungriness = 0;

66 66
        $('#hungriness').html(hungriness);
67 67
    }
68 68
69
    function feed_alien_some_bread()
70
    {
71
        feed_alien(20);
72
    }
73
69 74
    $('#feed-bread').click(feed_alien_some_bread);
70 75
76
    function feed_alien_sweets()
77
    {
78
        feed_alien(10);
79
    }
80
81
    $('#feed-sweets').click(feed_alien_sweets);
82
71 83
    function time_goes_by()
72 84
    {
73 85
        make_alien_hungrier();

page.html

21 21
22 22
    <p id="action-buttons">
23 23
      <button id="feed-bread">Bread</button>
24
      <button id="feed-sweets">Sweets</button>
24 25
    </p>
25 26
26 27
    <p id="messages"></p>

10.1 Add button to give Tamagotchi sweets

Within our paragraph of 'action buttons', add a 'Sweets' button with its own id.

page.html

21 21
22 22
    <p id="action-buttons">
23 23
      <button id="feed-bread">Bread</button>
24
      <button id="feed-sweets">Sweets</button>
24 25
    </p>
25 26
26 27
    <p id="messages"></p>

10.2 feed_alien_sweets(): Add

We can copy-and-paste the code from the feed_alien_some_bread() function to describe what to do to feed the Tamagotchi sweets. The only difference is that sweets only reduce the hungriness by 10, not 20.

This is untidy, though, and makes our code more difficult to maintain and change, so we will fix this near-repetition shortly.

code.js

68 68
69 69
    $('#feed-bread').click(feed_alien_some_bread);
70 70
71
    function feed_alien_sweets()
72
    {
73
        hungriness -= 10;
74
        if (hungriness < 0)
75
        {
76
            hungriness = 0;
77
        }
78
79
        $('#hungriness').html(hungriness);
80
    }
71 81
    function time_goes_by()
72 82
    {
73 83
        make_alien_hungrier();

10.3 Connect 'sweets' button to feed_alien_sweets()

This is familiar by now — declare that we want the feed_alien_sweets function to be run when the 'Sweets' button is clicked.

code.js

78 78
79 79
        $('#hungriness').html(hungriness);
80 80
    }
81
82
    $('#feed-sweets').click(feed_alien_sweets);
83
81 84
    function time_goes_by()
82 85
    {
83 86
        make_alien_hungrier();

10.4 feed_alien(): Add

We can extract the common behaviour between the very similar functions

  • feed_alien_some_bread()
  • feed_alien_sweets()

and at the same time notice that the only difference is the reduction in hungriness that happens. So we include an argument which will allow the caller to provide this piece of information.

code.js

55 55
        $('#hungriness').html(hungriness);
56 56
    }
57 57
58
    function feed_alien(hungriness_reduction)
59
    {
60
        hungriness -= hungriness_reduction;
61
        if (hungriness < 0)
62
        {
63
            hungriness = 0;
64
        }
65
66
        $('#hungriness').html(hungriness);
67
    }
68
58 69
    function feed_alien_some_bread()
59 70
    {
60 71
        hungriness -= 20;

10.5 Simplify alien-feeding functions

We can now get rid of the near-duplicated code between the two feeding functions, and instead just call our new feed_alien() function.

code.js

68 68
69 69
    function feed_alien_some_bread()
70 70
    {
71
        hungriness -= 20;
72
        if (hungriness < 0)
73
        {
74
            hungriness = 0;
75
        }
76
77
        $('#hungriness').html(hungriness);
71
        feed_alien(20);
78 72
    }
79 73
80 74
    $('#feed-bread').click(feed_alien_some_bread);
81 75
82 76
    function feed_alien_sweets()
83 77
    {
84
        hungriness -= 10;
85
        if (hungriness < 0)
86
        {
87
            hungriness = 0;
88
        }
89
90
        $('#hungriness').html(hungriness);
78
        feed_alien(10);
91 79
    }
92 80
93 81
    $('#feed-sweets').click(feed_alien_sweets);

11 Stop the player hammering the feeding buttons

At the moment, the player can just keep clicking the Bread and Sweets buttons as quickly as they like, which makes the game too easy. We will make it so the feeding buttons are disabled for a short while after being clicked.

Because we want this behaviour for both buttons, we'll write a function for it, and then use it for both the buttons.

code.js

66 66
        $('#hungriness').html(hungriness);
67 67
    }
68 68
69
    function temporarily_disable(button_id)
70
    {
71
        $(button_id).attr('disabled', true);
72
        window.setTimeout(
73
            function() { $(button_id).attr('disabled', false); },
74
            3000);
75
    }
76
69 77
    function feed_alien_some_bread()
70 78
    {
71 79
        feed_alien(20);
80
        temporarily_disable('#feed-bread');
72 81
    }
73 82
74 83
    $('#feed-bread').click(feed_alien_some_bread);

76 85
    function feed_alien_sweets()
77 86
    {
78 87
        feed_alien(10);
88
        temporarily_disable('#feed-sweets');
79 89
    }
80 90
81 91
    $('#feed-sweets').click(feed_alien_sweets);

11.1 Disable 'bread' button after clicked

As part of what happens when the player clicks the 'Bread' button, we disable that button. This is done by setting the disabled attribute to true.

code.js

69 69
    function feed_alien_some_bread()
70 70
    {
71 71
        feed_alien(20);
72
73
        // The player can't immediately give more bread
74
        $('#feed-bread').attr('disabled', true);
72 75
    }
73 76
74 77
    $('#feed-bread').click(feed_alien_some_bread);

11.2 enable_bread_button(): Add

Disabling the button is a good start, but we'll need to re-anable it again after a delay. Start by creating a function which turns off the disabled attribute.

code.js

55 55
        $('#hungriness').html(hungriness);
56 56
    }
57 57
58
    function enable_bread_button()
59
    {
60
        $('#feed-bread').attr('disabled', false);
61
    }
62
58 63
    function feed_alien(hungriness_reduction)
59 64
    {
60 65
        hungriness -= hungriness_reduction;

11.3 Re-enable bread button after 3" delay

Immediately after disabling the button, set a timer so that our new function is called after a 3-second delay.

code.js

77 77
78 78
        // The player can't immediately give more bread
79 79
        $('#feed-bread').attr('disabled', true);
80
        //
81
        // but after a few seconds have gone by, they can.
82
        window.setTimeout(enable_bread_button, 3000);
80 83
    }
81 84
82 85
    $('#feed-bread').click(feed_alien_some_bread);

11.4 temporarily_disable(): Add

We are going to want to apply the same 'disable then re-enable' behaviour to the 'feed sweets' button, so make this into a function. One wrinkle is that we can't (directly) define a general equivalent of the enable_bread_button() function, so instead we'll use an anonymous function in the setTimeout() call.

Our new function needs to know which button to work with, so this is the argument — callers will pass something like '#feed-bread'.

code.js

71 71
        $('#hungriness').html(hungriness);
72 72
    }
73 73
74
    function temporarily_disable(button_id)
75
    {
76
        $(button_id).attr('disabled', true);
77
        window.setTimeout(
78
            function() { $(button_id).attr('disabled', false); },
79
            3000);
80
    }
81
74 82
    function feed_alien_some_bread()
75 83
    {
76 84
        feed_alien(20);

11.5 Simplify feed_alien_some_bread()

We can now replace the disable/enable logic in feed_alien_some_bread() with a call to our new temporarily_disable() function.

code.js

82 82
    function feed_alien_some_bread()
83 83
    {
84 84
        feed_alien(20);
85
86
        // The player can't immediately give more bread
87
        $('#feed-bread').attr('disabled', true);
88
        //
89
        // but after a few seconds have gone by, they can.
90
        window.setTimeout(enable_bread_button, 3000);
85
        temporarily_disable('#feed-bread');
91 86
    }
92 87
93 88
    $('#feed-bread').click(feed_alien_some_bread);

11.6 Remove now-unused enable_bread_button()

We are no longer using the enable_bread_button() function, so remove it.

code.js

55 55
        $('#hungriness').html(hungriness);
56 56
    }
57 57
58
    function enable_bread_button()
59
    {
60
        $('#feed-bread').attr('disabled', false);
61
    }
62
63 58
    function feed_alien(hungriness_reduction)
64 59
    {
65 60
        hungriness -= hungriness_reduction;

11.7 Temporarily disable sweets button too

It's now easy to use our temporarily_disable() function to give the behaviour we want to the Sweets button.

code.js

85 85
    function feed_alien_sweets()
86 86
    {
87 87
        feed_alien(10);
88
        temporarily_disable('#feed-sweets');
88 89
    }
89 90
90 91
    $('#feed-sweets').click(feed_alien_sweets);

12 Sweets are not healthy

As we all know, it's bad for you to eat too many sweets. We'll add code to keep track of how healthy the Tamagotchi is, and make it so that sweets reduce its health.

If the Tamagotchi becomes too unhealthy, it dies and you lose the game.

code.js

4 4
    var ctx = canvas.getContext('2d');
5 5
6 6
    var hungriness = 0;
7
    var health = 100;
7 8
8 9
    function alien_is_alive()
9 10
    {
10
        return (hungriness < 100);
11
        return (hungriness < 100) && (health > 0);
11 12
    }
12 13
13 14
    function draw_circle(x, y, r)

86 87
    {
87 88
        feed_alien(10);
88 89
        temporarily_disable('#feed-sweets');
90
91
        // Sweets are not healthy:
92
        health -= 5;
93
        $('#health').html(health);
94
95
        if ( ! alien_is_alive())
96
        {
97
            game_over_lost();
98
        }
89 99
    }
90 100
91 101
    $('#feed-sweets').click(feed_alien_sweets);
92 102
103
    function game_over_lost()
104
    {
105
        $('#messages').html('Oh no!  Your Tamagotchi died!');
106
    }
107
93 108
    function time_goes_by()
94 109
    {
95 110
        make_alien_hungrier();

100 115
        }
101 116
        else
102 117
        {
103
            $('#messages').html('Oh no!  Your Tamagotchi died!');
118
            game_over_lost();
104 119
        }
105 120
    }
106 121
});

12.1 Add 'health' variable

We need a variable so that the JavaScript code can keep track of the Tamagotchi's health. We'll define this near the top, where 'hungriness' is already defined.

code.js

4 4
    var ctx = canvas.getContext('2d');
5 5
6 6
    var hungriness = 0;
7
    var health = 100;
7 8
8 9
    function alien_is_alive()
9 10
    {

12.2 Make sweets reduce the Tamagotchi's health

As part of what happens when the player feeds the Tamagotchi some sweets, we want to reduce its health. In the same way as we handled the hungriness, there are two parts:

  • Update the health variable.
  • Update the displays in the web page with the new value.

There is a TODO to remind us to do something: in this case, we want to check whether the Tamagotchi is so unhealthy that it dies. We'll fill this in later.

code.js

87 87
    {
88 88
        feed_alien(10);
89 89
        temporarily_disable('#feed-sweets');
90
91
        // Sweets are not healthy:
92
        health -= 5;
93
        $('#health').html(health);
94
95
        // TODO: Make the alien die if all health gone.
90 96
    }
91 97
92 98
    $('#feed-sweets').click(feed_alien_sweets);

12.3 game_over_lost(): Add

There's already one way the player can lose the game: if the Tamagotchi gets too hungry. Shortly there's going to be another way: if the Tamagotchi becomes too unhealthy. We'll move the 'lose game' behaviour into its own function, for re-use shortly.

code.js

97 97
98 98
    $('#feed-sweets').click(feed_alien_sweets);
99 99
100
    function game_over_lost()
101
    {
102
        $('#messages').html('Oh no!  Your Tamagotchi died!');
103
    }
104
100 105
    function time_goes_by()
101 106
    {
102 107
        make_alien_hungrier();

12.4 Use new game_over_lost() function

In time_goes_by(), use the new function. This is a refactoring, as no behaviour is changed. Instead we're making the code easier to work with and understand.

code.js

112 112
        }
113 113
        else
114 114
        {
115
            $('#messages').html('Oh no!  Your Tamagotchi died!');
115
            game_over_lost();
116 116
        }
117 117
    }
118 118
});

12.5 Tamagotchi needs positive 'health' to be alive

The && means 'and' — the alien is alive if it's not too hungry and it's healthy enough. This sort of works, except the game doesn't notice the alien is too unhealthy straight away. We will fix this next.

code.js

8 8
9 9
    function alien_is_alive()
10 10
    {
11
        return (hungriness < 100);
11
        return (hungriness < 100) && (health > 0);
12 12
    }
13 13
14 14
    function draw_circle(x, y, r)

12.6 Lose game if Tamagotchi loses all its health

We can now do our TODO. If the alien isn't alive, show the game-over message straight away, without waiting for the next time_goes_by() call. The exclamation mark ! means 'not'. I've put spaces round it to make sure a human reader notices it.

This isn't perfect, as the hungriness ticks up one more time after the game-over message appears, but we look at fixing that later.

code.js

92 92
        health -= 5;
93 93
        $('#health').html(health);
94 94
95
        // TODO: Make the alien die if all health gone.
95
        if ( ! alien_is_alive())
96
        {
97
            game_over_lost();
98
        }
96 99
    }
97 100
98 101
    $('#feed-sweets').click(feed_alien_sweets);

13 Improve end-of-game behaviour

To fix the problem noted previously, where the Tamagotchi gets hungrier after dying through sickness, we need to check whether the game is still running before doing anything which changes the state of the game.

code.js

11 11
        return (hungriness < 100) && (health > 0);
12 12
    }
13 13
14
    function game_is_running()
15
    {
16
        return alien_is_alive();
17
    }
18
14 19
    function draw_circle(x, y, r)
15 20
    {
16 21
        ctx.beginPath();

77 82
78 83
    function feed_alien_some_bread()
79 84
    {
85
        if ( ! game_is_running())
86
            return;
87
80 88
        feed_alien(20);
81 89
        temporarily_disable('#feed-bread');
82 90
    }

85 93
86 94
    function feed_alien_sweets()
87 95
    {
96
        if ( ! game_is_running())
97
            return;
98
88 99
        feed_alien(10);
89 100
        temporarily_disable('#feed-sweets');
90 101

107 118
108 119
    function time_goes_by()
109 120
    {
121
        if ( ! game_is_running())
122
            return;
123
110 124
        make_alien_hungrier();
111 125
112 126
        if (alien_is_alive())

13.1 game_is_running(): Add

Looking ahead a bit, the player will be able to win the game by nurturing their Tamagotchi to old age. We therefore need a function which tells us whether the game is still running. (The alternative being that it has stopped, either through the player losing or through the player winning.)

code.js

11 11
        return (hungriness < 100) && (health > 0);
12 12
    }
13 13
14
    function game_is_running()
15
    {
16
        return alien_is_alive();
17
    }
18
14 19
    function draw_circle(x, y, r)
15 20
    {
16 21
        ctx.beginPath();

13.2 Do not update game state if game is not running

Once the game has stopped, clicking on buttons shouldn't do anything, and the 'time goes by' function shouldn't do anything either. To achieve this, we check whether game_is_running() as the first thing we do on entering these functions:

  • feed_alien_some_bread()
  • feed_alien_sweets()
  • time_goes_by()

If the game isn't running, we return straight away, which means we don't do any more of the instructions in that function.

code.js

82 82
83 83
    function feed_alien_some_bread()
84 84
    {
85
        if ( ! game_is_running())
86
            return;
87
85 88
        feed_alien(20);
86 89
        temporarily_disable('#feed-bread');
87 90
    }

90 93
91 94
    function feed_alien_sweets()
92 95
    {
96
        if ( ! game_is_running())
97
            return;
98
93 99
        feed_alien(10);
94 100
        temporarily_disable('#feed-sweets');
95 101

112 118
113 119
    function time_goes_by()
114 120
    {
121
        if ( ! game_is_running())
122
            return;
123
115 124
        make_alien_hungrier();
116 125
117 126
        if (alien_is_alive())

14 Random illnesses

Occasionally, the Tamagotchi gets sick just through bad luck. We already have a function to make things happen every 'tick' of the game clock, so we can add a 'randomly make sicker' feature to it. The randomness comes from JavaScript's Math.random() function, and to keep the code easy to read, we'll put this into a separate new function.

code.js

61 61
        $('#hungriness').html(hungriness);
62 62
    }
63 63
64
    function maybe_make_alien_sicker()
65
    {
66
        if (Math.random() < 0.1)
67
        {
68
            health -= 40;
69
            if (health < 0)
70
            {
71
                health = 0;
72
            }
73
74
            $('#health').html(health);
75
76
            if ( ! alien_is_alive())
77
            {
78
                game_over_lost();
79
            }
80
        }
81
    }
82
64 83
    function feed_alien(hungriness_reduction)
65 84
    {
66 85
        hungriness -= hungriness_reduction;

122 141
            return;
123 142
124 143
        make_alien_hungrier();
144
        maybe_make_alien_sicker();
125 145
126 146
        if (alien_is_alive())
127 147
        {

14.1 maybe_make_alien_sicker(): Add skeleton

We will fill in the details in a minute, but first we set up the structure of how to do something randomly. JavaScript provides a Math.random() function, which gives you a random number between 0 and 1 each time you call it. By testing whether this random number is less than 0.1 (i.e., 1/10), we can make something happen about one time in ten that we call the function.

code.js

61 61
        $('#hungriness').html(hungriness);
62 62
    }
63 63
64
    function maybe_make_alien_sicker()
65
    {
66
        if (Math.random() < 0.1)
67
        {
68
            // TODO: Make alien sicker
69
        }
70
    }
71
64 72
    function feed_alien(hungriness_reduction)
65 73
    {
66 74
        hungriness -= hungriness_reduction;

14.2 Knock lots of health off if Tamagotchi gets sick

This has the various parts we've dealt with before:

  • Adjust the health variable.
  • Make sure the health is not less than zero.
  • Update the web page to display the new health.

code.js

65 65
    {
66 66
        if (Math.random() < 0.1)
67 67
        {
68
            // TODO: Make alien sicker
68
            health -= 40;
69
            if (health < 0)
70
            {
71
                health = 0;
72
            }
73
74
            $('#health').html(health);
69 75
        }
70 76
    }
71 77

14.3 Randomly make Tamagotchi sicker as time goes by

To actually make the Tamagotchi occasionally become sick, we just have to call our new maybe_make_alien_sicker() function from within the time_goes_by() function. About every 20 seconds, on average, the Tamagotchi will get sick.

code.js

136 136
            return;
137 137
138 138
        make_alien_hungrier();
139
        maybe_make_alien_sicker();
139 140
140 141
        if (alien_is_alive())
141 142
        {

14.4 Check Tamagotchi not dead after getting sicker

The final piece is to check whether this bout of sickness was fatal for the Tamagotchi, and display the you lost message if so.

code.js

72 72
            }
73 73
74 74
            $('#health').html(health);
75
76
            if ( ! alien_is_alive())
77
            {
78
                game_over_lost();
79
            }
75 80
        }
76 81
    }
77 82

15 Allow player to give medicine to the Tamagotchi

It's a bit unfair on the player if the alien keeps getting sicker but the player can't do anything about it. We'll add a Medicine button which restores some of its health.

code.js

80 80
        }
81 81
    }
82 82
83
    function make_healthier()
84
    {
85
        health += 40;
86
        if (health > 100)
87
        {
88
            health = 100;
89
        }
90
91
        $('#health').html(health);
92
    }
93
83 94
    function feed_alien(hungriness_reduction)
84 95
    {
85 96
        hungriness -= hungriness_reduction;

130 141
131 142
    $('#feed-sweets').click(feed_alien_sweets);
132 143
144
    function give_alien_medicine()
145
    {
146
        if ( ! game_is_running())
147
            return;
148
149
        make_healthier();
150
        temporarily_disable('#give-medicine');
151
    }
152
153
    $('#give-medicine').click(give_alien_medicine);
154
133 155
    function game_over_lost()
134 156
    {
135 157
        $('#messages').html('Oh no!  Your Tamagotchi died!');

page.html

22 22
    <p id="action-buttons">
23 23
      <button id="feed-bread">Bread</button>
24 24
      <button id="feed-sweets">Sweets</button>
25
      <button id="give-medicine">Medicine</button>
25 26
    </p>
26 27
27 28
    <p id="messages"></p>

15.1 Add 'medicine' button

Add another button to the 'action buttons' bar, giving it a unique id.

page.html

22 22
    <p id="action-buttons">
23 23
      <button id="feed-bread">Bread</button>
24 24
      <button id="feed-sweets">Sweets</button>
25
      <button id="give-medicine">Medicine</button>
25 26
    </p>
26 27
27 28
    <p id="messages"></p>

15.2 make_healthier(): Add

This has the same components we've seen a few times in other settings:

  • Update the JavaScript variable health.
  • Check it hasn't gone outside its bounds — we can't have health more than 100, so set to 100 if it tries to.
  • Update the web-page display.

Here, the medicine gives back 40 health points, but you can experiment with this value and see what makes a good game.

code.js

80 80
        }
81 81
    }
82 82
83
    function make_healthier()
84
    {
85
        health += 40;
86
        if (health > 100)
87
        {
88
            health = 100;
89
        }
90
91
        $('#health').html(health);
92
    }
93
83 94
    function feed_alien(hungriness_reduction)
84 95
    {
85 96
        hungriness -= hungriness_reduction;

15.3 give_alien_medicine(): Add

Create a function which does the two things we need to do when the player clicks the Medicine button:

  • Make the Tamagotchi healthier.
  • Disable the Medicine button for a few seconds.

code.js

141 141
142 142
    $('#feed-sweets').click(feed_alien_sweets);
143 143
144
    function give_alien_medicine()
145
    {
146
        make_healthier();
147
        temporarily_disable('#give-medicine');
148
    }
149
144 150
    function game_over_lost()
145 151
    {
146 152
        $('#messages').html('Oh no!  Your Tamagotchi died!');

15.4 Forbid giving medicine if game not running

If the game isn't running, leave give_alien_medicine() immediately.

code.js

143 143
144 144
    function give_alien_medicine()
145 145
    {
146
        if ( ! game_is_running())
147
            return;
148
146 149
        make_healthier();
147 150
        temporarily_disable('#give-medicine');
148 151
    }

15.5 Connect the 'Medicine' button

Finally we ensure our new function is called when the player clicks the button.

code.js

150 150
        temporarily_disable('#give-medicine');
151 151
    }
152 152
153
    $('#give-medicine').click(give_alien_medicine);
154
153 155
    function game_over_lost()
154 156
    {
155 157
        $('#messages').html('Oh no!  Your Tamagotchi died!');

16 Create one function to make Tamagotchi sicker

There is a large overlap between the code which reduces the Tamagotchi's health when the player feeds it a sweet, and the code which reduces its health because of random sickness. To make the code easier to work with, we can refactor by pulling that common logic out into a new function.

code.js

61 61
        $('#hungriness').html(hungriness);
62 62
    }
63 63
64
    function make_alien_sicker(health_lost)
65
    {
66
        health -= health_lost;
67
        if (health < 0)
68
        {
69
            health = 0;
70
        }
71
72
        $('#health').html(health);
73
74
        if ( ! alien_is_alive())
75
        {
76
            game_over_lost();
77
        }
78
    }
79
64 80
    function maybe_make_alien_sicker()
65 81
    {
66 82
        if (Math.random() < 0.1)
67 83
        {
68
            health -= 40;
69
            if (health < 0)
70
            {
71
                health = 0;
72
            }
73
74
            $('#health').html(health);
75
76
            if ( ! alien_is_alive())
77
            {
78
                game_over_lost();
79
            }
84
            make_alien_sicker(40);
80 85
        }
81 86
    }
82 87

130 135
        temporarily_disable('#feed-sweets');
131 136
132 137
        // Sweets are not healthy:
133
        health -= 5;
134
        $('#health').html(health);
135
136
        if ( ! alien_is_alive())
137
        {
138
            game_over_lost();
139
        }
138
        make_alien_sicker(5);
140 139
    }
141 140
142 141
    $('#feed-sweets').click(feed_alien_sweets);

16.1 make_alien_sicker(): Add

Copying the common behaviour between maybe_make_alien_sicker() and feed_alien_sweets(), we see that the difference is in how much health the Tamagotchi loses. We therefore make this amount be an argument to our new function.

One other difference is that we never checked for health < 0 in feed_alien_sweets(), which we got away with because health was always a multiple of 5. It's no harm to check, though, and in future we might not always have health being a multiple of 5.

code.js

61 61
        $('#hungriness').html(hungriness);
62 62
    }
63 63
64
    function make_alien_sicker(health_lost)
65
    {
66
        health -= health_lost;
67
        if (health < 0)
68
        {
69
            health = 0;
70
        }
71
72
        $('#health').html(health);
73
74
        if ( ! alien_is_alive())
75
        {
76
            game_over_lost();
77
        }
78
    }
79
64 80
    function maybe_make_alien_sicker()
65 81
    {
66 82
        if (Math.random() < 0.1)

16.2 maybe_make_alien_sicker(): Simplify

All the logic is now inside make_alien_sicker(), so call that (with argument 40, because that's the amount of health we knock off when the Tamagotchi gets a random illness).

code.js

81 81
    {
82 82
        if (Math.random() < 0.1)
83 83
        {
84
            health -= 40;
85
            if (health < 0)
86
            {
87
                health = 0;
88
            }
89
90
            $('#health').html(health);
91
92
            if ( ! alien_is_alive())
93
            {
94
                game_over_lost();
95
            }
84
            make_alien_sicker(40);
96 85
        }
97 86
    }
98 87

16.3 feed_alien_sweets(): Simplify

Again, use the new function, passing 5 as the amount of health to lose.

code.js

135 135
        temporarily_disable('#feed-sweets');
136 136
137 137
        // Sweets are not healthy:
138
        health -= 5;
139
        $('#health').html(health);
140
141
        if ( ! alien_is_alive())
142
        {
143
            game_over_lost();
144
        }
138
        make_alien_sicker(5);
145 139
    }
146 140
147 141
    $('#feed-sweets').click(feed_alien_sweets);

17 Make player win game if Tamagotchi reaches old age

So far the game only has ways to lose, and not to win. We'll make it so that the player wins the game if the Tamagotchi reaches the ripe old age of 100. This will involve adding an 'age' display, and keeping track of the age via a JavaScript variable. The age needs to increase as time goes by, and we need to check whether the Tamagotchi becomes old enough to tell the player they've won.

code.js

5 5
6 6
    var hungriness = 0;
7 7
    var health = 100;
8
    var age = 0;
8 9
9 10
    function alien_is_alive()
10 11
    {
11 12
        return (hungriness < 100) && (health > 0);
12 13
    }
13 14
15
    function alien_still_growing()
16
    {
17
        return (age < 100);
18
    }
19
14 20
    function game_is_running()
15 21
    {
16
        return alien_is_alive();
22
        return alien_is_alive() && alien_still_growing();
17 23
    }
18 24
19 25
    function draw_circle(x, y, r)

96 102
        $('#health').html(health);
97 103
    }
98 104
105
    function make_alien_older()
106
    {
107
        age += 1;
108
        $('#age').html(age);
109
    }
110
99 111
    function feed_alien(hungriness_reduction)
100 112
    {
101 113
        hungriness -= hungriness_reduction;

156 168
        $('#messages').html('Oh no!  Your Tamagotchi died!');
157 169
    }
158 170
171
    function game_over_won()
172
    {
173
        $('#messages').html('Well done!  Your Tamagotchi reached old age!');
174
    }
175
159 176
    function time_goes_by()
160 177
    {
161 178
        if ( ! game_is_running())

163 180
164 181
        make_alien_hungrier();
165 182
        maybe_make_alien_sicker();
183
        make_alien_older();
166 184
167
        if (alien_is_alive())
185
        if ( ! alien_still_growing())
168 186
        {
169
            window.setTimeout(time_goes_by, 2000);
187
            game_over_won();
170 188
        }
171
        else
189
        else if ( ! alien_is_alive())
172 190
        {
173 191
            game_over_lost();
174 192
        }
193
        else
194
        {
195
            window.setTimeout(time_goes_by, 2000);
196
        }
175 197
    }
176 198
});

page.html

14 14
      <button id="hatch">Hatch</button></p>
15 15
16 16
    <div id="levels">
17
      <p id="age-para">Age: <span id="age">0</span> hours</p>
17 18
      <p>Health: <span id="health">100</span></p>
18 19
      <p>Happiness: <span id="happiness">100</span></p>
19 20
      <p>Hungriness: <span id="hungriness">0</span></p>

style.css

5 5
#action-buttons { display: none;
6 6
                  width: 480px; background-color: #ddf; text-align: center; }
7 7
#action-buttons button { margin: 24px; }
8
#age-para { font-weight: bold; }

17.1 Add 'age' display

When first hatched, the Tamagotchi is zero hours old. The whole <p> has an id, as well as the <span> containing the value, because we'll want to apply styling to this whole line of the display.

page.html

14 14
      <button id="hatch">Hatch</button></p>
15 15
16 16
    <div id="levels">
17
      <p id="age-para">Age: <span id="age">0</span> hours</p>
17 18
      <p>Health: <span id="health">100</span></p>
18 19
      <p>Happiness: <span id="happiness">100</span></p>
19 20
      <p>Hungriness: <span id="hungriness">0</span></p>

17.2 Add styling for the age display

The 'age' is a bit like the score for the game, so it should be more prominent. Make the text bold. You could add any other styling here too (e.g., colours).

style.css

5 5
#action-buttons { display: none;
6 6
                  width: 480px; background-color: #ddf; text-align: center; }
7 7
#action-buttons button { margin: 24px; }
8
#age-para { font-weight: bold; }

17.3 Add 'age' JavaScript variable

The program will need to keep track of how old the Tamagotchi is, so add a variable age. It starts at zero, matching the display.

code.js

5 5
6 6
    var hungriness = 0;
7 7
    var health = 100;
8
    var age = 0;
8 9
9 10
    function alien_is_alive()
10 11
    {

17.4 make_alien_older(): Add

Every 'tick' of the game, the Tamagotchi will get 'one hour' older. Make a function to do this, which needs to update two things:

  • The age variable.
  • The display.

code.js

97 97
        $('#health').html(health);
98 98
    }
99 99
100
    function make_alien_older()
101
    {
102
        age += 1;
103
        $('#age').html(age);
104
    }
105
100 106
    function feed_alien(hungriness_reduction)
101 107
    {
102 108
        hungriness -= hungriness_reduction;

17.5 Make Tamagotchi get older each 'tick' of the game

To actually make the Tamagotchi get older, we need to call our new make_alien_older() on each 'tick' of the game, i.e., within time_goes_by().

(One slightly annoying thing is that it says '1 hours', rather than '1 hour', but we'll ignore that.)

code.js

170 170
171 171
        make_alien_hungrier();
172 172
        maybe_make_alien_sicker();
173
        make_alien_older();
173 174
174 175
        if (alien_is_alive())
175 176
        {

17.6 game_over_won(): Add

Make a function to announce that the player has won, to go along with the existing game_over_lost() function for when they lose.

code.js

163 163
        $('#messages').html('Oh no!  Your Tamagotchi died!');
164 164
    }
165 165
166
    function game_over_won()
167
    {
168
        $('#messages').html('Well done!  Your Tamagotchi reached old age!');
169
    }
170
166 171
    function time_goes_by()
167 172
    {
168 173
        if ( ! game_is_running())

17.7 Check whether Tamagotchi has reached winning age

After updating the age, we need to check if the player has won. This needs to fit in with the 'has the player lost?' check, so we'll rearrange this part of time_goes_by() a bit.

For testing, it's a good idea to use a smaller number than 100 (maybe 10), otherwise it takes an annoyingly long time to check it's working.

If, by coincidence, the alien reaches old age at the same time as getting to zero health (say), we give the player the benefit of the doubt and call it a win. We do this by checking the age before the alien_is_alive() check.

code.js

177 177
        maybe_make_alien_sicker();
178 178
        make_alien_older();
179 179
180
        if (alien_is_alive())
180
        if (age == 100)
181 181
        {
182
            window.setTimeout(time_goes_by, 2000);
182
            game_over_won();
183 183
        }
184
        else
184
        else if ( ! alien_is_alive())
185 185
        {
186 186
            game_over_lost();
187 187
        }
188
        else
189
        {
190
            window.setTimeout(time_goes_by, 2000);
191
        }
188 192
    }
189 193
});

17.8 alien_still_growing(): Add

A function which lets us tell whether the alien is still growing. (The alternative is that it's reached old age.)

code.js

12 12
        return (hungriness < 100) && (health > 0);
13 13
    }
14 14
15
    function alien_still_growing()
16
    {
17
        return (age < 100);
18
    }
19
15 20
    function game_is_running()
16 21
    {
17 22
        return alien_is_alive();

17.9 Declare game not running at old age

When the Tamagotchi reaches old age, the game should stop running. We add a condition to game_is_running() — for the game to be running, the alien needs to be alive and still growing.

code.js

19 19
20 20
    function game_is_running()
21 21
    {
22
        return alien_is_alive();
22
        return alien_is_alive() && alien_still_growing();
23 23
    }
24 24
25 25
    function draw_circle(x, y, r)

17.10 Use alien_still_growing() in time_goes_by()

We don't want to have two places in the code where we decide whether the alien has reached old age. This kind of thing inevitably leads to trouble when you change things in one place and not the other. To avoid this, use the alien_still_growing() function to decide whether the player has won — if it's not still growing, the player has won.

code.js

182 182
        maybe_make_alien_sicker();
183 183
        make_alien_older();
184 184
185
        if (age == 100)
185
        if ( ! alien_still_growing())
186 186
        {
187 187
            game_over_won();
188 188
        }

18 Move levels display to right of Tamagotchi

On a short screen, things don't quite fit vertically. The title, the Tamagotchi canvas, the levels display, the action buttons, and the messages take up so much room that the player needs to scroll to see it all, which is annoying. We'll move the levels display to the right of the Tamagotchi, giving a more useful layout.

page.html

8 8
  <body>
9 9
    <h1>Look after your Tamagotchi!</h1>
10 10
11
    <div id="game-container">
11 12
    <canvas id="game" width="480" height="480"></canvas>
12 13
13 14
    <p id="hatch-instructions">Click here to make it hatch:

19 20
      <p>Happiness: <span id="happiness">100</span></p>
20 21
      <p>Hungriness: <span id="hungriness">0</span></p>
21 22
    </div>
23
    </div>
22 24
23 25
    <p id="action-buttons">
24 26
      <button id="feed-bread">Bread</button>

style.css

1 1
body { background-color: #eee; margin: 24px; }
2 2
h1 { color: red; }
3
#game-container { position: relative; }
3 4
#game { background-color: #ffd; }
4
#levels { display: none }
5
#levels { display: none; position: absolute; left: 520px; top: 0px; }
5 6
#action-buttons { display: none;
6 7
                  width: 480px; background-color: #ddf; text-align: center; }
7 8
#action-buttons button { margin: 24px; }

18.1 Wrap Tamagotchi and levels in a <div>

This produces no change yet, but we need the container <div> to make the positioning work correctly.

page.html

8 8
  <body>
9 9
    <h1>Look after your Tamagotchi!</h1>
10 10
11
    <div id="game-container">
11 12
    <canvas id="game" width="480" height="480"></canvas>
12 13
13 14
    <p id="hatch-instructions">Click here to make it hatch:

19 20
      <p>Happiness: <span id="happiness">100</span></p>
20 21
      <p>Hungriness: <span id="hungriness">0</span></p>
21 22
    </div>
23
    </div>
22 24
23 25
    <p id="action-buttons">
24 26
      <button id="feed-bread">Bread</button>

18.2 Position levels to right of Tamagotchi

The full rules for positioning using CSS are quite complex, but for this case, the idea is:

  • Make the container have relative position. By itself this does nothing, but means that the game-container div is used as a reference for where any contained absolute-positioned elements go.
  • Make the levels display be positioned absolutely, with its left edge just beyond the right edge of the <canvas> (which is 480px wide), and with its top edge level with the top of the canvas.

In fact the 'top' of the levels display includes its margin, so the top of the text doesn't quite line up. We'll put up with this.

style.css

1 1
body { background-color: #eee; margin: 24px; }
2 2
h1 { color: red; }
3
#game-container { position: relative; }
3 4
#game { background-color: #ffd; }
4
#levels { display: none }
5
#levels { display: none; position: absolute; left: 520px; top: 0px; }
5 6
#action-buttons { display: none;
6 7
                  width: 480px; background-color: #ddf; text-align: center; }
7 8
#action-buttons button { margin: 24px; }

19 Implement happiness and boredom

We'll bring in the last part of the display now: happiness. The idea is that the Tamagotchi needs to be played with otherwise it gets bored. If it gets bored it starts losing happiness. The player can play a game with their Tamagotchi by clicking a button.

code.js

5 5
6 6
    var hungriness = 0;
7 7
    var health = 100;
8
    var happiness = 100;
9
    var bored = false;
10
    var last_game_age = 0;
8 11
    var age = 0;
9 12
10 13
    function alien_is_alive()
11 14
    {
12
        return (hungriness < 100) && (health > 0);
15
        return (hungriness < 100) && (health > 0) && (happiness > 0);
13 16
    }
14 17
15 18
    function alien_still_growing()

108 111
        $('#age').html(age);
109 112
    }
110 113
114
    function update_alien_happiness()
115
    {
116
        bored = ((age - last_game_age) >= 3);
117
118
        if (bored)
119
        {
120
            happiness -= 5;
121
            if (happiness < 0) happiness = 0;
122
        }
123
        else
124
        {
125
            happiness += 2;
126
            if (happiness > 100) happiness = 100;
127
        }
128
129
        $('#happiness').html(happiness);
130
    }
131
111 132
    function feed_alien(hungriness_reduction)
112 133
    {
113 134
        hungriness -= hungriness_reduction;

163 184
164 185
    $('#give-medicine').click(give_alien_medicine);
165 186
187
    function play_game()
188
    {
189
        if ( ! game_is_running())
190
            return;
191
192
        last_game_age = age;
193
        temporarily_disable('#play-game');
194
    }
195
196
    $('#play-game').click(play_game);
197
166 198
    function game_over_lost()
167 199
    {
168 200
        $('#messages').html('Oh no!  Your Tamagotchi died!');

181 213
        make_alien_hungrier();
182 214
        maybe_make_alien_sicker();
183 215
        make_alien_older();
216
        update_alien_happiness();
184 217
185 218
        if ( ! alien_still_growing())
186 219
        {

page.html

26 26
      <button id="feed-bread">Bread</button>
27 27
      <button id="feed-sweets">Sweets</button>
28 28
      <button id="give-medicine">Medicine</button>
29
      <button id="play-game">Game</button>
29 30
    </p>
30 31
31 32
    <p id="messages"></p>

19.1 Add variable for happiness

The happiness starts off at 100 — the Tamagotchi is perfectly happy when freshly hatched.

code.js

5 5
6 6
    var hungriness = 0;
7 7
    var health = 100;
8
    var happiness = 100;
8 9
    var age = 0;
9 10
10 11
    function alien_is_alive()

19.2 Add variable for whether Tamagotchi is bored

The bored variable is a Boolean variable — its value is going to be either true (if the Tamagotchi is bored) or false (if it's not). It starts off not bored.

code.js

6 6
    var hungriness = 0;
7 7
    var health = 100;
8 8
    var happiness = 100;
9
    var bored = false;
9 10
    var age = 0;
10 11
11 12
    function alien_is_alive()

19.3 Add variable to track when game last played

The Tamagotchi will get bored if the player hasn't played a game with it for too long. To keep track of this, we'll add a variable to remember how old the Tamagotchi was when the player last played a game with it.

code.js

7 7
    var health = 100;
8 8
    var happiness = 100;
9 9
    var bored = false;
10
    var last_game_age = 0;
10 11
    var age = 0;
11 12
12 13
    function alien_is_alive()

19.4 update_alien_happiness(): Add

Being bored makes the Tamagotchi less happy. If it's not bored, it has a generally positive outlook on life, so its happiness goes up. We need a function to check whether the alien is bored, and then move its happiness up or down accordingly. This function has to do quite a lot.

The main bit is the ((age - last_game_age) >= 3) test. This subtraction works out how long ago the alien last had a game, and if that was three or more hours ago, it's bored. Then we adjust the happiness variable either down (if bored) or up (if not), and update the display. We also check that the happiness doesn't become less than zero or more than 100.

code.js

111 111
        $('#age').html(age);
112 112
    }
113 113
114
    function update_alien_happiness()
115
    {
116
        bored = ((age - last_game_age) >= 3);
117
118
        if (bored)
119
        {
120
            happiness -= 5;
121
            if (happiness < 0) happiness = 0;
122
        }
123
        else
124
        {
125
            happiness += 2;
126
            if (happiness > 100) happiness = 100;
127
        }
128
129
        $('#happiness').html(happiness);
130
    }
131
114 132
    function feed_alien(hungriness_reduction)
115 133
    {
116 134
        hungriness -= hungriness_reduction;

19.5 Check boredom and update happiness every tick

To make this actually happen, we must call our new function on every game tick.

code.js

202 202
        make_alien_hungrier();
203 203
        maybe_make_alien_sicker();
204 204
        make_alien_older();
205
        update_alien_happiness();
205 206
206 207
        if ( ! alien_still_growing())
207 208
        {

19.6 Add 'Game' button

It would be unfair if the player had no way of playing a game with the Tamagotchi, so add an action button for this.

page.html

26 26
      <button id="feed-bread">Bread</button>
27 27
      <button id="feed-sweets">Sweets</button>
28 28
      <button id="give-medicine">Medicine</button>
29
      <button id="play-game">Game</button>
29 30
    </p>
30 31
31 32
    <p id="messages"></p>

19.7 play_game(): Add

When the player clicks the Game button, the code needs to record this fact. The variable last_game_age needs updating to the Tamagotchi's current age. We also include the usual temporary disabling of the button.

code.js

184 184
185 185
    $('#give-medicine').click(give_alien_medicine);
186 186
187
    function play_game()
188
    {
189
        last_game_age = age;
190
        temporarily_disable('#play-game');
191
    }
192
187 193
    function game_over_lost()
188 194
    {
189 195
        $('#messages').html('Oh no!  Your Tamagotchi died!');

19.8 Forbid playing a game if game not running

If the game isn't running, leave play_game() immediately.

code.js

186 186
187 187
    function play_game()
188 188
    {
189
        if ( ! game_is_running())
190
            return;
191
189 192
        last_game_age = age;
190 193
        temporarily_disable('#play-game');
191 194
    }

19.9 Connect play_game() function to button

The last piece is to connect the buttom to the function via a call to click().

code.js

193 193
        temporarily_disable('#play-game');
194 194
    }
195 195
196
    $('#play-game').click(play_game);
197
196 198
    function game_over_lost()
197 199
    {
198 200
        $('#messages').html('Oh no!  Your Tamagotchi died!');

19.10 Lose game if Tamagotchi becomes too sad

Finally, the Tamagotchi dies of a broken heart if it becomes too unhappy, so add another condition to the test in alien_is_alive().

code.js

12 12
13 13
    function alien_is_alive()
14 14
    {
15
        return (hungriness < 100) && (health > 0);
15
        return (hungriness < 100) && (health > 0) && (happiness > 0);
16 16
    }
17 17
18 18
    function alien_still_growing()

20 Give Tamagotchi a face

To make the game look better, we'll give the Tamagotchi two eyes and a smiling mouth.

code.js

42 42
43 43
        draw_circle(240, 110, 80);
44 44
        draw_circle(240, 300, 150);
45
46
        ctx.fillStyle = 'black';
47
        draw_circle(210, 90, 10);
48
        draw_circle(270, 90, 10);
49
50
        ctx.lineWidth = 5.0;
51
        ctx.lineCap = 'round';
52
        ctx.beginPath();
53
        ctx.moveTo(200, 130);
54
        ctx.lineTo(220, 150);
55
        ctx.lineTo(260, 150);
56
        ctx.lineTo(280, 130);
57
        ctx.stroke();
45 58
    }
46 59
47 60
    $('#hatch').click(draw_alien_fade_instructions);

20.1 Draw eyes

After a bit of experimenting, these look like reasonable places for the Tamagotchi's eyes. You can experiment with other colours, or the size, position or even number of eyes.

We can see how our draw_circle() function is getting more use, keeping our code easy to write and read.

code.js

42 42
43 43
        draw_circle(240, 110, 80);
44 44
        draw_circle(240, 300, 150);
45
46
        ctx.fillStyle = 'black';
47
        draw_circle(210, 90, 10);
48
        draw_circle(270, 90, 10);
45 49
    }
46 50
47 51
    $('#hatch').click(draw_alien_fade_instructions);

20.2 Draw smiling mouth

To give the Tamagotchi a mouth, we create a path and then stroke() it. This just gives the outline, not a filled-in shape. To build the path, we move to the starting point, then draw three lines to the other corners in the shape. Experiment with the shape, colour, width, etc.

code.js

46 46
        ctx.fillStyle = 'black';
47 47
        draw_circle(210, 90, 10);
48 48
        draw_circle(270, 90, 10);
49
50
        ctx.lineWidth = 5.0;
51
        ctx.beginPath();
52
        ctx.moveTo(200, 130);
53
        ctx.lineTo(220, 150);
54
        ctx.lineTo(260, 150);
55
        ctx.lineTo(280, 130);
56
        ctx.stroke();
49 57
    }
50 58
51 59
    $('#hatch').click(draw_alien_fade_instructions);

20.3 Make line ends rounded

The mouth looks slightly better if the line-ends are rounded rather than square. This is controlled by the lineCap property of the drawing context.

code.js

48 48
        draw_circle(270, 90, 10);
49 49
50 50
        ctx.lineWidth = 5.0;
51
        ctx.lineCap = 'round';
51 52
        ctx.beginPath();
52 53
        ctx.moveTo(200, 130);
53 54
        ctx.lineTo(220, 150);

21 Make Tamagotchi's appearance show its state

The game will look more appealing, and also the player will have a better chance of keeping an eye on what's going on, if we make the Tamagotchi look different according to how hungry, happy, and healthy it is. We change draw_alien() quite a bit, to show:

  • A sad, bored, or happy mouth.
  • A sickly green colour if it's ill.
  • A hole in its belly as it gets hungrier.

The code needs updating to call draw_alien() at various places, to make sure the appearance always matches the variables and levels display.

code.js

38 38
    function draw_alien()
39 39
    {
40 40
        ctx.clearRect(0, 0, 480, 480);
41
        ctx.fillStyle = 'blue';
41
        if (health >= 50)
42
        {
43
            ctx.fillStyle = 'blue';
44
        }
45
        else
46
        {
47
            ctx.fillStyle = 'rgb(0, 255, 200)';
48
        }
42 49
43 50
        draw_circle(240, 110, 80);
44 51
        draw_circle(240, 300, 150);
45 52
53
        ctx.fillStyle = 'rgb(255, 255, 221)';
54
        draw_circle(240, 300, hungriness);
55
46 56
        ctx.fillStyle = 'black';
47 57
        draw_circle(210, 90, 10);
48 58
        draw_circle(270, 90, 10);

50 60
        ctx.lineWidth = 5.0;
51 61
        ctx.lineCap = 'round';
52 62
        ctx.beginPath();
53
        ctx.moveTo(200, 130);
54
        ctx.lineTo(220, 150);
55
        ctx.lineTo(260, 150);
56
        ctx.lineTo(280, 130);
63
        if (happiness < 50)
64
        {
65
            ctx.moveTo(200, 150);
66
            ctx.lineTo(220, 130);
67
            ctx.lineTo(260, 130);
68
            ctx.lineTo(280, 150);
69
        }
70
        else if (bored)
71
        {
72
            ctx.moveTo(200, 145);
73
            ctx.lineTo(280, 145);
74
        }
75
        else
76
        {
77
            ctx.moveTo(200, 130);
78
            ctx.lineTo(220, 150);
79
            ctx.lineTo(260, 150);
80
            ctx.lineTo(280, 130);
81
        }
57 82
        ctx.stroke();
58 83
    }
59 84

81 106
    {
82 107
        hungriness += 5;
83 108
        $('#hungriness').html(hungriness);
109
        draw_alien();
84 110
    }
85 111
86 112
    function make_alien_sicker(health_lost)

92 118
        }
93 119
94 120
        $('#health').html(health);
121
        draw_alien();
95 122
96 123
        if ( ! alien_is_alive())
97 124
        {

116 143
        }
117 144
118 145
        $('#health').html(health);
146
        draw_alien();
119 147
    }
120 148
121 149
    function make_alien_older()

140 168
        }
141 169
142 170
        $('#happiness').html(happiness);
171
        draw_alien();
143 172
    }
144 173
145 174
    function feed_alien(hungriness_reduction)

151 180
        }
152 181
153 182
        $('#hungriness').html(hungriness);
183
        draw_alien();
154 184
    }
155 185
156 186
    function temporarily_disable(button_id)

203 233
            return;
204 234
205 235
        last_game_age = age;
236
        update_alien_happiness();
206 237
        temporarily_disable('#play-game');
207 238
    }
208 239

21.1 Draw Tamagotchi sickly green if not very healthy

To make the game look more interesting, and also slightly easier, we'll draw the Tamagotchi a different colour when it's sick. The rgb() notation lets you describe a colour using red, green, and blue parts in the same way as the #e3f notation in CSS, except here you give three whole numbers between 0 and 255.

code.js

38 38
    function draw_alien()
39 39
    {
40 40
        ctx.clearRect(0, 0, 480, 480);
41
        ctx.fillStyle = 'blue';
41
        if (health >= 50)
42
        {
43
            ctx.fillStyle = 'blue';
44
        }
45
        else
46
        {
47
            ctx.fillStyle = 'rgb(0, 255, 200)';
48
        }
42 49
43 50
        draw_circle(240, 110, 80);
44 51
        draw_circle(240, 300, 150);

21.2 Re-draw alien when health changes

Whenever we change the health, we need to re-draw the alien, in case it now needs to be a different colour.

code.js

99 99
        }
100 100
101 101
        $('#health').html(health);
102
        draw_alien();
102 103
103 104
        if ( ! alien_is_alive())
104 105
        {

123 124
        }
124 125
125 126
        $('#health').html(health);
127
        draw_alien();
126 128
    }
127 129
128 130
    function make_alien_older()

21.3 Make Tamagotchi look bored if it is

If the Tamagotchi is bored, it shouldn't be smiling. In draw_alien(), we can check whether it's bored, and if so, draw a straight mouth rather than a smiling one.

code.js

57 57
        ctx.lineWidth = 5.0;
58 58
        ctx.lineCap = 'round';
59 59
        ctx.beginPath();
60
        ctx.moveTo(200, 130);
61
        ctx.lineTo(220, 150);
62
        ctx.lineTo(260, 150);
63
        ctx.lineTo(280, 130);
60
        if (bored)
61
        {
62
            ctx.moveTo(200, 145);
63
            ctx.lineTo(280, 145);
64
        }
65
        else
66
        {
67
            ctx.moveTo(200, 130);
68
            ctx.lineTo(220, 150);
69
            ctx.lineTo(260, 150);
70
            ctx.lineTo(280, 130);
71
        }
64 72
        ctx.stroke();
65 73
    }
66 74

21.4 Re-draw Tamagotchi when boredom changes

Whenever the bored variable might change, we should re-draw the alien. Do so inside update_alien_happiness().

code.js

157 157
        }
158 158
159 159
        $('#happiness').html(happiness);
160
        draw_alien();
160 161
    }
161 162
162 163
    function feed_alien(hungriness_reduction)

21.5 Become un-bored immediately when playing a game

When the player clicks the Game button, the Tamagotchi doesn't immediately become non-bored, only on the next game tick. This isn't a good game experience, so update the happiness (which re-draws the Tamagotchi) as part of responding to the Game click. This also gives an immediate happiness boost, which is probably appropriate.

code.js

221 221
            return;
222 222
223 223
        last_game_age = age;
224
        update_alien_happiness();
224 225
        temporarily_disable('#play-game');
225 226
    }
226 227

21.6 Make Tamagotchi look unhappy if too unhappy

We can turn the smile upside down into a sad face if the Tamagotchi is too unhappy. We check this first, so if the Tamagotchi is both very unhappy and bored, it looks sad.

We're already calling draw_alien() when the happiness variable might change, because this is tied in with the bored variable.

code.js

57 57
        ctx.lineWidth = 5.0;
58 58
        ctx.lineCap = 'round';
59 59
        ctx.beginPath();
60
        if (bored)
60
        if (happiness < 50)
61
        {
62
            ctx.moveTo(200, 150);
63
            ctx.lineTo(220, 130);
64
            ctx.lineTo(260, 130);
65
            ctx.lineTo(280, 150);
66
        }
67
        else if (bored)
61 68
        {
62 69
            ctx.moveTo(200, 145);
63 70
            ctx.lineTo(280, 145);

21.7 Draw hole in Tamagotchi's belly to show hunger

Draw a circle on top of the main circle, to make the Tamagotchi look hollow-bellied. The hungrier it is, the bigger the 'hole'. The rgb() values match the background-color: #ffd style for the game element that we used in the CSS.

code.js

50 50
        draw_circle(240, 110, 80);
51 51
        draw_circle(240, 300, 150);
52 52
53
        ctx.fillStyle = 'rgb(255, 255, 221)';
54
        draw_circle(240, 300, hungriness);
55
53 56
        ctx.fillStyle = 'black';
54 57
        draw_circle(210, 90, 10);
55 58
        draw_circle(270, 90, 10);

21.8 Re-draw Tamagotchi when hungriness changes

To make sure the Tamagotchi's appearance matches how hungry it is, we need to re-draw it every time we change the hungriness variable.

code.js

106 106
    {
107 107
        hungriness += 5;
108 108
        $('#hungriness').html(hungriness);
109
        draw_alien();
109 110
    }
110 111
111 112
    function make_alien_sicker(health_lost)

179 180
        }
180 181
181 182
        $('#hungriness').html(hungriness);
183
        draw_alien();
182 184
    }
183 185
184 186
    function temporarily_disable(button_id)

22 Minor gameplay adjustments

After a bit of playing, it seems the game would be better with a few tweaks:

  • The Tamagotchi gets bored too easily, so adjust the value saying how often it must be played with.
  • The Sweets button doesn't really add much to gameplay, so make it so sweets also make the Tamagotchi happier (as well as less hungry and less healthy).
  • If the Tamagotchi is sick, it should become less happy.

code.js

154 154
155 155
    function update_alien_happiness()
156 156
    {
157
        bored = ((age - last_game_age) >= 3);
157
        bored = ((age - last_game_age) >= 5);
158
159
        if (health < 75)
160
        {
161
            if (happiness > 40)
162
            {
163
                happiness -= 5;
164
            }
165
        }
158 166
159 167
        if (bored)
160 168
        {

210 218
        feed_alien(10);
211 219
        temporarily_disable('#feed-sweets');
212 220
221
        // Sweets make the Tamagotchi a bit happier:
222
        happiness += 10;
223
        if (happiness > 100) happiness = 100;
224
        $('#happiness').html(happiness);
225
213 226
        // Sweets are not healthy:
214 227
        make_alien_sicker(5);
215 228
    }

22.1 Make Tamagotchi get bored less readily

Make it so 5 game ticks can go by before the Tamagotchi gets bored.

code.js

154 154
155 155
    function update_alien_happiness()
156 156
    {
157
        bored = ((age - last_game_age) >= 3);
157
        bored = ((age - last_game_age) >= 5);
158 158
159 159
        if (bored)
160 160
        {

22.2 Sweets make Tamagotchi a bit happier

We can get away without having to call draw_alien(), because we know that make_alien_sicker() will do that for us. This is quite a fragile approach, however.

code.js

210 210
        feed_alien(10);
211 211
        temporarily_disable('#feed-sweets');
212 212
213
        // Sweets make the Tamagotchi a bit happier:
214
        happiness += 10;
215
        if (happiness > 100) happiness = 100;
216
        $('#happiness').html(happiness);
217
213 218
        // Sweets are not healthy:
214 219
        make_alien_sicker(5);
215 220
    }

22.3 Being unhealthy makes the Tamagotchi sadder

If the Tamagotchi is unhealthy, it gets sadder, but only down to a certain level of happiness. We don't have to check happiness remains non-negative, because we only adjust it if it is bigger than 40 to start with.

code.js

156 156
    {
157 157
        bored = ((age - last_game_age) >= 5);
158 158
159
        if (health < 75)
160
        {
161
            if (happiness > 40)
162
            {
163
                happiness -= 5;
164
            }
165
        }
166
159 167
        if (bored)
160 168
        {
161 169
            happiness -= 5;

23 Simplify display-update logic

The current logic for making sure the display is up-to-date is a bit fragile and scattered. Every time we change a variable, we update that part of the display. Simpler would be to update the whole display after anything might have changed.

We collect all the display-update code into one new function, update_display(), and call it whenever things might have changed.

code.js

102 102
        window.setTimeout(time_goes_by, 2000);
103 103
    }
104 104
105
    function make_alien_hungrier()
105
    function update_display()
106 106
    {
107
        hungriness += 5;
108 107
        $('#hungriness').html(hungriness);
108
        $('#health').html(health);
109
        $('#happiness').html(happiness);
110
        $('#age').html(age);
109 111
        draw_alien();
112
        if ( ! alien_still_growing())
113
            game_over_won();
114
        else if ( ! alien_is_alive())
115
            game_over_lost();
116
    }
117
118
    function make_alien_hungrier()
119
    {
120
        hungriness += 5;
110 121
    }
111 122
112 123
    function make_alien_sicker(health_lost)

116 127
        {
117 128
            health = 0;
118 129
        }
119
120
        $('#health').html(health);
121
        draw_alien();
122
123
        if ( ! alien_is_alive())
124
        {
125
            game_over_lost();
126
        }
127 130
    }
128 131
129 132
    function maybe_make_alien_sicker()

141 144
        {
142 145
            health = 100;
143 146
        }
144
145
        $('#health').html(health);
146
        draw_alien();
147 147
    }
148 148
149 149
    function make_alien_older()
150 150
    {
151 151
        age += 1;
152
        $('#age').html(age);
153 152
    }
154 153
155 154
    function update_alien_happiness()

174 173
            happiness += 2;
175 174
            if (happiness > 100) happiness = 100;
176 175
        }
177
178
        $('#happiness').html(happiness);
179
        draw_alien();
180 176
    }
181 177
182 178
    function feed_alien(hungriness_reduction)

186 182
        {
187 183
            hungriness = 0;
188 184
        }
189
190
        $('#hungriness').html(hungriness);
191
        draw_alien();
192 185
    }
193 186
194 187
    function temporarily_disable(button_id)

206 199
207 200
        feed_alien(20);
208 201
        temporarily_disable('#feed-bread');
202
203
        update_display();
209 204
    }
210 205
211 206
    $('#feed-bread').click(feed_alien_some_bread);

221 216
        // Sweets make the Tamagotchi a bit happier:
222 217
        happiness += 10;
223 218
        if (happiness > 100) happiness = 100;
224
        $('#happiness').html(happiness);
225 219
226 220
        // Sweets are not healthy:
227 221
        make_alien_sicker(5);
222
223
        update_display();
228 224
    }
229 225
230 226
    $('#feed-sweets').click(feed_alien_sweets);

236 232
237 233
        make_healthier();
238 234
        temporarily_disable('#give-medicine');
235
236
        update_display();
239 237
    }
240 238
241 239
    $('#give-medicine').click(give_alien_medicine);

248 246
        last_game_age = age;
249 247
        update_alien_happiness();
250 248
        temporarily_disable('#play-game');
249
250
        update_display();
251 251
    }
252 252
253 253
    $('#play-game').click(play_game);

272 272
        make_alien_older();
273 273
        update_alien_happiness();
274 274
275
        if ( ! alien_still_growing())
276
        {
277
            game_over_won();
278
        }
279
        else if ( ! alien_is_alive())
280
        {
281
            game_over_lost();
282
        }
283
        else
284
        {
285
            window.setTimeout(time_goes_by, 2000);
286
        }
275
        update_display();
276
277
        window.setTimeout(time_goes_by, 2000);
287 278
    }
288 279
});

23.1 update_display(): Add

Collect the various updates to the different bits of the webpage together into one function. When we call this function, many parts will not have changed, so we will be doing more work than required. But computers are fast these days, and making the code simpler to understand is usually more important than having it run as quickly as possible.

code.js

102 102
        window.setTimeout(time_goes_by, 2000);
103 103
    }
104 104
105
    function update_display()
106
    {
107
        $('#hungriness').html(hungriness);
108
        $('#health').html(health);
109
        $('#happiness').html(happiness);
110
        $('#age').html(age);
111
        draw_alien();
112
        if ( ! alien_still_growing())
113
            game_over_won();
114
        else if ( ! alien_is_alive())
115
            game_over_lost();
116
    }
117
105 118
    function make_alien_hungrier()
106 119
    {
107 120
        hungriness += 5;

23.2 make_alien_hungrier(): Simplify

Remove display-update code.

code.js

118 118
    function make_alien_hungrier()
119 119
    {
120 120
        hungriness += 5;
121
        $('#hungriness').html(hungriness);
122
        draw_alien();
123 121
    }
124 122
125 123
    function make_alien_sicker(health_lost)

23.3 make_alien_sicker(): Simplify

Remove display-update code.

code.js

127 127
        {
128 128
            health = 0;
129 129
        }
130
131
        $('#health').html(health);
132
        draw_alien();
133
134
        if ( ! alien_is_alive())
135
        {
136
            game_over_lost();
137
        }
138 130
    }
139 131
140 132
    function maybe_make_alien_sicker()

23.4 make_healthier(): Simplify

Remove display-update code.

code.js

144 144
        {
145 145
            health = 100;
146 146
        }
147
148
        $('#health').html(health);
149
        draw_alien();
150 147
    }
151 148
152 149
    function make_alien_older()

23.5 make_alien_older(): Simplify

Remove display-update code.

code.js

149 149
    function make_alien_older()
150 150
    {
151 151
        age += 1;
152
        $('#age').html(age);
153 152
    }
154 153
155 154
    function update_alien_happiness()

23.6 update_alien_happiness(): Simplify

Remove display-update code.

code.js

173 173
            happiness += 2;
174 174
            if (happiness > 100) happiness = 100;
175 175
        }
176
177
        $('#happiness').html(happiness);
178
        draw_alien();
179 176
    }
180 177
181 178
    function feed_alien(hungriness_reduction)

23.7 feed_alien(): Simplify

Remove display-update code.

code.js

182 182
        {
183 183
            hungriness = 0;
184 184
        }
185
186
        $('#hungriness').html(hungriness);
187
        draw_alien();
188 185
    }
189 186
190 187
    function temporarily_disable(button_id)

23.8 feed_alien_sweets(): Simplify

Remove display-update code.

code.js

214 214
        // Sweets make the Tamagotchi a bit happier:
215 215
        happiness += 10;
216 216
        if (happiness > 100) happiness = 100;
217
        $('#happiness').html(happiness);
218 217
219 218
        // Sweets are not healthy:
220 219
        make_alien_sicker(5);

23.9 Call update_display() where required

Every part of the code which changes the game's state now needs to call update_display(). These parts are the 'time goes by' code, and any user-driven behaviour (button clicks):

  • feed_alien_some_bread()
  • feed_alien_sweets()
  • give_alien_medicine()
  • play_game()
  • time_goes_by()

We have temporarily broken the timer code, because it was tied up with the display update for win/lose. We'll fix that shortly.

code.js

199 199
200 200
        feed_alien(20);
201 201
        temporarily_disable('#feed-bread');
202
203
        update_display();
202 204
    }
203 205
204 206
    $('#feed-bread').click(feed_alien_some_bread);

217 219
218 220
        // Sweets are not healthy:
219 221
        make_alien_sicker(5);
222
223
        update_display();
220 224
    }
221 225
222 226
    $('#feed-sweets').click(feed_alien_sweets);

228 232
229 233
        make_healthier();
230 234
        temporarily_disable('#give-medicine');
235
236
        update_display();
231 237
    }
232 238
233 239
    $('#give-medicine').click(give_alien_medicine);

240 246
        last_game_age = age;
241 247
        update_alien_happiness();
242 248
        temporarily_disable('#play-game');
249
250
        update_display();
243 251
    }
244 252
245 253
    $('#play-game').click(play_game);

264 272
        make_alien_older();
265 273
        update_alien_happiness();
266 274
267
        if ( ! alien_still_growing())
268
        {
269
            game_over_won();
270
        }
271
        else if ( ! alien_is_alive())
272
        {
273
            game_over_lost();
274
        }
275
        else
276
        {
277
            window.setTimeout(time_goes_by, 2000);
278
        }
275
        update_display();
279 276
    }
280 277
});

23.10 Re-call time_goes_by() while game still running

The first thing we do in time_goes_by() is to leave the function if the game has stopped (through win or loss), so we should always re-schedule a timer if we reach the end of this function.

code.js

273 273
        update_alien_happiness();
274 274
275 275
        update_display();
276
277
        window.setTimeout(time_goes_by, 2000);
276 278
    }
277 279
});

24 Add 'sweet wrappers' features

When the Tamagotchi eats sweets, it drops the wrappers on the floor of its room. The untidiness makes it unhappy, and it gets unhappier more quickly the more wrappers are littering its room. To allow the player to fix this, we add a Sweep button, which clears up all the wrappers.

code.js

9 9
    var bored = false;
10 10
    var last_game_age = 0;
11 11
    var age = 0;
12
    var sweet_wrappers = [];
12 13
13 14
    function alien_is_alive()
14 15
    {

80 81
            ctx.lineTo(280, 130);
81 82
        }
82 83
        ctx.stroke();
84
85
        ctx.fillStyle = 'red';
86
        sweet_wrappers.forEach(function(w) {
87
            ctx.fillRect(w[0], w[1], 50, 20);
88
        });
83 89
    }
84 90
85 91
    $('#hatch').click(draw_alien_fade_instructions);

153 159
154 160
    function update_alien_happiness()
155 161
    {
162
        happiness -= sweet_wrappers.length;
163
        if (happiness < 0) happiness = 0;
164
156 165
        bored = ((age - last_game_age) >= 5);
157 166
158 167
        if (health < 75)

220 229
        // Sweets are not healthy:
221 230
        make_alien_sicker(5);
222 231
232
        add_sweet_wrapper();
233
223 234
        update_display();
224 235
    }
225 236

252 263
253 264
    $('#play-game').click(play_game);
254 265
266
    function sweep_up_wrappers()
267
    {
268
        sweet_wrappers = [];
269
        temporarily_disable('#sweep-up');
270
271
        update_display();
272
    }
273
274
    $('#sweep-up').click(sweep_up_wrappers);
275
276
    function add_sweet_wrapper()
277
    {
278
        var wrapper_x = 10 + Math.random() * 420;
279
        var wrapper_y = 240 + Math.random() * 200;
280
281
        var wrapper = [wrapper_x, wrapper_y];
282
283
        sweet_wrappers.push(wrapper);
284
    }
285
255 286
    function game_over_lost()
256 287
    {
257 288
        $('#messages').html('Oh no!  Your Tamagotchi died!');

page.html

27 27
      <button id="feed-sweets">Sweets</button>
28 28
      <button id="give-medicine">Medicine</button>
29 29
      <button id="play-game">Game</button>
30
      <button id="sweep-up">Sweep</button>
30 31
    </p>
31 32
32 33
    <p id="messages"></p>

style.css

5 5
#levels { display: none; position: absolute; left: 520px; top: 0px; }
6 6
#action-buttons { display: none;
7 7
                  width: 480px; background-color: #ddf; text-align: center; }
8
#action-buttons button { margin: 24px; }
8
#action-buttons button { margin: 16px; }
9 9
#age-para { font-weight: bold; }

24.1 Add variable to remember where wrappers are

JavaScript variables can be lists, properly known in JavaScript as arrays, which means they can hold more than one object. Scratch has the same idea. At the start of the game, there are no wrappers, so we have a list with nothing in it. This is known as an empty list.

code.js

9 9
    var bored = false;
10 10
    var last_game_age = 0;
11 11
    var age = 0;
12
    var sweet_wrappers = [];
12 13
13 14
    function alien_is_alive()
14 15
    {

24.2 Temporarily add some wrappers for testing

But! For testing how we draw the wrappers, we want to start off by putting some wrappers in the list. How are we going to say where the wrappers are? To say where one wrapper is, we need its x and y coordinates, so we'll represent each wrapper as a two-entry list. We'll make up places for two test wrappers.

code.js

9 9
    var bored = false;
10 10
    var last_game_age = 0;
11 11
    var age = 0;
12
    var sweet_wrappers = [];
12
    var sweet_wrappers = [ [280, 300], [140, 320] ];
13 13
14 14
    function alien_is_alive()
15 15
    {

24.3 Draw the wrappers

We have a draw_alien() function, which we'll give the job of drawing the wrappers, even though they're not really part of the alien.

We want to draw each wrapper, and one way to do this in JavaScript is to use the forEach() method on the list of things.

In this code, the w variable is set (by JavaScript) to be equal to each of the things in sweet_wrappers in turn, and the fillRect() is done for each one. Remember that each thing in the list is itself a list, with the x and y coordinates as its two entries. You get things out of this list with w[0] to get the first, and w[1] for the second. JavaScript counts list entries starting from zero. Here I've just drawn a red rectangular sweet wrapper but you can do something more complicated if you like.

If you try the game now, you should see the two test sweet-wrappers we set up.

code.js

81 81
            ctx.lineTo(280, 130);
82 82
        }
83 83
        ctx.stroke();
84
85
        ctx.fillStyle = 'red';
86
        sweet_wrappers.forEach(function(w) {
87
            ctx.fillRect(w[0], w[1], 50, 20);
88
        });
84 89
    }
85 90
86 91
    $('#hatch').click(draw_alien_fade_instructions);

24.4 Remove temporary testing sweet-wrappers

Now we've got the drawing code working, we can remove the temporary sweet-wrappers, and start off with an empty sweet_wrappers list.

code.js

9 9
    var bored = false;
10 10
    var last_game_age = 0;
11 11
    var age = 0;
12
    var sweet_wrappers = [ [280, 300], [140, 320] ];
12
    var sweet_wrappers = [];
13 13
14 14
    function alien_is_alive()
15 15
    {

24.5 Make up random position for a new wrapper

When the Tamagotchi gets a sweet, we'll need to make up a random position for it to drop the wrapper. We'll use the same Math.random() function as we've done before (when we were randomly making it sick). But this time we need to turn our random number between 0 and 1 into a sensible position on the canvas, which we do by multiplying by the range of possible values, then adding the smallest desired value.

Here's I've made it so the wrappers will only appear in the bottom half of the screen, by making wrapper_y be at least 240.

So far this function makes up the x and y coordinates of the wrapper's position, but doesn't yet add the new wrapper to the list.

code.js

258 258
259 259
    $('#play-game').click(play_game);
260 260
261
    function add_sweet_wrapper()
262
    {
263
        var wrapper_x = 10 + Math.random() * 420;
264
        var wrapper_y = 240 + Math.random() * 200;
265
266
        // TODO: Add the new wrapper to the list.
267
    }
268
261 269
    function game_over_lost()
262 270
    {
263 271
        $('#messages').html('Oh no!  Your Tamagotchi died!');

24.6 Combine x- and y-coordinates into two-element list

Our sweet_wrappers variable is a list, where each thing on that list is itself a two-element list, for the x and y coordinates of the wrapper. We'll create that two-element list now, using the [...] notation to make a new list with the required two elements.

code.js

263 263
        var wrapper_x = 10 + Math.random() * 420;
264 264
        var wrapper_y = 240 + Math.random() * 200;
265 265
266
        var wrapper = [wrapper_x, wrapper_y];
267
266 268
        // TODO: Add the new wrapper to the list.
267 269
    }
268 270

24.7 Add new sweet-wrapper location to list

Finally, we use the push() method to add a new item to the end of our sweet_wrappers array.

code.js

265 265
266 266
        var wrapper = [wrapper_x, wrapper_y];
267 267
268
        // TODO: Add the new wrapper to the list.
268
        sweet_wrappers.push(wrapper);
269 269
    }
270 270
271 271
    function game_over_lost()

24.8 Add a sweet-wrapper when feeding sweets

To actually add a wrapper, we need to call our new add_sweet_wrapper() function as part of feed_alien_sweets().

Because we recently re-organised the code to do all display updates inside update_display(), the wrappers get drawn automatically.

code.js

226 226
        // Sweets are not healthy:
227 227
        make_alien_sicker(5);
228 228
229
        add_sweet_wrapper();
230
229 231
        update_display();
230 232
    }
231 233

24.9 Wrappers make the Tamagotchi unhappy

The messier its home is, the quicker it becomes unhappy. We'll subtract happiness points equal to the number of wrappers littering the place. To do this, we use the length property of sweet_wrappers, which tells us how many things are in the array.

As usual, we have to make sure that we don't leave the happiness negative.

code.js

159 159
160 160
    function update_alien_happiness()
161 161
    {
162
        happiness -= sweet_wrappers.length;
163
        if (happiness < 0) happiness = 0;
164
162 165
        bored = ((age - last_game_age) >= 5);
163 166
164 167
        if (health < 75)

24.10 Add button to sweep up

The player needs a way to tidy up the sweet wrappers, so add a Sweep button.

page.html

27 27
      <button id="feed-sweets">Sweets</button>
28 28
      <button id="give-medicine">Medicine</button>
29 29
      <button id="play-game">Game</button>
30
      <button id="sweep-up">Sweep</button>
30 31
    </p>
31 32
32 33
    <p id="messages"></p>

24.11 Tighten spacing of action buttons

Depending on your browser zoom and other styling you've chosen for the buttons, there might now be too many buttons to fit on one row. Adjust the styling to put less space by the buttons, allowing them to fit.

style.css

5 5
#levels { display: none; position: absolute; left: 520px; top: 0px; }
6 6
#action-buttons { display: none;
7 7
                  width: 480px; background-color: #ddf; text-align: center; }
8
#action-buttons button { margin: 24px; }
8
#action-buttons button { margin: 16px; }
9 9
#age-para { font-weight: bold; }

24.12 sweep_up_wrappers(): Add

When the player clicks the button, we're going to want to remove all the sweet wrappers. The easiest way to do this is just to make sweet_wrappers be a fresh empty array.

We also need to temporarily disable the Sweep button, and also update the display, to make sure the clean room is immediately shown.

code.js

263 263
264 264
    $('#play-game').click(play_game);
265 265
266
    function sweep_up_wrappers()
267
    {
268
        sweet_wrappers = [];
269
        temporarily_disable('#sweep-up');
270
271
        update_display();
272
    }
273
266 274
    function add_sweet_wrapper()
267 275
    {
268 276
        var wrapper_x = 10 + Math.random() * 420;

24.13 Connect sweep_up_wrappers() to button

Finally, say that we want our new sweep_up_wrappers() function to be run when the player clicks the Sweep button.

code.js

271 271
        update_display();
272 272
    }
273 273
274
    $('#sweep-up').click(sweep_up_wrappers);
275
274 276
    function add_sweet_wrapper()
275 277
    {
276 278
        var wrapper_x = 10 + Math.random() * 420;