- Real Programming S01E01: Getting Started | Video
- Real Programming S01E02: Look, Ma! A Test!
- Real Programming S01E03: Baby Needs New Shoes!
- Real Programming S01E04: Into The Frying Domain!
- Real Programming S01E05: Models Don’t Do Real Work!
- Real Programming S01E06: Getting A Little Testy
- Real Programming S01E07: A Spike and An Event Bus
- Real Programming S01E08: Tighter, Tighter
Real Programming S01E05: Models Don’t Do Real Work!
There’s still game stuff going on in the model, so let’s get it out of there.
If you want to follow along in the code, the project is at github.
Hey! Episode Five, Real Programming. The show where the slogan has 142.1 story points left before it’s delivered.
It’s been a while since we last chatted, so let me do a quick review. We’ve broken our code up into three layers. At the top, we have a View class. The View’s job is just to represent things onto the screen. At the bottom, we have the Game class or Game layer, and its job is actually to run the real Yahtzee game that we’re working on. And in the middle, we have the model.
Let’s take a look at the code real quick. And we’re going to see a problem right away.
All right, so this is our View. And it looks crisp and tight and very simple at this point. It just basically lays out these visual components. And it hooks each one of them over and over again into the model, the Model.start, the Model.roll. And these guys are coming from inside the Model.dice.
Down at the bottom, we have the game. And look at this game. Hm.
Gertrude Stein said of Los Angeles many, many years ago, she said, “The problem with Los Angeles is there’s no there there.” And that’s the problem with this Game class. There’s nothing there. All it’s holding on to right now is dice. So what’s going on with that?
Well, if we look at the middle, the model, we see it. Here’s the deal. This stuff in the model that is handling how many roles are left, and whether or not you can roll, and what to do when you actually roll, well that’s all game stuff, right? That’s game stuff. That’s not model stuff. Remember, the model’s job is just to pass things on to the game, get results back from the game, and stuff that into the properties that the View is watching. So this work doesn’t belong here. It belongs in the game.
So how are we going to go about doing this? Well, if we take a look at the model’s tests, it’s actually here. Ignore the text for the moment and just concentrate on the names of these methods. You can’t roll at the start. You can roll after you have started. Rolls actually change dice properties. These are all game-ish sorts of things. We throw on an unallowed roll and so on.
So what I want to do today is I just want to transliterate most of these tests so that they are tests on the game. So that the game is actually doing the work for us instead of having the model do it.
Let’s just start with this one. I’ll get myself a test for the game. And I will transliterate this test. And then we’ll get it passing.
So here’s that first test. Assert that canRoll is false. Notice that there’s a difference here between this guy, which says canRoll. And this guy which says canRoll.value. Why? Well, because in the Game level, we don’t use properties. We just use flat Kotlin regular fields. So that’s that difference.
Anyway, we got the red here because canRoll doesn’t exist yet. So let’s go get this guy passing.
OK, well, that was easy enough to pass. How did I do it? I rather cleverly added a variable called “can roll” and assigned it the value of false. So that got us right through that pretty quick.
What’s next? Well, you can roll after you do a start. So that will be the next test we move over.
And here’s the test. Can roll after start. I start. And then I assert that I can roll. How did I solve it? Well, I changed “can roll” from being a Val to a Var. And I said canRoll=true.
That code is not going to last very long, though, because we’ve got a test coming that’s going to break it. That’s this bad boy. Three rolls allowed without a start. So you can roll three times, but then it should be false again after the start.
Let’s move that guy over. Moving him requires us to implement roll in the Yz game. Now I need to make canRoll just a little bit smarter. Basically, it depends on how many times we’ve roll already. If we haven’t rolled at all, we can roll. If we’ve rolled once or twice, we can roll. But if we rolled three times, we cannot roll. That’s pretty easy to express in Kotlin.
All right. So we now have a private variable, rolls in round. I’m going to start it at three. We’ll come back to that in a second. Val canRoll Get.
Now look at this. This will be new to you if you’ve never used Kotlin before. But what we’re saying here is that there exists a field called canRoll. And anytime somebody dereferences the field, we want to call this method, Get.
Now this part here may seem a little strange to you, but this is just the Kotlin way of saying if a function just returns a simple expression, we can define it like this. This guy is exactly the same as the alternative body. In fact, there’s hotkeys that will let us flip that body. Convert to block body now makes it look like this. So that’s what this equal sign was doing. And we can convert it back with just a flick of the wrist.
So what we’re saying is, as long as you haven’t rolled three times, you’re allowed to roll. Now, when we start a round, we set the rolls that are in that round to 0.
When we actually roll, we go ahead and roll the dice. And then we say rollsinround plus equals 1. Because we just did a roll. This business of initializing it to 3 is our way of saying, well, at the beginning, you still haven’t called Start yet. So all of this allows this particular test to pass. Let’s go back and see what else we got.
What about throwing on an unallowed roll? OK, that’s good. Now this time I don’t have to change any code to do the copy except to make it called the Game instead of the Model.
But we’re going to get a red test here because, in fact, my game roll code doesn’t throw this exception. So let’s make it do that. And then we’ll be good to go.
And there we go. That’s passing that test. What else so we got? OK, we’ve handled these two. We have not done this one yet. “Roles change the dice” properties. Well, it would not be the properties. But we certainly can test that we’ve changed the dice. Let’s go ahead and do that.
And there you have it. We didn’t have to actually change any game code. It was already doing this correctly. All we did was move the test and transliterate it to game terms instead of model terms.
Ah, the Start actually resets things correctly. And that’s our last test for that game.
And there it is. And again, that didn’t require any further coding in game. It just locked down our test a little bit more tightly around the game test.
So at this point, our belief is that game does everything that model wants it to do. But we haven’t actually made model rely on what game does. Instead, it’s doing it for itself. So it’s time to fix that.
OK, first we have to fix this canRoll. Now, remember, this is really a property, canRoll. So we’re going to need a similar property here. But this time, instead of doing a direct binding to rolesleft, what we will do is say give me a simple Boolean property.
So to convert it, we say canRoll. And we wrap the game’s canRoll value in a simple Boolean property. That’s going to ultimately allow us to get rid of this rollsleft property. But I think we might have a little bit more.
Role here, if not canRoll.value. Well, it’s really ifnotgame.canRoll. And we don’t have to throw that exception in any case because the game is throwing it. So we can actually get rid of that. And we don’t call the dice directly to roll.
Now, is this part the same? Well, for the die, yes, we are updating the dice in this way. Although I don’t care for that. We’re going to have to do something about that. But we’ll do something about that in a bit.
What about this? Hm, we no longer need to attract the rollsleft property. So we can lose that as well. And that leaves us with fixing up the start.
Now this is a little bit messy. But what it comes down to is this: We certainly want to call game.start. But after the game.start call, we’re maintaining these properties in here, the die model and the canRoll. And anytime we tell the game to do something, we need to update those. That also includes these guys here, this PIPS.
So any time I change something down in the game, I then need to call this function that doesn’t exist yet called Update. Well, the core of that is right here for now. So let’s extract that method out and see what we get.
So I’ve extracted the method. Now I want to call that method from inside after I have called the Start. Now, this is almost it, but not quite.
Now, the update has to update the PIPS for sure. But it also has to update that canRoll property. And that’s what we’ll do now. And so now you see that the update really is updating all of the visible properties of the Yz model. And that’s exactly what we want to do. And we want to update after a roll. And we want to update after a Start.
So at this point, our model tests should just fly. Let’s see how good GeePaw is at this.
Ah! Like a boss, we changed that like a boss! OK, we did a terrific job with that. I’m pretty pleased with it. Let’s make sure our game still runs, that’s always useful, too.
When I run the game, I cannot roll. And then I can start, after which I can roll three times once, twice, three times. Now I can’t roll anymore. But I can start again, and then I can roll.
Except I notice, hm, that’s interesting. It doesn’t do anything when I start. And what it really should be doing is changing all these guys to unknowns.
Now that’s not a new bug. That bug was already present. But you know what? Right now, I feel like we’ve come a long way today. And we should fix that, just a little tiny thing. So let’s go fix that.
We do that by changing the game code so that the call to start resets those dice PIPS to unknown. I’m going to actually do it down at the level of the dice. The dice should know how to reset themselves.
So now I’ve called dice.reset. And it should reset all these guys to unknown. Now when I say roll it three times, one, two, three.
Now, when I say start, these guys should all go back to being question marks. And they do, sweet! I’m telling you, kids, we’re getting good at this. We’re going to be great, famous Yahtzee programmers someday.
So let’s take a look around and see what we got here. One thing that I mentioned that it was bugging me before is this guy is public. And he’s being exposed to the model. That could mean that the model could mess with him in ways that are unpleasant.
I much prefer this kind of situation where, essentially, nobody above the level of Game can do anything about canRoll, can do anything about rollsinround, all they can do is call Start, call Roll, and look at those two values. I would like to have that be the same situation here.
So this is what I’m going to do to pull that off. If I go to the dice, the PIPS right now, totally exposed to the world. They could easily be changed pretty much at will. What if I said, hm, I don’t want to do it like that. I want to do it like this, valPIPS.
Now, what this does is now this PIPS field shows as an array events. It can be dereferenced exactly the same as it was before, although without mentioning the dice. And, because I’ve converted it to a list, I can’t make changes to it. So nobody can mess with those PIPS.
The last piece of this is now privatize the dice themselves. Let’s see what problems that causes when we do it.
OK, I get some compilation errors here. What are they? Well, over here in the model, we get this complaint, dice of dienotPIPS is equal to– there is no game.dice.PIPS anymore. What if we just get rid of the dice intermediate word? Will that take care of this one? Seems to, but I still got other issues.
Same thing, exactly, kill off the game. Let’s see how this goes. Ah ha! We got all the way through our test. And I’m just going to be pretty confident that our run is going to work as well. And it seems to.
So what did we do? Well, the gist of it is this. We took this guy and moved him into the private space. He’s no longer exposed to the world. And everything below here, the canRoll and the PIPS, and the Start, and the Role, these are the only parts of the game that can be called by outsiders or manipulated by outsiders.
This is effectively creating a firewall. The Game class is a firewall. Everybody above Game or around Game has to go through Game to change anything below Game. It’s a way of giving us some fairly secure system, a safe system. It’s hard to break it. Because, if you try to break this, what you’re going to do is you’re going to break tests.
Now, if you instantiate your own dice for some other game and manipulate them in various ways, that’s between you and your spiritual advisor. But in our environment, with our team, we will say everything below Game belongs to Game. And nobody gets to touch it unless they go through Game.
That is, essentially, a facade pattern. Now, in this case, as we can see, it ain’t facading much. There isn’t very much below the level of Game right now. There’s just the dice. But I got a feeling that might change.
Anyway, I think we’re done for this section. It’s a little short, but we did a little bit of good work. Let’s foreshadow a little.
In the game in real life, what you do is you keep some of the dice. You don’t just roll all five dice over and over again unless that’s what you want to do. Instead, you choose individual dice and you say, let’s keep these dice. And then, if we keep those dice, when we roll, they don’t change. Hm, that’s getting us a little bit closer to the game. So maybe that’s where we should go next.
It’s also true that, even if I do that, even if we implement that next, we still don’t have a damn game. I wish I had a game, even a bad game. So it may be that that’s where we go next. I see those as our two possibilities. And we’ll pick one or the other and take a swing at it next time I see you.
I’m GeePaw, thanks for watching! Have a good day!