What's here?
These are solutions to a set of exercises, along with a fair amount of additional background explanation. Note that I've gone into more detail with those explanations that we would have wanted or expected students to do, but I'm using these as an opportunity to solidify your understanding; don't take this as an indication that you should be writing your answers in as much detail as I am here.
Problem 1
- Think about the alternative: What if things were visible as soon as we drew them? This would cause the problem that we might sometimes see some of the changes without seeing the others. In drawing a typical frame, we might do all of the following:
- Fill the background with a solid color.
- Draw overlapping objects, in an order that allows some to be seen as being "on top" of others.
- Position some text on the screen to display the current score or other details about the status of the game.
If we could see those changes as they happen, it would mean that we'd see an empty screen — with only the solid-colored background — then we might see some of the objects but not all of them, then we might see the score appear. Sure, we might not see them be drawn so slowly that we could pick all of them out, but there would almost certainly be some times when we would see a "partial" frame of animation (i.e., with some things drawn but not others). The solution is to use a technique called double buffering, in which we assemble one picture in memory while displaying another, then swap the two (i.e., start displaying the new picture and stop displaying the old one, then we can assemble the next picture where the old picture was, and so on). pygame.display.flip() is how we switch between the two buffers.
- There are at least a couple of practical benefits:
- While it's certainly true that a higher frame rate can look better to players, there's a tradeoff being made there: We're spending processing power — which drains battery life more quickly, makes other programs run more slowly, uses more power that we might have to pay more for, has an additional environmental impact, and so on — drawing all of those extra frames. If the game looks good at 60 frames per second, spending twice as much processing power to draw 120 frames per second might not be worth what we gave up to do it.
- Using a
Clock object to control the frame rate also allows us to schedule events more simply. For example, if we wanted to take some action — draw the screen differently, add 10 points to a player's score, or whatever — every time one second passes, that's a lot easier to do if we know there are, say, 30 frames per second, in which case we can schedule it every 30th frame. If every time we call into our game's underlying logic to prepare to draw the next frame, we know that 1/30th of a second will have passed by the time we're drawing it, we know how much activity we need to simulate (i.e., how far objects will have traveled, whether two objects will have collided, etc.).
Problem 2
- In a program with a user interface, the model is focused on the underlying details of how the program behaves, while ignoring the details of how the program looks and feels. In a PyGame-based game, for example, the model would understand where various objects in our game world are, what their status is (e.g., health, damage, or whatever), and would be where we'd implement the various algorithms for how those objects interact with each other.
- In a program with a user interface, the view is focused on how the program looks and feels; in other words, its job is to present the model's data in some way, and to accept inputs that are translated into underlying actions on the model (e.g., a mouse click in a particular place might cause an object in the game world to be damaged).
- There are probably a lot of good answers here, honestly, but a couple of them are below.
- The study of software engineering teaches us that complexity is best handled by abstracting it away. To the extent that we write a program where various parts are isolated from one another as much as possible, with modules that have high cohesion and low coupling, we're better off. A natural division in a user interface is the division between how it looks and how it works underneath. That way, changes to one have less impact on the other.
- Separating models from views allows more than one view to be built for the same model. For example, you could imagine a flight simulator, in which it's possible to switch from one "view" to another (e.g., a camera looking out of the cockpit, a camera trailing behind the airplane, a camera situated on the ground in an air traffic controller's tower, or whatever), but in which the game would otherwise behave the same way. If we separated the model (i.e., how planes fly, and so on) from the view (i.e., how we draw the current state of the world), this becomes straightforward.
Problem 3
The key to this problem was figuring out the appropriate formulas to use to convert the coordinates. There were a few things that we necessary:
- Figure out where the center of the display is. (Since the display is resizable, we can't know this without asking.)
- Figure out the angle in radians, since we'll need that to pass to
math.cos and math.sin.
- Use what we know to make the appropriate translation.
A complete solution is available at the link below.
Problem 4
There are at least a couple of things that become more difficult when we involve a computer network.
- Computer networks are more fragile. That's not to say that they crash constantly or that they rarely work, but simply to say that the range of things that can go wrong — and the proportion of the time they do go wrong — is bigger than in a program that runs entirely self-contained on one computer. This means that we'd need to build a way to accommodate those kinds of failures, with the bar being set higher, in the sense that users have a higher expectation of a smooth experience in a program with a graphical user interface than they do with a program that runs 1980s-style in a Python shell.
- In a PyGame-based game, our code is perpetually in a race against time. Our frame rate determines how often we want to respond to new inputs, draw a new frame of animation, and so on. If that frame rate is 40 frames per second, then we have a budget of 25 milliseconds (1/40 of a second) to finish everything — ask PyGame for the events that have occurred, make updates to the model accordingly, assemble the next image to be drawn, then call
pygame.display.flip(). If we don't, then the program will stop responding to inputs temporarily, it won't draw frames of animation when scheduled, and it will generally feel "jerky" to a user. Why network access introduces the potential for problems is that we can't easily predict how long it might take. If the server responds more slowly than we expected, if our Internet connection is flaky temporarily, or whatever, then we'll have a problem, unless we use fancier techniques — things we haven't learned about in this course, such as threads or multiprocessing — to run the network access "in the background," outside of the scope of our game loop, so that the game loop stays responsive even if the networking "falls behind."