Using some structs

In this homework, we are going to explore using structs to hold data and run methods. For the first part, we will create a small program to track pets. And finally we'll start up a project to begin building our snake game, including the structs that are needed.

Let's track some pets!

Well, we can track pets, games, whatever you want. What we need are a list of things that have fields and methods.

struct Pet
{
    string name;
    string species;
    int age;
    int hunger;
    int alertness;
}

We have a struct here, and each value of this struct type has all of the listed fields as pieces of the struct. The struct's name is Pet, and it has a name, a species, how old it is, how hungry it is, and how alert it is.

If we want to make a single pet, we use what is called a constructor. The constructor is a special kind of function which accepts parameters and can set up the fields. Every struct has a default constructor which accepts the fields, and fills in the fields in order they are listed in the struct. So for instance, if we want a pet that is a 5 year old cat named "Fred", hunger 3, and alertness 10, we would write Pet("Fred", "Cat", 5, 3, 10).

What does the hunger and alertness mean? We can say that hunger is a value from 1 to 10 to indicate how much the pet wants to eat, and that alertness is a value from 1 to 10 to indicate how much the pet needs to sleep.

Let's create a list of these pets:

Pet[] myPets = [
   Pet("Fred", "Cat", 5, 3, 10),
   Pet("Fido", "Dog", 4, 6, 5),
   Pet("Big Boy", "Frog", 1, 2, 2)
];

This creates 3 pets, of 3 different species all with varying ages, hunger, and alertness. If we wanted to describe a pet, we can write a method inside pet that will tell us information in a nice way:

import std.stdio;

struct Pet
{
    string name;
    string species;
    int age;
    int hunger;
    int alertness;

    void describe() {
        write("I am a ", age, " year old ", species, " named ", name, ".");
        if(hunger > 5) {
            write(" I am hungry, please feed me!");
        }
        if(alertness < 3) {
            write(" I need a nap!");
        }
        writeln(); // finish the description line
    }
}

Now that we have something to do with our pets, let's describe them! To get each pet, we use a foreach loop:


void main()
{
    Pet[] myPets = [
       Pet("Fred", "Cat", 5, 3, 10),
       Pet("Fido", "Dog", 4, 6, 5),
       Pet("Big Boy", "Frog", 1, 2, 2)
    ];

    foreach(pet; myPets) {
       pet.describe();
    }
}

The result of describing the pets is going to be:

I am a 5 year old Cat named Fred.
I am a 4 year old Dog named Fido. I am hungry, please feed me!
I am a 1 year old Frog named Big Boy. I need a nap!

Note how we could have put the describe function into our main function, and it would have worked too. But making it a method does two very useful things:

  1. You don't have to prefix the data you are looking at (such as name or species) with pet.
  2. You can use this describe function anywhere you have a Pet. It has become part of the type itself!

Feed your pets!

You are to write a method feed(string foodType, int amount) which will subtract from the pet's hunger points the amount fed, but only if the type of food matches the species! If the pet can eat that food (that is, he has enough hunger to eat it all), it should say "Yummy!", and subtract the food from the hunger points. If the hunger is 0, it should say "I'm not hungry". If the food doesn't match the species it should say "Yuck! I don't like that kind of food". And finally, if after eating the food, the hunger is still above 5, it should say "Yummy! But I'm still hungry."

See if you can write this on your own. Here is how you should start out. Note that I have already written the first part that returns early if the food type doesn't match:

struct Pet
{
    string name;
    string species;
    int age;
    int hunger;
    int alertness;

    void describe() {
        write("I am a ", age, " year old ", species, " named ", name, ".");
        if(hunger > 5) {
            write(" I am hungry, please feed me!");
        }
        if(alertness < 3) {
            write(" I need a nap!");
        }
        writeln(); // finish the description line
    }

    void feed(string foodType, int amount) {
        write(name, ": ");
        if(foodType != species) {
            writeln("Yuck! I don't like ", foodType, " food");
            return;
        }
        // your code should go here
    }
}

Note that it's already writing the pet's name. Use write to add to the line of text, and writeln to finish the line of text. You can use return to return early from the function (as it already does if the food is not the right type).

If your code is written properly, then the following main function should output the following text.

void main()
{
    Pet[] myPets = [
       Pet("Fred", "Cat", 5, 3, 10),
       Pet("Fido", "Dog", 4, 9, 5),
       Pet("Big Boy", "Frog", 1, 2, 2)
    ];

    foreach(pet; myPets) {
       pet.describe();
    }

    // feed the pets
    myPets[0].feed("Cat", 3);
    myPets[1].feed("Dog", 3);
    myPets[2].feed("Rabbit", 1);
    myPets[0].feed("Cat", 1);
}

Output:

I am a 5 year old Cat named Fred.
I am a 4 year old Dog named Fido. I am hungry, please feed me!
I am a 1 year old Frog named Big Boy. I need a nap!
Fred: Yummy!
Fido: Yummy! But I'm still hungry.
Big Boy: Yuck! I don't like Rabbit food
Fred: I'm not hungry

If you want to see the answer, it is here, but only look if you can't figure it out on your own! You can also ask for help on discord.

Start the Snake Game

Now, I want you to start getting your snake game ready. We are going to use structs to hold all the data, and methods to run the game.

If you remember from the class, I had set up a few structs that are going to track the game state. This is what we had so far. Note that I replaced the direction integer with an enum. This should be the stub program that you have ready to use for the next class. I will also :

import std.stdio;
import raylib;

enum Dir
{
    left,
    up,
    right,
    down
}

struct Snake
{
    int x;
    int y;
    int apples; // apples eaten
    Dir direction;
}

struct Apple
{
    int x;
    int y;
    string appleType;
    int mass;
}

struct TailPiece
{
    int x;
    int y;
}

struct GameState {
    Snake player;
    TailPiece[] tail;
    Apple[] apples;
    int rightWall;
    int bottomWall;
}

void main()
{
    // setup
    InitWindow(500, 500, "Snake");
    GameState gameState;

    SetTargetFPS(60);
    while(!WindowShouldClose())
    {
        BeginDrawing();
        ClearBackground(Colors.WHITE);
        EndDrawing();
    }
}

I have discovered an easier way to include raylib, which I have updated in the Raylib Installation Instructions. Please don't just copy a prior project, as this won't have the right version of raylib.

Build and run your program with CTRL-SHIFT-B, and select the "dub run" task. If everything works right, you should see a white screen pop up.

Now you are ready to go for the next class!

©2021 Steven Schveighoffer