Homework 8
Due Date: Sunday November 8, 9pm
Purpose: To practice list abstractions, local, and lambda and extend the project.
Expectations
You and your partner must submit a single .rkt file containing your responses to all exercises via the Handin Server. We accept no email submissions.
You must use the language specified at the top of this page.
Your code must conform to the guidelines outlined in the style guide on the course website. The style guide will be updated as the semester progresses, so revisit it before submitting each assignment. (You can resubmit as many times as you like until the due date without penalty.)
Unless otherwise stated, every function and data definition that you design, including helper functions, must follow all steps of the design recipe.
Failure to comply with these expectations will result in deductions and possibly a 0 score.
Finger Exercises:
Graded Exercises:
Note that for this homework, you must use foldr, filter, map, andmap, ormap, and/or build-list when appropriate.
Tetris Pile Up
General Advice: Since this is a project homework, the exercises are somewhat
intertwined (as they were for Homework 6). Before you begin working on any one
exercise, make sure you read the entire assignment and understand the overall set of
features you are required to implement. In a project, there are
interdependencies. Data definitions you design for one part must work for other
parts. Moreover, when you design functions, you are expected to plan ahead and
see if you can design a set of functions that could be reused to implement
multiple features—
Exercise 2 Incorporate the feedback you received on your last Tetris assignment (Homework 6, see feedback on handin server). Make the suggested improvements and fix any bugs that may still exist following the guidance below.
Note If your Homework 6 feedback (and point deductions) indicates that your data definitions were problematic—
i.e., they are data definitions that made, or we think will make, your life harder when implementing the rest of the project— your first task is to revisit those data definitions. If you do not correct these design errors, you will likely lose points for them again on this homework. Remember that our goal is not merely to hack together something that works, but to craft a program that is designed well. Here we give a more detailed explanation of what data definitions you should have had. (There is no need to change your data definitions if there is no negative feedback or points deducted; there are many different ways to design good data definitions and ours is not the only one.)
Data Definitions For Homework 6, you should have had data definitions (and related interpretations, examples, templates) for the following:
World: a World consists of a Piece. That piece represents the falling piece, which has at least one brick visible in the grid.
Piece: a Piece consists of two things: a collection of Bricks, and a center point. The “collection of bricks” represents all the bricks that form a piece. For Homework 6, these collections contained exactly four bricks, but Homework 6 indicated that we want you to design the collection so it is possible for a collection to have more than four or less than four bricks for future homeworks. This implies that a collection ought to be more flexible and contain an arbitrary number of bricks. The “center point” represents the point around which the piece spins when rotated. (A later hint in Homework 6 told you that the center point should be a grid coordinate, not a pixel coordinate.)
Brick: we gave you the data definition and interpretation for Brick and you were expected to use it unchanged.
Notice that all we have done above is help you parse the sentences we wrote in Homework 6. Reading carefully and analysing text is a necessary skill in most careers. Remember that in order to solve one part of a problem, you’ll often have to make use of information gleaned from different parts of a document (or assignment)—
as we said above: projects have interdependent parts. Try to practice this skill as you read the rest of this assignment. Seven Functions for Seven Shapes of Pieces For Homework 6, Exercise 3, you should have designed functions to produce Pieces for each of the seven shapes shown in that homework and place them at a given x and y grid position. These functions do not draw the shapes shown. Instead each one generates a Piece that consists of a collection of four Bricks and a center point—
here the sensible interpretation is that the center point should be the given x and y, and one of the Bricks in your collection must be at that x, y position. The collection of Bricks you produce should be such that when you draw these bricks (which you have do for Homework 6, Exercise 4, as part of rendering the world state), you get an image that matches the shape and color shown for that piece in the assignment. Producing a New Piece at Random x Next, for Homework 6, Exercise 3, you should have designed a function that places a piece of a random shape (one of the seven shapes) at a random x (grid) coordinate at the top of the grid, while ensuring that all the Bricks in the Piece are within the left and right edges of the grid.
One strategy for generating this “random piece within bounds” is to:
Decide on the random shape and the random x, then use the appropriate one of the above seven functions to generate the piece.
Next, calculate an offset by which the generated piece is out of bounds. (The offset could be an x-coordinate offset or a Posn offset—
you can try to think about which design gives you greater reuse.) Finally, adjust the position of (i.e., move) all the bricks in the piece to compensate for the calculated offset—
this adjusted piece serves as the “random piece within bounds” that you had to produce. Another seemingly plausible strategy is to randomly generate an x-coordinate between 3 and 17 because then regardless of what piece you generate, it’ll be within bounds. The drawbacks to this strategy are that it excludes valid positions for many pieces and it isn’t future-proof when we add larger pieces (as we said we would in future assignments). (Note: Do not adopt this second strategy. We describe it here only to explain its drawbacks.)
Rotation and Origin Homework 6 provided you with the function brick-rotate-ccw for counter-clockwise rotation, which you were required to use unchanged. The code for that function—
in particular, its purpose statement and its behavior when you try using it, especially on the “T” piece— provided a hint about where the origin of your grid should be. Note: we will not require that you fix the location of your origin, but you should leave us a comment explaining if you have kept your origin at the top-left.
Handlers Once you have fixed the above parts of your Homework 6 Tetris, you’ll be on the right track as you fix the design of your big-bang handlers for rendering the world state, changing the world state on each clock tick, and to handle keyboard commands.
Exercise 3 Next, you are to use local and list abstractions (e.g., map, foldr, filter, etc.) wherever your functions may benefit from them, especially for the lists of objects in your project. You may also use lambda terms in place of locally-named helper functions, but keep in mind that you should only use lambda if the function is simple—
at a minimum, the function’s signature and purpose should be easy to figure out. You should notice that the length of your program decreases considerably.
Exercise 4 Extend your Tetris to support two additional kinds of pieces.
A piece that consists of a single brick of color "magenta":
A piece that is like the “I” piece but contains 5 "darkgreen" bricks in a row instead of 4 "blue" ones:
Make whatever changes are required to support the new kinds of pieces. For instance, you’ll need two additional functions that produce a Piece for each of the two new shapes at a given x and y grid position. Change your existing implementation so that when you randomly generate a new piece, it is one of the 9 kinds of pieces now available.
Exercise 5 Let’s make our Tetris more like the real game by making bricks pile up on the base of the grid. Once a piece lands on the base of the grid or on another piece, its bricks are frozen in position—
that is, they should be added to the pile of bricks accumulating in the grid. After a piece has landed, a new piece of randomly selected shape and at random x coordinate should start to fall from the ceiling. Make any necessary changes to your Tetris to support this functionality. Note that you will need to extend your World data definition: in addition to a falling piece, a World should now also contain a pile of bricks. You must decide how to represent this pile of bricks. Here are some things that should influence your decision:
Consider row-clearing which we will implement in a future homework: recall from Homework 6 that if the board has any full rows of bricks, those rows are deleted, and everything piled up above those rows is shifted down. When we implement row clearing for Homework 10, we will end up with fragments of pieces left over after clearing. That suggests that it will no longer make sense to keep track of pieces, so instead we should only keep track of the bricks themselves.
Once a piece has fallen and frozen onto the pile, it will never rotate or drop again—
in other words, it no longer needs to behave like a piece. Notice that our Brick data definition includes the color of the brick—
that is, instead of asking you to put the color of a piece in Piece, we put the color in a Brick. Why do you think we did that? Lastly, consider that there may be an arbitrary number of bricks in the pile at any given moment during the game.
Exercise 6 Another feature of real Tetris is that the game ends when any part of the pile of bricks accumulating in the world extends beyond the top of the grid. Extend your Tetris to support this functionality.
Exercise 7 Add the option to start your Tetris with randomly generated debris in the grid—
that is, in an initial world state that has a non-empty pile of bricks. Specifically, change your main function to accept two inputs: the number or rows n at the bottom of the grid that are allowed to contain some initial debris (i.e., bricks), and a probability p. The probability p is a natural number between 0 and 100 that represents the probability with which each cell in the bottom n rows contains a brick (Hint: for each cell, you can use (random 101) to generate a natural number between 0 and 100 (inclusive) that you can use as a probability.)