Lab 11: BYOW Introduction

FAQ #

Each assignment will have an FAQ linked at the top. You can also access it by adding “/faq” to the end of the URL. The FAQ for Lab 11 is located here.

This lab will help you with Project 3: Build your own World (BYOW). The first part will teach you how to use a set of “tiles” to generate shapes on your screen. This will apply to building the rooms, hallways, and other features of your world in Project 3. Next week’s lab will teach you more about how to use the StdDraw package to make a fun text-based game. This will help you build the main menu and other text-based elements of Project 3. It will also teach you how to achieve user interactivity, which is vital to Project 3!

Pre-lab #

Some steps to complete before getting started on this lab:

  • As usual, use git pull skeleton main

  • Watch a previous semester’s project 3 getting started video at this link. Note the name and API have changed slightly, but the bigger picture still applies.

  • Understand that project 3 will be a marathon and not a sprint. Don’t wait until the last minute. You and your partner should start thinking about your design NOW.

  • Read over Phase 1 of the project 3 spec.

Plus World Introduction #

In the first half of this lab, you and your partner will learn some basic techniques and tools that will be helpful for project 3.

Part I: Meet the Tile Rendering Engine #

Boring World #

Open the up the skeleton and check out the BoringWorldDemo file. Try running it and you should see a window appear that looks like the following:

boring world

This world consists of empty space, except for the rectangular block near the bottom middle. The code to generate this world consists of three main parts:

  • Initializing the tile rendering engine.
  • Generating a two dimensional TETile[][] array.
  • Using the tile rendering engine to display the TETile[][] array.

The API for the tile rendering engine is simple. After creating a TERenderer object, you simply need to call the initialize method, specifying the width and height of your world, where the width and height are given in terms of the number of tiles. Each tile is 16 pixels by 16 pixels, so for example, if we called ter.initialize(10, 20), we’d end up with a world that is 10 tiles wide and 20 tiles tall, or equivalently 160 pixels wide and 320 pixels tall. For this lab, you don’t need to think about pixels, though you’ll eventually need to when you start building the user interface for Project 3 (discussed in the next lab).

TETile objects are also quite simple. You can either build them from scratch using the TETile constructor (see TETile.java), or you can choose from a palette of pregenerated tiles in the file Tileset.java. For example, the code from BoringWorldDemo.java below generates a 2D array of tiles and fills them with the pregenerated tile given by Tileset.NOTHING.

TETile[][] world = new TETile[WIDTH][HEIGHT];
for (int x = 0; x < WIDTH; x += 1) {
    for (int y = 0; y < HEIGHT; y += 1) {
        world[x][y] = Tileset.NOTHING;
    }
}

Of course, we can overwrite existing tiles. For example, the code below from BoringWorld.java creates a 14 x 4 tile region made up of the pregenerated tile Tileset.WALL and writes it over some of the NOTHING tiles created by the loop code shown immediately above.

for (int x = 20; x < 35; x += 1) {
    for (int y = 5; y < 10; y += 1) {
        world[x][y] = Tileset.WALL;
    }
}

The last step in rendering is to simply call ter.renderFrame(world), where ter is a TERenderer object. Changes made to the tiles array will not appear on the screen until you call the renderFrame method.

Try changing the tile specified to something else in the Tileset class other than WALL and see what happens. Also experiment with changing the constants in the loop and see how the world changes.

Note: Tiles themselves are immutable! You cannot do something like world[x][y].character = 'X'.

Random World #

Now open up RandomWorldDemo.java. Try running it and you should see something like this:

random world

This world is sheer chaos – walls and flowers everywhere! If you look at the RandomWorldDemo.java file, you’ll see that we’re doing a few new things:

  • We create and use an object of type Random that is a “pseudorandom number generator”.
  • We use a new type of conditional called a switch statement.
  • We have delegated work to functions instead of doing everything in main.

A random number generator does exactly what its name suggests, it produces an infinite stream of numbers that appear to be randomly ordered. The Random class provides the ability to produce pseudorandom numbers for us in Java. For example, the following code generates and prints 3 random integers:

Random r = new Random(1000);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());

We call Random a pseudorandom number generator because it isn’t truly random. Underneath the hood, it uses cool math to take the previously generated number and calculate the next number. We won’t go into the details of this math, but see Wikipedia if you’re curious. Importantly, the sequence generated is deterministic, and the way we get different sequences is by choosing what is called a “seed”. If you start up a pseudorandom generator with a particular seed, you are guaranteed to get the exact sequence of random values.

In the above code snippet, the seed is the input to the Random constructor, so 1000 in this case. Having control over the seed is pretty useful since it allows us to indirectly control the output of the random number generator. If we provide the same seed to the constructor, we will get the same sequence values. For example, the code below prints 4 random numbers, then prints the SAME 4 random numbers again. Since the seed is different than the previous code snippet, the 4 numbers will likely be different than the 3 numbers printed above. This is super helpful in Project 3, as it will give us deterministic randomness: you worlds look totally random, but you can recreate them consistently for debugging (and grading) purposes.

Random r = new Random(82731);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
r = new Random(82731);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());

