Real Programming S01E03: Baby Needs New Shoes!
Watching those itty-bitty numbers for the dice is making me crazy, so let’s get some dice images going today!
If you want to follow along in the code, the project is https://github.com/geepawhill/yz
Hey there. Welcome to episode 3 of Real Programming, the show where we’re still trying to figure out what our slogan is. We’re working on a Yahtzee program. And today, we’re going to push really hard and see if we can get some images into that program. Let’s take a look.
Here’s our little program. And what’s going on here is, if I press the Start button, I can then roll. And those little, itsy-bitsy, teensy-tiny numbers that you can’t even see, unless you’re on your big 60-inch screen, change accordingly. And that’s what we want to fix. Let’s take a look at how we’re rendering these guys now and just generally refresh ourselves about the view and the model.
This is MainView. It’s a bad name. Let’s change it. Why is it a YzView? It’s a YzView because it’s watching a YzModel. That’s what this line is doing. Model is equal to YzModel. It has a pane, that’s the part of this that’s inside the top-level window. It also has an hbox, which is this row of things here.
In that hbox, we’ve just blurted out some controls. We have a button, we have another button, called roll, and we have five labels. In fact, looking at those five labels, right away I want to change that so, boom. Let’s do that. Same thing, really. I just compacted things. I put the action lines all on one line, and I changed this from being a man old-style walking to the model.dice for-each.
When you do a for-each, you don’t have to declare the variable. It comes through already as an it. So what are we seeing here? Let’s make sure we understand that before we go any further.
So what we have here is, we have a View. And the View is watching the Model. And the parts of View are watching parts of the Model. So the Button’s Action is actually calling Functions on the Model. Whether or not the button is enabled, like the roll button, is watching a Boolean property. Our Labels are watching the dice, which is an array of simple integer properties. And that’s what we have right now.
Now, you might have had some doubt about whether those numbers were changed because they’re so damn small. Let’s turn them into images.
So if we’re going to get images, instead of those stupid, little, tiny letters for the Label, we’re going to have to understand how images work. In JavaFx, you have an image object, which does require the JavaFx framework to be running, and you have an ImageView.
An ImageView is a window, an image object is the bits associated with that object. So if we were to try to turn our Labels into ImageViews, we would have to be watching, in some sense, some images from somewhere. Well, where are we going to put them?
So let’s go back to our picture. What we have right now is we have these Labels, and they’re watching this array called dice full of simple integer properties. But you know, those dice aren’t really dice, they’re just pips. They’re the little dots on top of the dice. And the count of those is what’s in those simple integer properties.
If we were to try to convert these guys, so that, in our new code, we had ImageViews instead, the ImageViews would be wanting more. They would be wanting dice to incorporate an image. And that suggests, to me, that we can no longer use simple integer property for our purposes. Instead, we want something new and different. Let’s call it DieModel because it’s the model of an individual die.
What are the things that we want? Well I still need pips, right? I still have to have settable pips somehow. Although I notice, right now you can set the amount of pips to anything, whereas we’re going to want to limit that so that you can only set its legal values.
But in addition to the pips, it’s going to want an image property. And the image property is going to change as the pips change. So that is our first activity, is to instantiate this DieModel class and start writing a few tests against it to get it to go.
To get us started, I created a DieModel test, that’s this guy, and I gave him a DieModel. What’s a DieModel? Nothing. It’s just a class I pooped right into this thing. I’m going to keep them together for a little while until I decide I’m ready to actually ship them, in which case I’ll split it out into another file.
And what we know about the DieModel, first of all, is that he starts with a pips value that is equal to unknown. And of course, he won’t just start to that. After we have scored a round, we’re going to set those to unknown again, too. So let’s do that.
OK here’s a DieModel, it’s got a variable pips, its int, and its unknown, initialised. Unknown is a companion object. If you haven’t seen this before, this is Kotlin’s way of saying static. It’s a constant value. Unknown is equal to 0. Now assert that model.pips is equal to DieModel.unknown. And we have a winner. Very good.
Now, when we change the pips, we want the pips to be changing. That might seem a little over-testy, but trust me. Go with me for another two minutes. OK, here’s that rather pathetic test. Of course, if you change a variable’s value, it stays changed. That’s fine.
But notice the name, legal value changes on set. So only if the value is legal, are we allowed to actually change things. However, I don’t have a test for that. Don’t you think I should put that test in? I do.
So what happens if we say [? minimal ?] legal value has no effect. Model.pips is equal to negative seven, assert that Model.pips is equal to DieModel.unknown. Oh, that’s not no effect, that’s changing it to unknown. Fix that name up.
Now, does that pass right now? Of course, it doesn’t. It does not pass. You can’t see it, but because I was expecting negative seven to be equal to unknown. But it isn’t negative seven is equal to negative seven. Now you see why I split the test.
So now I’ve tweaked my variable, here. var pips int is equal to an unknown now has a setter. If the value is in the range 0..6, which is inclusive, that means all seven of those numbers, then the field is equal to the value. Otherwise, the field is equal to the unknown.
This field business inside a set method is just part of Kotlin. Every one of the Kotlin properties actually has a backing field. And that name field refers to it. Now, will we get through it this time? Yeah. No problem.
So right now, the Kotlin property isn’t a real JavaFx property. I know, it causes confusion, right? Kotlin calls its fields property, and so does JavaFx call its properties, properties. Anyway, we need to make this guy a property, as well. How do we do that?
OK, this is what it looks like at its fully protected manner. What it says is, there’s a property called pips property. It is a read-only property. It’s a read-only integer wrapper. And what that means is, the outer guy, pips property, actually has an inner guy that is read-only to anybody trying to get it.
So you’ve got this super wrapped around this minor. And the name of that minor is this, the read-only property coming from the pips property, which we have fronted to the public world as pips read-only. So now anybody can check pips read-only. It’s a read-only property, and they can still get it.
That meant that our variable had to change. It had to have a getter, that gets the value out of the pips property, and it had to have a setter that continues to do our check. Now, are we still passing our tests? Yeah, [? ship ?] [? it. ?]
So now we’ve got our DieModel. It’s doing what we want it to do. Let’s get it out of this file, and once it’s out of this file, let’s hook it up inside the model and see what we get. OK, it’s in its separate file, go to the model. Instead of making this a list of simple integer properties– it shouldn’t actually be a list, It should be an array. Let’s make it an array of DieModels.
So that’s what it looks like. Now, that’s going to break everything instantly. Why? Because we have several places in the code where we refer directly to the simple integer properties. But now, we’ve introduced that property as a subfield. So we need to work with it correctly.
Here’s one right below us right here, Die.value. We can now say Die.pips. It’s not read-only. We can just assign to it and we’re just fine. So that takes care of that one, but you can see we also have a broken view and a broken model test.
What about the view? We make the view it.pipsReadonly. So that takes care of the view, what do we have going on over here in the model test? Well it’s here, right? Assert that the value– well, that value is pips.
So let’s run our tests again, make sure we’re now operational. And let’s see if we’ve changed any behavior on the screen. I don’t believe we will have. We still Start, we still Roll, Start again, Roll again. It’s all good man, it’s all good.
So what’s next? Now that we pulled out this DieModel guy, we’re going to have to give him an additional property. We gave him a pips property, now we want to give him an image property. Now, we’re going to run into some trouble, here. I’m calling it in advance. You’re going to see it.
So when we go back to the DieModel, what I really want to do is, do something as simple as these two lines, right? But instead of holding an integer, I want them to hold an image. Let’s try that and see what happens. So when I just try this naively, of course, I immediately find out you can’t just make an image, right? Can’t just make an image, you’ve got to have expressly loaded it.
So I’m going to take this guy, the starting image, and I’m going to replace it with the particular image for unknown. OK, it’s kind of hard to see. But what I did over here was added a resource directory and I added a bunch of images.
Now, I made these images in Adobe Illustrator, and please don’t yell at me. This is my first ever attempt to make dice in Adobe Illustrator, and I swear to you it took longer than we have spent making this video, so far. Anyway, I got images.
The special one that’s interesting is, I have a question mark for the unknown. And the rest just look like dice. So going back, then, to our DieModel, we want this guy to start as the image for unknown. OK, this form of the image just takes, literally, the path down through the resources tree to the file, question.png. Let’s see what happens when we try to run this, as a test.
Woah. Explosion, we ‘sploded. Look, every single test is failing. Now let’s see, looking at the screen, now that I’m up top, there’s actually error messages in here. And they are all the same error message for every one of these. Internal graphics not initialized yet.
Now, watch this. I’m going to run, instead of test. Am I going to get the same problem? No. Everything’s perfect. What’s going on?
OK, so here’s the deal. When you run your application, it’s a JavaFx application. What it’s doing is, it is firing up the JavaFx framework. And it fires that up before any windows are rendered anywhere. It’s just a fundamental part of launching such an application.
But when we run our tests, we’re not running the JavaFx main. That means the framework isn’t started. And that particular error message, that is saying, hey, I did not initialize the graphics OK, yet, is telling us you don’t have the JavaFx framework started.
Now, I’m going to fix that in just a minute with a quick copy, paste, but before I fix that, I just want to make it clear. When I first encountered this bug, about two years ago, I went insane figuring out how to deal with it. It turns out, it’s not very hard, but you do have to know in advance what you’re doing.
So let’s take a look at a solution. OK, here we go. In the companion object to DieModel, I have added these lines. Now, if you’re not used to Kotlin, and you’re not used to JavaFx, this is going to be pretty weird. In fact, this is not hard to do.
What it amounts to is this, this is a static block. This is how we say static in Kotlin. This is a static initializer block, which is to say it is code that is run the very moment the DieModel is loaded for the first time. And what is that code calling? It’s calling Jfx.required. With those lines in it, the JavaFx framework will get fired up, before the test. First, let me prove that.
There you go. We’re good, we’re good. Now, let’s take a look at Jfx, and see what it really does here. Here is the deal. The key line is here it’s this. Here’s the thing. I am telling Swing– Swing? Are we using Swing? Well, Swing is built into Java. And JavaFx no longer is. And Swing knows how to construct a panel inside it, a Swing window, that contains JavaFx stuff.
So what’s happening here is, I’m saying, hey Swing, in a second will you make a Jfx panel for me? And while Swing is doing that, it will fire up the JavaFx system. And the rest of this is, I have a latch, instead of doing a sleep, so that I can be sure that by the time I fall off of run JavaFx, JavaFx is actually running.
Now, up here, the function required that I’m invoking says, run JavaFx. It is possible that this will time out. If it times out, I want to know about it with an exception, and that’s all there is to it. This class, Jfx, is one that took me an hour to write, originally. But of course, it took me eight hours of looking at Stack Overflow to figure out what the underlying problem is. Anyway, you can see, we now have running tests.
OK, now where we before we were so rudely interrupted? Well, the issue is that we were in DieModel here and were testing its functionality, and we want to make sure that this new image property that we added, for DieModel, actually changes when the pips change.
So I’m going to start with my unknown case, right in the beginning, and I’m going to assert. So I added a second assert here, assertThat model– notice, I’m working, not through the var, through any sort of direct verb, but working through the property, here– that the value of that guy’s URL, images have a URL, must end with question.png. Does it pass? Trust me. Of course, it passes.
Now, the legal value should change it to something else. If I change this guy to five, then this guy is now going to end with five.png. And now, we’re saying it’s going to end with five.png and, oops. We’re out of luck. Let’s go write the code to change that.
OK to pass that bad boy, what I had to do was, first– still in our static, by the way, it has to be after that static initializer block. I’ve turned my hardwired image into an array of seven images, one for each dice face and that zeroth one for the question. All I do is, I change my setter.
Now, instead of just setting the pips property, we also set the image property. We do it in both cases. This is a little bit of dupe, here. Maybe we’ll fix that in a minute. Let’s see if we get through our test, though. We’re doing good. That last test was to assert that model.pips is equal to five actually changes that.
We’ll come back for a minute and talk about the minimumality of these tests, but you know what? I’m really excited to see what happens on the screen. So to fix that up we’re going to have to go to the view and instead of showing a label, here, that’s showing the number of pips, let’s do something cool. Let’s say it’s an ImageView.
And what we’re doing here, this might seem a little odd to you, but remember that property over there in the model? What we’re saying is my image– I’m in ImageView– my image property is bound to that read-only image over in the model. When the model’s image property changes, my image property changes. Let’s give it a run and see what it looks like.
Whoa, those are some awfully large dice. I mean they look cool, but they’re awfully big. What’s that about? Well, what’s going on here is that my size for the images, by default, was humongous. So we can fix that. We could fix it in ImageView, but again, I usually don’t want to write code in a view, if I can get away with it. I’d rather write it in the model.
So what can we do? We can go to our DieModel, and we can initialize the images a little better. So what we did was pretty straightforward. I just went ahead and added some extra arguments. Requested width, requested height, make it 100-by-100. Preserve the ratio, and go ahead and smooth the image as you load it. Let’s try this again.
Oh, nice. Now we have dice. If I roll the dice, look, they change interactively. That is exactly the behavior that we were looking for. I’m going to call this a win.
So there you go. Now, I’ve left some cruft in place. We’ll tackle that cruft next time around, and then we’ll do something else. For the moment, you know, we’re really only using two parts of the view and model architecture. There’s a third part, and that is the domain. We’ll take a look at moving some of our code out of the model into domain objects, next time around. I’m GeePaw. Thanks for watching. We’ll see you next time.