Recap of features

In this lesson, we saw a lot of new features and types. I want to go over all those features, and maybe recap some of the other features we already have talked about. You will need them for the homework!

Associative Arrays (or Dictionaries) are arrays where the data can have any index, and the index can be of any type. A normal array is stored sequentially in memory, and only can use integers for indexes. Therefore, in order to have an array with index 500, you have to have elements for indexes 0 through 499 as well. However, an Associative Array can have a value at index 500, and at index 5000000, and it only needs to store those two values.

When should you use an associative array vs. a normal array? Use an array when:

Use an associative array when:

To declare an associative array, we use the syntax ValueType[IndexType]. To declare an array we use the syntax ValueType[].

Examples:

int[] arr; // an array of integers
arr ~= 5; // add an element to the END of an array
arr[500] = 6; // this would generate an error, as there is no storage at index 500 (yet).

char[string] arr2; // an associative array with string as the index type and char as the value type
arr2["hello"] = 'c'; // insert or assign a new index-to-value pair.

int[int] arr3; // an associative array with integer as the index and value types.
arr3[500000] = 2; // this only stores one item.

if(500000 in arr3) { // we can check if an index exists in an associative array
    writeln(arr[500000]);
}

I also want to introduce you to array literals. An array literal is a way to enter the data for an array without having to call functions to build it. You already have seen a string literal, it is provided by using double quotes: "hello". But you can provide normal array literals and associative array literals as well. They look like this:

int[] arr = [1, 2, 3]; // create an array with the values 1, 2, 3
char[string] arr2 = ["hello" : 'd', "goodbye" : 'c']; // this maps index "hello" to value 'd', and index "goodbye" to value 'c'

You will see some literals in the code I wrote. A really nice usage of literals is if you want to use foreach over a list of items (hint, you may want to use this for the homework):

foreach(v; [100, 55, 2000]) {
    writeln(v); // write each number
}
foreach(c; "hello") {
    writeln(c); // write each character
}

We also learned about the double type. This is a number you can use to represent arbitrarily large or small numbers. It stands for "double precision floating point." As I said in class, this type is going to be very important for gaming.

double pi = 3.14159;
double circlearea = pi * radius * radius;
double circlediameter = pi * radius * 2;

I also introduced the not operator. This operator is written with an exclamation point, and basically turns any boolean from true to false or false to true. It is handy for checking the opposite of what something gives you:

while(!finished) {
   doStuff();
}

And finally, we touched briefly upon structures. I'm going to detail more about this in the homework below. But the syntax looks like this:

struct User {
   string name;
   int age;
}

Now, we can declare a User variable by using that identifier as the type. We can set or read the members of the variable (the name and age) by putting a dot after the variable name, and naming the member we want to access:

User myUser;
myUser.name = "Steve";
myUser.age = 15;
writeln(myUser);

The last line will print the myUser structure to the output, including all the members inside.

structs are extremely important for organizing memory, and for creating complex program state. If you are writing a game, nearly everything is going to be considered a type of object. For example, you can imagine an enemy struct, with an integer for health, an integer for how much damage it can do, which direction it's moving, where it is on the screen, etc. We will be using structs quite a bit moving forward.

Homework assignment - Tic Tac Toe

I wrote a complete Tic Tac Toe game for you to try. Download the executable here, you can run it either by double clicking on it, or in a command window. I set it up to require you to press enter to exit the game, so the window doesn't go away at the end. The way you play it is to type in the column and row for each player's move, and if one player gets 3 in a row, the game is over. If all the spaces are used up without a winner, the game is a tie.

Here is ALMOST all the code. I want you to write the function called checkBoard. The function should return true if the game is not over, and false if the game should exit. If there is a winner, you should set the state.winner member to the winning player's character. Don't forget to handle the case where there is a tie!

import std.stdio;
import std.uni : toLower;
import std.string;

struct GameState
{
    char curPlayer; // X = player 1, O = player 2
    char[string] board;
    int spacesleft; // number of spaces that are not empty. If 0, the game is over.
    char winner = ' ';
}

void printBoard(GameState state)
{
    writeln("  A B C ");
    writeln(" +-+-+-+");
    foreach(row; "012")
    {
        write(row, "|");
        foreach(col; "abc")
        {
            string space = [col, row];
            write(state.board[space], "|");
        }
        writeln();
        writeln(" +-+-+-+");
    }
}

void main()
{
    GameState state;
    state.curPlayer = 'X';
    state.board = [
        "a0" : ' ', "a1" : ' ', "a2" : ' ',
        "b0" : ' ', "b1" : ' ', "b2" : ' ',
        "c0" : ' ', "c1" : ' ', "c2" : ' ',
    ];

    state.spacesleft = 9;
    // function to check if someone has won
    bool checkBoard()
    {
        // 1. check for any row that is all the same

        // 2. check for any column that is all the same

        // 3. check for diagonal wins

        // no winner yet
        return true;
    }

    // Game loop
    while(checkBoard())
    {
        printBoard(state);
        writeln(state.curPlayer, "'s turn, choose your space (like a0):");
        string answer = toLower(strip(readln()));
        if(!(answer in state.board))
        {
            writeln("invalid space: ", answer);
            continue;
        }

        if(state.board[answer] == ' ')
        {
            state.board[answer] = state.curPlayer;
            --state.spacesleft;
        }
        else
        {
            writeln("Space ", answer, " is already occupied");
            continue;
        }

        if(state.curPlayer == 'X')
            state.curPlayer = 'O';
        else
            state.curPlayer = 'X';
    }
    printBoard(state);
    if(state.winner == ' ')
        writeln("Tie!");
    else
        writeln(state.winner, " won the game!");
    writeln("Press enter to exit");
    readln();
}

Note that I have stored all the game state into a struct GameState. This means that while you are looking at the game state, you have to access all the data using that state structure. The game board is an associative array with the coordinates as the index. Look over the existing code for hints as to how to process the state and finish the function! As usual, you can ask questions on discord, and I'm OK with you coordinating with each other on the homework.

One thing that is nice about storing game state into a structure, is that you can print the entire game state to see what is happening while the code is running.

Create a new project in Visual Studio Code, and copy this code into your app.d file. I have added hints in the checkBoard function you need to fill in. Also, you should take some time to read the code and see if you can understand what is happening. I didn't include extensive comments this time, so you can try and figure it out on your own!

©2019-2020 Steven Schveighoffer