In the case a seed is not provided by the user/programmer, i.e. Random r = new Random(), random number generators select a seed using some value that changes frequently and produces a lot of unique values, such as the current time and date. Seeds can be generated in all sorts of other stranger ways, such as using a wall full of lava lamps.

For now, RandomWorldDemo uses a hard coded seed, namely 2873123, so it will always generate the exact same random world. You can change the seed if you want to see other random worlds, though given how chaotic the world is, it probably won’t be very interesting.

The final and most important thing is that rather than doing everything in main, our code delegates work to functions with clearly defined behavior. This is critically important for your project 3 experience! You’re going to want to constantly identify small subtasks that can be solved with clearly defined methods. Furthermore, your methods should form a hierarchy of abstractions! We’ll see how this can be useful in the final part of this lab.

Part II: Use the Tile Rendering Engine #

Plus World Intro #

Above, we’ve seen how we can draw a world and generate randomness. Your task for the first half of lab is to use the tile generator we’ve seen to make a plus shape, like below.

single plus

Optionally, you can take these plus shapes, and form a beautiful (and randomized) tesselation like below.

example world

In the actual Project 3, you’ll be generating random worlds as well, although in the project, they will be indoor spaces instead of open landscapes. While this lab task does not directly apply to the project, it will familiarize you with important libraries including our Tile Rendering engine, and also help you think about how you can take complex drawing tasks and break them into simpler pieces.

Your should be able to draw differently sized plus signs. The picture above is of size-4 plusses, and below we see a world consisting of size-1, size-2, and size-3 plusses, respectively.

example world size 1

example world size 2

example world size 3

Drawing A Single Plus #

The only task you are required to do for this lab is to draw a single plus. Tesselating them to fill the whole screen is a cool but optional task. Your class is completely blank, but you’re encouraged to reference BoringWorld and RandomWorld to get an idea of how to set up the class! Once you’ve done the setup to make an empty world, start by trying to create a method addPlus that adds a plus of size s to a given position in the world.

Here, we see the size is the tile width for one “leg” of the plus. There are many ways to break down a plus. You could think of it as three rows, where the middle row is wider. You could think of it as 5 squares: top, bottom, left, right, and center. You could view it as neither! The way you define the “position” of a plus is also up to you!

                       aaa
           aa          aaa
           aa          aaa
  a      aaaaaa     aaaaaaaaa
 aaa     aaaaaa     aaaaaaaaa
  a        aa       aaaaaaaaa
           aa          aaa
                       aaa
                       aaa

To verify that your addPlus method works, write a short main method and verify that things looked OK. Unfortunately, writing a JUnit test to verify that you’ve properly drawn a plus is just as hard as drawing the plus itself, so you won’t be able to build confidence in your addPlus method in a nice way like you can with simpler methods.

Note that even deciding the addPlus method signature is a non-trivial task! This exercise will give you a glimpse into the kind of decision-making and design thinking you will have to do in Project 3.

Deciding what classes you need for this lab, just like Proj3, is entirely up to you! One example class you might add is a Point class, to represent coordinates in your world. You can also think about making a Plus class. This will require some careful thinking about what a Plus object is in this program, what it should know about itself (i.e. what are its instance variables), and what it can do (i.e. its public instance methods). This is what a lot of your work on Project 3 is going to look like!

There are many different ways to approach this problem, and that’s what makes it so interesting.

Tip: If you want randomized colors for your plus tiles, e.g. so that not every flower is exactly the same, see the TETile.colorVariant method

Optional: Drawing A Tesselation of Plusses #

Once you have code that can draw a single plus, you can try to tessellate them to form a world, like shown in the example images earlier.

As with drawing a single plus, there are a huge number of ways to draw a tesselation. The most important part is to identify helper methods that will be useful for the task!

You should absolutely not try to do something like do everything in a nested for loop with no helper methods. While it is technically possible to do this, you will melt your brain. In this project, it is absolutely vital that you avoid the temptation to always work at a “low level”. Without hierarchical abstraction, your mind will transform into a pile of goo under the weight of all the complexity.

By writing very well-defined, nicely commented helper methods, you also make it physically possible to get help from course staff. During office hours for this project, TAs will be limited to ten minutes per pair, and will not be allowed to spend a long time getting to know the intricacies of your code. They are there for high level guidance, as well as help debugging when you’ve really exhausted all your options.

As a hint for one possible solution, look for repeating patterns in a given tesselation. If you look at a single plus in a tesselation, when does it “repeat” itself? How many squares do you have to move over until you find another plus at that same height in the image?

HexWorld Live Coding Demo #

The live coding demo can be found here! Note that this video is from a previous semester, in which students were required to draw Hexagons instead of Pluses. Try to generalize the logic from drawing a tesselation of Hexagons to a tesselation of Pluses!

Moving on to Project 3 #

In theory, this lab has taught you everything you need to know to get started on Project 3! The process of generating your world will be similar in many ways to drawing a hexagon world, though Project 3 world generation will be considerably more complex. Read over Phase 1 of the project 3 spec.

Take a look at the questions in project3prep.md. Feel free to discuss with your partner or a TA before jotting down your answers.

Submission #

You’ll be submitting your completed project3prep.md file to Gradescope. You will get full credit as long as this is filled out and submitted!

Last built: 2023-04-08 01:29 UTC